From ac3b033665e1d5b9833e40faf269cc28db66433a Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 21 Oct 2020 10:01:10 +0200 Subject: [PATCH 001/195] Add catalog import plugin to router --- packages/app/package.json | 1 + packages/app/src/App.tsx | 2 ++ packages/app/src/plugins.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/app/package.json b/packages/app/package.json index dc452e0803..254bae9c46 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -37,6 +37,7 @@ "@roadiehq/backstage-plugin-github-insights": "^0.2.7", "@roadiehq/backstage-plugin-github-pull-requests": "^0.5.2", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", + "@roadiehq/backstage-plugin-catalog-import": "^0.1.0", "dayjs": "^1.9.1", "history": "^5.0.0", "prop-types": "^15.7.2", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 81d08f58a9..883fe1e1ca 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,6 +27,7 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; +import { Router as CatalogImportRouter } from '@roadiehq/backstage-plugin-catalog-import'; import { Router as CatalogRouter } from '@backstage/plugin-catalog'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; @@ -83,6 +84,7 @@ const AppRoutes = () => ( element={} /> } /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index bdc3851318..64c49eceba 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -37,4 +37,5 @@ export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; +export { plugin as CatalogImport } from '@roadiehq/backstage-plugin-catalog-import'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; From 0ef0518262668d4754d2fa6e18c5a5099edf77d4 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 21 Oct 2020 10:01:10 +0200 Subject: [PATCH 002/195] Add catalog import plugin to router --- packages/app/package.json | 1 + packages/app/src/App.tsx | 2 ++ packages/app/src/plugins.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/packages/app/package.json b/packages/app/package.json index dc452e0803..254bae9c46 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -37,6 +37,7 @@ "@roadiehq/backstage-plugin-github-insights": "^0.2.7", "@roadiehq/backstage-plugin-github-pull-requests": "^0.5.2", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", + "@roadiehq/backstage-plugin-catalog-import": "^0.1.0", "dayjs": "^1.9.1", "history": "^5.0.0", "prop-types": "^15.7.2", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 81d08f58a9..883fe1e1ca 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,6 +27,7 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; +import { Router as CatalogImportRouter } from '@roadiehq/backstage-plugin-catalog-import'; import { Router as CatalogRouter } from '@backstage/plugin-catalog'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; @@ -83,6 +84,7 @@ const AppRoutes = () => ( element={} /> } /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index bdc3851318..64c49eceba 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -37,4 +37,5 @@ export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; +export { plugin as CatalogImport } from '@roadiehq/backstage-plugin-catalog-import'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; From 3a438e1833ba3a23e24ce1f06e735721f08537d9 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Sun, 25 Oct 2020 23:38:29 +0100 Subject: [PATCH 003/195] Add config-backend endpoint to generate config --- packages/app/src/App.tsx | 2 +- packages/app/src/apis.ts | 14 - packages/backend/src/plugins/catalog.ts | 2 + packages/catalog-model/src/location/index.ts | 6 +- .../catalog-model/src/location/validation.ts | 7 + .../src/ingestion/ConfigGenerator.ts | 57 +++ .../catalog-backend/src/ingestion/types.ts | 8 + .../src/service/CatalogBuilder.ts | 5 + plugins/catalog-backend/src/service/router.ts | 20 +- yarn.lock | 471 +++++++++++++++--- 10 files changed, 516 insertions(+), 76 deletions(-) create mode 100644 plugins/catalog-backend/src/ingestion/ConfigGenerator.ts diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 883fe1e1ca..8b10366c6e 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -84,7 +84,7 @@ const AppRoutes = () => ( element={} /> } /> - } /> + } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index c129bebfa2..567ac13c78 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -25,16 +25,6 @@ import { GraphQLEndpoints, } from '@backstage/plugin-graphiql'; -import { - TravisCIApi, - travisCIApiRef, -} from '@roadiehq/backstage-plugin-travis-ci'; - -import { - GithubPullRequestsClient, - githubPullRequestsApiRef, -} from '@roadiehq/backstage-plugin-github-pull-requests'; - import { costInsightsApiRef } from '@backstage/plugin-cost-insights'; import { ExampleCostInsightsClient } from './plugins/cost-insights'; @@ -59,8 +49,4 @@ export const apis = [ }), createApiFactory(costInsightsApiRef, new ExampleCostInsightsClient()), - - // TODO: move to plugins - createApiFactory(travisCIApiRef, new TravisCIApi()), - createApiFactory(githubPullRequestsApiRef, new GithubPullRequestsClient()), ]; diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index 3c205a3800..8d6c823dce 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -28,6 +28,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, + configGenerator, } = await builder.build(); useHotCleanup( @@ -39,6 +40,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, + configGenerator, logger: env.logger, }); } diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index ff696524ab..1b7587c1f0 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -15,5 +15,9 @@ */ export type { Location, LocationSpec } from './types'; -export { locationSchema, locationSpecSchema } from './validation'; +export { + locationSchema, + locationSpecSchema, + repoPathSchema, +} from './validation'; export { LOCATION_ANNOTATION } from './annotation'; diff --git a/packages/catalog-model/src/location/validation.ts b/packages/catalog-model/src/location/validation.ts index 27d74b9a82..28662119b0 100644 --- a/packages/catalog-model/src/location/validation.ts +++ b/packages/catalog-model/src/location/validation.ts @@ -34,3 +34,10 @@ export const locationSchema = yup }) .noUnknown() .required(); + +export const repoPathSchema = yup + .object<{ repoPath: string }>({ + repoPath: yup.string().required(), + }) + .noUnknown() + .required(); diff --git a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts new file mode 100644 index 0000000000..6879cd8291 --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { InputError } from '@backstage/backend-common'; +import { Entity, Location, LocationSpec } from '@backstage/catalog-model'; +import { v4 as uuidv4 } from 'uuid'; +import { Logger } from 'winston'; +import { EntitiesCatalog, LocationsCatalog } from '../catalog'; +import { EntityUpsertResponse } from '../catalog/types'; +import { durationText } from '../util/timing'; +import { AddLocationResult, ConfigGenerator, LocationReader } from './types'; + +/** + * Placeholder for operations that span several catalogs and/or stretches out + * in time. + * + * TODO(freben): Find a better home for these, possibly refactoring to use the + * database more directly. + */ +export class ConfigGeneratorClient implements ConfigGenerator { + logger: Logger; + + constructor(logger: Logger) { + this.logger = logger; + } + async generateConfig(repoPath: string): Promise { + const [ownerName, repoName] = [ + repoPath.split('/')[0], + repoPath.split('/')[1], + ]; + const config = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: repoName, + description: '', + annotations: { 'github.com/project-slug': repoPath }, + }, + spec: { type: 'service', owner: ownerName, lifecycle: 'experimental' }, + }; + + return [config]; + } +} diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index c586e2d438..2d7165c140 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -65,3 +65,11 @@ export type ReadLocationError = { location: LocationSpec; error: Error; }; + +// +// ConfigGenerator +// + +export type ConfigGenerator = { + generateConfig(repoPath: string): Promise; +}; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 4ec2a0834d..c232887b04 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -64,12 +64,14 @@ import { YamlProcessor, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; +import { ConfigGeneratorClient } from '../ingestion/ConfigGenerator'; import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { jsonPlaceholderResolver, textPlaceholderResolver, yamlPlaceholderResolver, } from '../ingestion/processors/PlaceholderProcessor'; +import { ConfigGenerator } from '../ingestion/types'; export type CatalogEnvironment = { logger: Logger; @@ -249,6 +251,7 @@ export class CatalogBuilder { entitiesCatalog: EntitiesCatalog; locationsCatalog: LocationsCatalog; higherOrderOperation: HigherOrderOperation; + configGenerator: ConfigGenerator; }> { const { config, database, logger } = this.env; @@ -276,11 +279,13 @@ export class CatalogBuilder { locationReader, logger, ); + const configGenerator = new ConfigGeneratorClient(logger); return { entitiesCatalog, locationsCatalog, higherOrderOperation, + configGenerator, }; } diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index c7d05afb2d..aefcca28e2 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -15,27 +15,33 @@ */ import { errorHandler, InputError } from '@backstage/backend-common'; -import { locationSpecSchema } from '@backstage/catalog-model'; +import { locationSpecSchema, repoPathSchema } from '@backstage/catalog-model'; import type { Entity } from '@backstage/catalog-model'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import { EntitiesCatalog, LocationsCatalog } from '../catalog'; import { EntityFilters } from '../database'; -import { HigherOrderOperation } from '../ingestion/types'; +import { ConfigGenerator, HigherOrderOperation } from '../ingestion/types'; import { requireRequestBody, validateRequestBody } from './util'; export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; locationsCatalog?: LocationsCatalog; higherOrderOperation?: HigherOrderOperation; + configGenerator?: ConfigGenerator; logger: Logger; } export async function createRouter( options: RouterOptions, ): Promise { - const { entitiesCatalog, locationsCatalog, higherOrderOperation } = options; + const { + entitiesCatalog, + locationsCatalog, + higherOrderOperation, + configGenerator, + } = options; const router = Router(); router.use(express.json()); @@ -121,6 +127,14 @@ export async function createRouter( }); } + if (configGenerator) { + router.post('/generate-config', async (req, res) => { + const input = await validateRequestBody(req, repoPathSchema); + const output = await configGenerator.generateConfig(input.repoPath); + res.status(201).send(output); + }); + } + router.use(errorHandler()); return router; } diff --git a/yarn.lock b/yarn.lock index 82dc99d277..84334952e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1673,6 +1673,22 @@ resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@eslint/eslintrc@^0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.0.tgz#bc7e3c4304d4c8720968ccaee793087dfb5fe6b4" + integrity sha512-+cIGPCBdLCzqxdtwppswP+zTsH9BOIGzAeKfBIbtb4gW/giMlfMwP0HUSFfhzh20f9u8uZ8hOp62+4GPquTbwQ== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + lodash "^4.17.19" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + "@evocateur/libnpmaccess@^3.1.2": version "3.1.2" resolved "https://registry.npmjs.org/@evocateur/libnpmaccess/-/libnpmaccess-3.1.2.tgz#ecf7f6ce6b004e9f942b098d92200be4a4b1c845" @@ -3218,7 +3234,7 @@ resolved "https://registry.npmjs.org/@material-icons/font/-/font-1.0.3.tgz#f722e5a69a03f20ef47d015cb69420bebeeaabe5" integrity sha512-aIRd0Z9b/HJ/O24KOaP7dNsXypMnXhpWrpLVYvQB/JesVqXYSbioYND200sR+C14a0LSCp+qWnWCnSXmN1hWGw== -"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1": +"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1", "@material-ui/core@^4.9.10": version "4.11.0" resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a" integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g== @@ -3254,7 +3270,7 @@ prop-types "^15.7.2" react-is "^16.8.0" -"@material-ui/lab@^4.0.0-alpha.56": +"@material-ui/lab@^4.0.0-alpha.49", "@material-ui/lab@^4.0.0-alpha.56": version "4.0.0-alpha.56" resolved "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz#ff63080949b55b40625e056bbda05e130d216d34" integrity sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw== @@ -3547,7 +3563,7 @@ dependencies: "@types/node" ">= 8" -"@open-draft/until@^1.0.3": +"@open-draft/until@^1.0.0", "@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== @@ -3628,6 +3644,11 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" +"@rehooks/local-storage@^2.4.0": + version "2.4.0" + resolved "https://registry.npmjs.org/@rehooks/local-storage/-/local-storage-2.4.0.tgz#fb884b2b657cad5f77aa6ab60bb3532f0e0725d2" + integrity sha512-LoXDbEHsuIckVgBsFAv8SuU/M7memjyfWut9Zf36TQXqqCHBRFv8bweg9PymQCa1aWIMjNrZQflFdo55FDlXYg== + "@rjsf/core@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.4.0.tgz#c50bcff0d8178948ce08123177399d8816d51929" @@ -3670,51 +3691,6 @@ react-router "^6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-pull-requests@^0.5.2": - version "0.5.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.5.2.tgz#455355fe5cdcc43c22b80a8ef3697378084680a1" - integrity sha512-GM+nWQZ+aehMclN70Pp35UQe3I46SQCjWiGZ6/0TfDzrueHln39L2UspUj1L/713xDDtDK8ZnTJdFn/qKx7j4A== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.25" - "@backstage/core" "^0.1.1-alpha.25" - "@backstage/plugin-catalog" "^0.1.1-alpha.25" - "@backstage/theme" "^0.1.1-alpha.25" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "^4.0.0-alpha.56" - "@octokit/rest" "^18.0.0" - "@octokit/types" "^5.0.1" - "@types/react-dom" "^16.9.8" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-router "6.0.0-beta.0" - react-use "^15.3.3" - -"@roadiehq/backstage-plugin-travis-ci@^0.2.3": - version "0.2.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.2.3.tgz#2d1c5a7ed3eab4fdcf95243b73dee44c0cff5a57" - integrity sha512-Xi8vZFbcm+D4ykyU3I9hImAd22F1v6M1F/H9CJanRHqj5esJxnuCS2+Kf+w09bwb9DuM8/c68PuMlf3PMIsOwg== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.22" - "@backstage/core" "^0.1.1-alpha.22" - "@backstage/core-api" "^0.1.1-alpha.22" - "@backstage/plugin-catalog" "^0.1.1-alpha.22" - "@backstage/theme" "^0.1.1-alpha.22" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - date-fns "^2.15.0" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-lazylog "^4.5.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - "@rollup/plugin-commonjs@^13.0.0": version "13.0.0" resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec" @@ -3728,6 +3704,19 @@ magic-string "^0.25.2" resolve "^1.11.0" +"@rollup/plugin-commonjs@^14.0.0": + version "14.0.0" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz#4285f9ec2db686a31129e5a2b415c94aa1f836f0" + integrity sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw== + dependencies: + "@rollup/pluginutils" "^3.0.8" + commondir "^1.0.1" + estree-walker "^1.0.1" + glob "^7.1.2" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" + "@rollup/plugin-json@^4.0.2": version "4.1.0" resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" @@ -3735,6 +3724,19 @@ dependencies: "@rollup/pluginutils" "^3.0.8" +"@rollup/plugin-node-resolve@^8.4.0": + version "8.4.0" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.4.0.tgz#261d79a680e9dc3d86761c14462f24126ba83575" + integrity sha512-LFqKdRLn0ShtQyf6SBYO69bGE1upV6wUhBX0vFOUnLAyzx5cwp8svA0eHUnu8+YU57XOkrMtfG63QOpQx25pHQ== + dependencies: + "@rollup/pluginutils" "^3.1.0" + "@types/resolve" "1.17.1" + builtin-modules "^3.1.0" + deep-freeze "^0.0.1" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.17.0" + "@rollup/plugin-node-resolve@^9.0.0": version "9.0.0" resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6" @@ -3765,6 +3767,15 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.0.0.tgz#e18e9f5a3925779fc15209dd316c1bd260d195ef" + integrity sha512-b5QiJRye4JlSg29bKNEECoKbLuPXZkPEHSgEjjP1CJV1CPdDBybfYHfm6kyq8yK51h/Zsyl8OvWUrp0FUBukEQ== + dependencies: + "@types/estree" "0.0.45" + estree-walker "^2.0.1" + picomatch "^2.2.2" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -4696,7 +4707,7 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.3.0" -"@testing-library/react-hooks@^3.4.2": +"@testing-library/react-hooks@^3.4.1", "@testing-library/react-hooks@^3.4.2": version "3.4.2" resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.4.2.tgz#8deb94f7684e0d896edd84a4c90e5b79a0810bc2" integrity sha512-RfPG0ckOzUIVeIqlOc1YztKgFW+ON8Y5xaSPbiBkfj9nMkkiLhLeBXT5icfPX65oJV/zCZu4z8EVnUc6GY9C5A== @@ -4936,6 +4947,11 @@ dependencies: "@types/express" "*" +"@types/cookie@^0.3.3": + version "0.3.3" + resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" + integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== + "@types/cookie@^0.4.0": version "0.4.0" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" @@ -5027,6 +5043,11 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/estree@0.0.45": + version "0.0.45" + resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" + integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.5": version "4.17.9" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" @@ -5117,6 +5138,11 @@ resolved "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== +"@types/history@^4.7.7": + version "4.7.8" + resolved "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + "@types/html-minifier-terser@^5.0.0": version "5.1.0" resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" @@ -5226,6 +5252,14 @@ jest-diff "^25.2.1" pretty-format "^25.2.1" +"@types/jest@^25.2.2": + version "25.2.3" + resolved "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz#33d27e4c4716caae4eced355097a47ad363fdcaf" + integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw== + dependencies: + jest-diff "^25.2.1" + pretty-format "^25.2.1" + "@types/jquery@^3.3.34": version "3.5.1" resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.1.tgz#cebb057acf5071c40e439f30e840c57a30d406c3" @@ -5970,7 +6004,7 @@ resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^v3.10.1": +"@typescript-eslint/eslint-plugin@^3.8.0", "@typescript-eslint/eslint-plugin@^v3.10.1": version "3.10.1" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== @@ -6005,7 +6039,7 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^v3.10.1": +"@typescript-eslint/parser@^3.8.0", "@typescript-eslint/parser@^v3.10.1": version "3.10.1" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== @@ -6342,6 +6376,11 @@ acorn@^7.1.1, acorn@^7.2.0: resolved "https://registry.npmjs.org/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.npmjs.org/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -9231,7 +9270,7 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== @@ -9597,7 +9636,7 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@*, cypress@^4.2.0: +cypress@*, cypress@^4.11.0, cypress@^4.2.0: version "4.12.1" resolved "https://registry.npmjs.org/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec" integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q== @@ -9930,6 +9969,11 @@ deep-extend@0.6.0, deep-extend@^0.6.0, deep-extend@~0.6.0: resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-freeze@^0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" + integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ= + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -10683,6 +10727,41 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es- string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-abstract@^1.17.5: + version "1.17.7" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" @@ -10773,6 +10852,11 @@ es6-weak-map@^2.0.2: es6-iterator "^2.0.3" es6-symbol "^3.1.1" +esbuild@^0.6.18: + version "0.6.34" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.6.34.tgz#76565a60e006f45d5f273b6e59e61ed0816551f5" + integrity sha512-InRdL/Q96pUucPqovJzvuLhquZr6jOn81FDVwFjCKz1rYKIm9OdOC+7Fs4vr6x48vKBl5LzKgtjU39BUpO636A== + esbuild@^0.7.7: version "0.7.7" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.7.tgz#fd86332d1c0a047231bd6da930666028c40c14bf" @@ -10861,6 +10945,13 @@ eslint-plugin-cypress@^2.10.3: dependencies: globals "^11.12.0" +eslint-plugin-cypress@^2.11.1: + version "2.11.2" + resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0" + integrity sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA== + dependencies: + globals "^11.12.0" + eslint-plugin-graphql@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz#d238ff2baee4d632cfcbe787a7a70a1f50428358" @@ -10957,6 +11048,23 @@ eslint-plugin-react@^7.12.4: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" +eslint-plugin-react@^7.20.5: + version "7.21.5" + resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" + integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.18.1" + string.prototype.matchall "^4.0.2" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -10973,6 +11081,14 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-utils@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" @@ -10980,11 +11096,23 @@ eslint-utils@^2.0.0: dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== +eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + eslint-visitor-keys@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" @@ -11032,6 +11160,49 @@ eslint@^7.1.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +eslint@^7.6.0: + version "7.12.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-7.12.0.tgz#7b6a85f87a9adc239e979bb721cde5ce0dc27da6" + integrity sha512-n5pEU27DRxCSlOhJ2rO57GDLcNsxO0LPpAbpFdh7xmcDmjmlGUfoyrsB3I7yYdQXO5N3gkSTiDrPSPNFiiirXA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@eslint/eslintrc" "^0.2.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.19" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + esm@^3.2.25: version "3.2.25" resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" @@ -11046,6 +11217,15 @@ espree@^7.1.0: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.2.0" +espree@^7.3.0: + version "7.3.0" + resolved "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" + integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.3.0" + esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -11065,6 +11245,13 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" @@ -11075,6 +11262,11 @@ estraverse@^5.1.0: resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estraverse@^5.2.0: + version "5.2.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" + integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" @@ -11085,6 +11277,11 @@ estree-walker@^1.0.1: resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.1.tgz#f8e030fb21cefa183b44b7ad516b747434e7a3e0" + integrity sha512-tF0hv+Yi2Ot1cwj9eYHtxC0jB9bmjacjQs6ZBTj82H8JwUywFuc+7E83NWfNMwHXZc11mjfFcVXPe9gEP4B8dg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -12705,7 +12902,7 @@ graphql-upload@^8.0.2: http-errors "^1.7.3" object-path "^0.11.4" -graphql@15.3.0, graphql@^15.3.0: +graphql@15.3.0, graphql@^15.0.0, graphql@^15.3.0: version "15.3.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== @@ -12881,7 +13078,7 @@ he@^1.1.0, he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-utils@^1.2.0: +headers-utils@^1.1.9, headers-utils@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz#5e10d1bc9d2bccf789547afca5b991a3167241e8" integrity sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A== @@ -13684,6 +13881,11 @@ is-callable@^1.1.4, is-callable@^1.1.5: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -13703,6 +13905,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" + integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -13871,6 +14080,11 @@ is-module@^1.0.0: resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + is-npm@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -14389,6 +14603,14 @@ jest-esm-transformer@^1.0.0: "@babel/core" "^7.4.4" "@babel/plugin-transform-modules-commonjs" "^7.4.4" +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^25.2.6: version "25.2.6" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" @@ -14683,6 +14905,11 @@ jose@^2.0.2: dependencies: "@panva/asn1.js" "^1.0.0" +joycon@^2.2.5: + version "2.2.5" + resolved "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615" + integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -15088,6 +15315,14 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.1.0" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -16516,6 +16751,22 @@ ms@2.1.2, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +msw@^0.19.5: + version "0.19.5" + resolved "https://registry.npmjs.org/msw/-/msw-0.19.5.tgz#7d2a1a852ccf1644d3db6735d69fff6777aac33f" + integrity sha512-J5eQ++gDVZoHPC8gVXtWcakLjgmPipvFj/sEnlRV/WViXuiq2CamSqO3Wbh6H8bAmj+k2vUWCfcVT1HjMdKB2Q== + dependencies: + "@open-draft/until" "^1.0.0" + "@types/cookie" "^0.3.3" + chalk "^4.0.0" + cookie "^0.4.1" + graphql "^15.0.0" + headers-utils "^1.1.9" + node-match-path "^0.4.2" + node-request-interceptor "^0.2.5" + statuses "^2.0.0" + yargs "^15.3.1" + msw@^0.20.5: version "0.20.5" resolved "https://registry.npmjs.org/msw/-/msw-0.20.5.tgz#b6141080c0d8b17c451d9ca36c28cc47b4ac487a" @@ -16829,7 +17080,7 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-match-path@^0.4.4: +node-match-path@^0.4.2, node-match-path@^0.4.4: version "0.4.4" resolved "https://registry.npmjs.org/node-match-path/-/node-match-path-0.4.4.tgz#516a10926093c0cc6f237d020685b593b19baebb" integrity sha512-pBq9gp7TG0r0VXuy/oeZmQsjBSnYQo7G886Ly/B3azRwZuEtHCY155dzmfoKWcDPGgyfIGD8WKVC7h3+6y7yTg== @@ -16888,6 +17139,14 @@ node-releases@^1.1.52, node-releases@^1.1.58: resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== +node-request-interceptor@^0.2.5: + version "0.2.6" + resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.2.6.tgz#541278d7033bb6a8befb5dd793f83428cf6446a2" + integrity sha512-aJW1tPSM7nzuZFRe+C/KSz22GJO3CVFMxHHmMGX8Z+tjP7TCIVbzeckLFVfJG68BdVgrdOOP7Ejc57ag820eyA== + dependencies: + debug "^4.1.1" + headers-utils "^1.2.0" + node-request-interceptor@^0.3.5: version "0.3.5" resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.3.5.tgz#4b26159617829c9a70643012c0fdc3ae4c78ae43" @@ -17150,6 +17409,11 @@ object-inspect@^1.7.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== +object-inspect@^1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + object-is@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" @@ -17182,6 +17446,16 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" + integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.0" + has-symbols "^1.0.1" + object-keys "^1.1.1" + object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" @@ -17202,6 +17476,15 @@ object.entries@^1.1.0, object.entries@^1.1.1: function-bind "^1.1.1" has "^1.0.3" +object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + has "^1.0.3" + "object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" @@ -18862,6 +19145,11 @@ promise-inflight@^1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise-polyfill@^8.1.3: + version "8.2.0" + resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" + integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== + promise-retry@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" @@ -20449,6 +20737,14 @@ resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.0 dependencies: path-parse "^1.0.6" +resolve@^1.18.1: + version "1.18.1" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" + integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== + dependencies: + is-core-module "^2.0.0" + path-parse "^1.0.6" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -20563,13 +20859,20 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rollup-plugin-dts@1.4.13: +rollup-plugin-dts@1.4.13, rollup-plugin-dts@^1.4.10: version "1.4.13" resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.13.tgz#4f086e84f4fdcc1f49160799ebc66f6b09db292b" integrity sha512-7mxoQ6PcmCkBE5ZhrjGDL4k42XLy8BkSqpiRi1MipwiGs+7lwi4mQkp2afX+OzzLjJp/TGM8llfe8uayIUhPEw== optionalDependencies: "@babel/code-frame" "^7.10.4" +rollup-plugin-dts@1.4.8: + version "1.4.8" + resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.8.tgz#3b0eadc285aede11c3f5ced72b56b5f7735df95c" + integrity sha512-2qHB4B3oaTyi1mDmqDzsRGKlev32v3EhMUAmc45Cn9eB22R7FsYDiigvT9XdM/oQzfd0qv5MkeZju8DP0nauiA== + optionalDependencies: + "@babel/code-frame" "^7.10.4" + rollup-plugin-esbuild@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" @@ -20577,11 +20880,25 @@ rollup-plugin-esbuild@^2.0.0: dependencies: "@rollup/pluginutils" "^3.1.0" +rollup-plugin-esbuild@^2.4.2: + version "2.5.2" + resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.5.2.tgz#fd7d4a88518898012a9d07e4c3ef8b4009c244d3" + integrity sha512-E4q3ac1AlMd0m0ZRYffdiorOt2eZcxfbdPaqBLs7JLnPE8krgIAihOD6cTUc54UJjoOMA9WcY63TR+JKWLzYNw== + dependencies: + "@rollup/pluginutils" "^4.0.0" + joycon "^2.2.5" + strip-json-comments "^3.1.1" + rollup-plugin-peer-deps-external@^2.2.2: version "2.2.3" resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.3.tgz#059a8aec1eefb48a475e9fcedc3b9e3deb521213" integrity sha512-W6IePXTExGXVDAlfZbNUUrx3GxUOZP248u5n4a4ID1XZMrbQ+uGeNiEfapvdzwx0qZi5DNH/hDLiPUP+pzFIxg== +rollup-plugin-peer-deps-external@^2.2.3: + version "2.2.4" + resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d" + integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g== + rollup-plugin-postcss@^3.1.1: version "3.1.8" resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-3.1.8.tgz#d1bcaf8eb0fcb0936e3684c22dd8628d13a82fd1" @@ -20620,6 +20937,13 @@ rollup-pluginutils@^2.8.2: dependencies: estree-walker "^0.6.1" +rollup@2.23.1: + version "2.23.1" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.1.tgz#d458d28386dc7660c2e8a4978bea6f9494046c20" + integrity sha512-Heyl885+lyN/giQwxA8AYT2GY3U+gOlTqVLrMQYno8Z1X9lAOpfXPiKiZCyPc25e9BLJM3Zlh957dpTlO4pa8A== + optionalDependencies: + fsevents "~2.1.2" + rollup@2.23.x: version "2.23.0" resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" @@ -20635,6 +20959,13 @@ rollup@^0.63.4: "@types/estree" "0.0.39" "@types/node" "*" +rollup@^2.23.1: + version "2.32.1" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz#625a92c54f5b4d28ada12d618641491d4dbb548c" + integrity sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw== + optionalDependencies: + fsevents "~2.1.2" + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -21756,6 +22087,14 @@ string.prototype.padstart@^3.0.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" +string.prototype.trimend@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz#6ddd9a8796bc714b489a3ae22246a208f37bfa46" + integrity sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + string.prototype.trimleft@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" @@ -21772,6 +22111,14 @@ string.prototype.trimright@^2.1.1: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimstart@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz#22d45da81015309cd0cdd79787e8919fc5c613e7" + integrity sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -21874,6 +22221,11 @@ strip-json-comments@^3.1.0: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -24194,6 +24546,11 @@ yaml@*, yaml@^1.10.0, yaml@^1.7.2, yaml@^1.9.2: resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== +yaml@^2.0.0-1: + version "2.0.0-1" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" + integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== + yargs-parser@18.x, yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" From 2ceddba979ea487052826e71147ca78a2c306907 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Tue, 27 Oct 2020 08:31:53 +0100 Subject: [PATCH 004/195] Cleanup --- .../src/ingestion/ConfigGenerator.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts index 6879cd8291..d4d586b4de 100644 --- a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts +++ b/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts @@ -14,22 +14,10 @@ * limitations under the License. */ -import { InputError } from '@backstage/backend-common'; -import { Entity, Location, LocationSpec } from '@backstage/catalog-model'; -import { v4 as uuidv4 } from 'uuid'; +import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; -import { EntitiesCatalog, LocationsCatalog } from '../catalog'; -import { EntityUpsertResponse } from '../catalog/types'; -import { durationText } from '../util/timing'; -import { AddLocationResult, ConfigGenerator, LocationReader } from './types'; +import { ConfigGenerator } from './types'; -/** - * Placeholder for operations that span several catalogs and/or stretches out - * in time. - * - * TODO(freben): Find a better home for these, possibly refactoring to use the - * database more directly. - */ export class ConfigGeneratorClient implements ConfigGenerator { logger: Logger; From dbadbe7982a5885199f86887ab7eadfab528d437 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 28 Oct 2020 23:45:43 +0100 Subject: [PATCH 005/195] Renames and new types --- packages/backend/src/plugins/catalog.ts | 4 +- packages/catalog-model/src/location/index.ts | 2 +- .../catalog-model/src/location/validation.ts | 6 +- ...ConfigGenerator.ts => LocationAnalyzer.ts} | 30 ++++---- .../catalog-backend/src/ingestion/types.ts | 63 +++++++++++++++- .../src/service/CatalogBuilder.ts | 10 +-- plugins/catalog-backend/src/service/router.ts | 19 +++-- yarn.lock | 75 ++++--------------- 8 files changed, 115 insertions(+), 94 deletions(-) rename plugins/catalog-backend/src/ingestion/{ConfigGenerator.ts => LocationAnalyzer.ts} (60%) diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index 8d6c823dce..1338fab440 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -28,7 +28,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer, } = await builder.build(); useHotCleanup( @@ -40,7 +40,7 @@ export default async function createPlugin(env: PluginEnvironment) { entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer, logger: env.logger, }); } diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index 1b7587c1f0..ce64b988a6 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -18,6 +18,6 @@ export type { Location, LocationSpec } from './types'; export { locationSchema, locationSpecSchema, - repoPathSchema, + analyzeLocationSchema, } from './validation'; export { LOCATION_ANNOTATION } from './annotation'; diff --git a/packages/catalog-model/src/location/validation.ts b/packages/catalog-model/src/location/validation.ts index 28662119b0..4d2e602862 100644 --- a/packages/catalog-model/src/location/validation.ts +++ b/packages/catalog-model/src/location/validation.ts @@ -35,9 +35,9 @@ export const locationSchema = yup .noUnknown() .required(); -export const repoPathSchema = yup - .object<{ repoPath: string }>({ - repoPath: yup.string().required(), +export const analyzeLocationSchema = yup + .object<{ location: LocationSpec }>({ + location: locationSpecSchema, }) .noUnknown() .required(); diff --git a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts similarity index 60% rename from plugins/catalog-backend/src/ingestion/ConfigGenerator.ts rename to plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index d4d586b4de..f6991b1c90 100644 --- a/plugins/catalog-backend/src/ingestion/ConfigGenerator.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -14,32 +14,36 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; -import { ConfigGenerator } from './types'; +import { + AnalyzeLocationRequest, + AnalyzeLocationResponse, + LocationAnalyzer, +} from './types'; -export class ConfigGeneratorClient implements ConfigGenerator { - logger: Logger; +export class LocationAnalyzerClient implements LocationAnalyzer { + private readonly logger: Logger; constructor(logger: Logger) { this.logger = logger; } - async generateConfig(repoPath: string): Promise { - const [ownerName, repoName] = [ - repoPath.split('/')[0], - repoPath.split('/')[1], - ]; - const config = { + async generateConfig( + request: AnalyzeLocationRequest, + ): Promise { + const [ownerName, repoName] = request.location.target.split('/').slice(-2); + const entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', metadata: { name: repoName, - description: '', - annotations: { 'github.com/project-slug': repoPath }, + annotations: { 'github.com/project-slug': `${ownerName}/${repoName}` }, }, spec: { type: 'service', owner: ownerName, lifecycle: 'experimental' }, }; - return [config]; + return { + existingEntityFiles: [], + generateEntities: [{ entity, fields: [] }], + }; } } diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 2d7165c140..19d472b3ab 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -20,6 +20,7 @@ import type { Location, LocationSpec, } from '@backstage/catalog-model'; +import { RecursivePartial } from './processors/ldap/util'; // // HigherOrderOperation @@ -70,6 +71,64 @@ export type ReadLocationError = { // ConfigGenerator // -export type ConfigGenerator = { - generateConfig(repoPath: string): Promise; +export type LocationAnalyzer = { + generateConfig( + location: AnalyzeLocationRequest, + ): Promise; +}; + +export type AnalyzeLocationRequest = { + location: LocationSpec; +}; + +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 +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. +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 +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: + | 'analysis_suggested_value' + | 'analysis_suggested_no_value' + | 'needs_user_input'; + + // 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-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 39417dfd6f..439b85591f 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -52,7 +52,7 @@ import { UrlReaderProcessor, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; -import { ConfigGeneratorClient } from '../ingestion/ConfigGenerator'; +import { LocationAnalyzerClient } from '../ingestion/LocationAnalyzer'; import { BuiltinKindsEntityProcessor } from '../ingestion/processors/BuiltinKindsEntityProcessor'; import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { @@ -60,7 +60,7 @@ import { textPlaceholderResolver, yamlPlaceholderResolver, } from '../ingestion/processors/PlaceholderProcessor'; -import { ConfigGenerator } from '../ingestion/types'; +import { LocationAnalyzer } from '../ingestion/types'; export type CatalogEnvironment = { logger: Logger; @@ -204,7 +204,7 @@ export class CatalogBuilder { entitiesCatalog: EntitiesCatalog; locationsCatalog: LocationsCatalog; higherOrderOperation: HigherOrderOperation; - configGenerator: ConfigGenerator; + locationAnalyzer: LocationAnalyzer; }> { const { config, database, logger } = this.env; @@ -232,13 +232,13 @@ export class CatalogBuilder { locationReader, logger, ); - const configGenerator = new ConfigGeneratorClient(logger); + const locationAnalyzer = new LocationAnalyzerClient(logger); return { entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer, }; } diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index b11572b180..8d5fb73c37 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -15,13 +15,16 @@ */ import { errorHandler } from '@backstage/backend-common'; -import { locationSpecSchema, repoPathSchema } from '@backstage/catalog-model'; +import { + locationSpecSchema, + analyzeLocationSchema, +} from '@backstage/catalog-model'; import type { Entity } from '@backstage/catalog-model'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import { EntitiesCatalog, LocationsCatalog } from '../catalog'; -import { ConfigGenerator, HigherOrderOperation } from '../ingestion/types'; +import { LocationAnalyzer, HigherOrderOperation } from '../ingestion/types'; import { translateQueryToEntityFilters, translateQueryToFieldMapper, @@ -32,7 +35,7 @@ export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; locationsCatalog?: LocationsCatalog; higherOrderOperation?: HigherOrderOperation; - configGenerator?: ConfigGenerator; + locationAnalyzer?: LocationAnalyzer; logger: Logger; } @@ -43,7 +46,7 @@ export async function createRouter( entitiesCatalog, locationsCatalog, higherOrderOperation, - configGenerator, + locationAnalyzer: locationAnalyzer, } = options; const router = Router(); @@ -135,10 +138,10 @@ export async function createRouter( }); } - if (configGenerator) { - router.post('/generate-config', async (req, res) => { - const input = await validateRequestBody(req, repoPathSchema); - const output = await configGenerator.generateConfig(input.repoPath); + if (locationAnalyzer) { + router.post('/analyze-location', async (req, res) => { + const input = await validateRequestBody(req, analyzeLocationSchema); + const output = await locationAnalyzer.generateConfig(input); res.status(201).send(output); }); } diff --git a/yarn.lock b/yarn.lock index 7f85f649c9..1c6125854e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3691,50 +3691,18 @@ react-router "^6.0.0-beta.0" react-use "^15.3.3" -"@roadiehq/backstage-plugin-github-pull-requests@^0.5.2": - version "0.5.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.5.2.tgz#455355fe5cdcc43c22b80a8ef3697378084680a1" - integrity sha512-GM+nWQZ+aehMclN70Pp35UQe3I46SQCjWiGZ6/0TfDzrueHln39L2UspUj1L/713xDDtDK8ZnTJdFn/qKx7j4A== +"@rollup/plugin-commonjs@^14.0.0": + version "14.0.0" + resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz#4285f9ec2db686a31129e5a2b415c94aa1f836f0" + integrity sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw== dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.25" - "@backstage/core" "^0.1.1-alpha.25" - "@backstage/plugin-catalog" "^0.1.1-alpha.25" - "@backstage/theme" "^0.1.1-alpha.25" - "@material-ui/core" "^4.11.0" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "^4.0.0-alpha.56" - "@octokit/rest" "^18.0.0" - "@octokit/types" "^5.0.1" - "@types/react-dom" "^16.9.8" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-router "6.0.0-beta.0" - react-use "^15.3.3" - -"@roadiehq/backstage-plugin-travis-ci@^0.2.5": - version "0.2.5" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.2.5.tgz#588cd8b4a4dd4bd426c0012f8d35eae4504b55f4" - integrity sha512-4aJizmvIeunpl8D/YDvzagNzikPU51DSZFz1tN0XCwRR+buTPknV141qsKP+yxUbs6nx3Xuh+T/vNkb9lk6FlA== - dependencies: - "@backstage/catalog-model" "^0.1.1-alpha.24" - "@backstage/core" "^0.1.1-alpha.24" - "@backstage/core-api" "^0.1.1-alpha.24" - "@backstage/plugin-catalog" "^0.1.1-alpha.24" - "@backstage/theme" "^0.1.1-alpha.24" - "@material-ui/core" "^4.9.1" - "@material-ui/icons" "^4.9.1" - "@material-ui/lab" "4.0.0-alpha.45" - date-fns "^2.15.0" - history "^5.0.0" - moment "^2.27.0" - react "^16.13.1" - react-dom "^16.13.1" - react-lazylog "^4.5.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" + "@rollup/pluginutils" "^3.0.8" + commondir "^1.0.1" + estree-walker "^1.0.1" + glob "^7.1.2" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" "@rollup/plugin-commonjs@^16.0.0": version "16.0.0" @@ -3749,19 +3717,6 @@ magic-string "^0.25.7" resolve "^1.17.0" -"@rollup/plugin-commonjs@^14.0.0": - version "14.0.0" - resolved "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-14.0.0.tgz#4285f9ec2db686a31129e5a2b415c94aa1f836f0" - integrity sha512-+PSmD9ePwTAeU106i9FRdc+Zb3XUWyW26mo5Atr2mk82hor8+nPwkztEjFo8/B1fJKfaQDg9aM2bzQkjhi7zOw== - dependencies: - "@rollup/pluginutils" "^3.0.8" - commondir "^1.0.1" - estree-walker "^1.0.1" - glob "^7.1.2" - is-reference "^1.1.2" - magic-string "^0.25.2" - resolve "^1.11.0" - "@rollup/plugin-json@^4.0.2": version "4.1.0" resolved "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" @@ -9881,7 +9836,7 @@ dateformat@^3.0.0: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.9.4: +dayjs@^1.9.1, dayjs@^1.9.4: version "1.9.4" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.9.4.tgz#fcde984e227f4296f04e7b05720adad2e1071f1b" integrity sha512-ABSF3alrldf7nM9sQ2U+Ln67NRwmzlLOqG7kK03kck0mw3wlSSEKv/XhKGGxUjQcS57QeiCyNdrFgtj9nWlrng== @@ -14227,7 +14182,7 @@ is-promise@^2.1, is-promise@^2.1.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-reference@^1.2.1: +is-reference@^1.1.2, is-reference@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== @@ -16125,7 +16080,7 @@ macos-release@^2.2.0: resolved "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== -magic-string@^0.25.7: +magic-string@^0.25.2, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -20782,7 +20737,7 @@ resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0 dependencies: path-parse "^1.0.6" -resolve@^1.18.1: +resolve@^1.11.0, resolve@^1.18.1: version "1.18.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== From babbd50ddb67d63be60bee495f5f41b00989b9b1 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 29 Oct 2020 09:56:28 +0100 Subject: [PATCH 006/195] Create import-component frontend plugin --- .../catalog-backend/src/ingestion/index.ts | 9 +- plugins/import-component/.eslintrc.js | 3 + plugins/import-component/README.md | 7 + plugins/import-component/dev/index.tsx | 22 +++ plugins/import-component/package.json | 56 ++++++ .../src/api/CatalogImportApi.ts | 56 ++++++ .../src/api/CatalogImportClient.ts | 135 ++++++++++++++ plugins/import-component/src/api/index.ts | 34 ++++ plugins/import-component/src/api/types.ts | 38 ++++ .../ImportComponentForm.tsx | 170 ++++++++++++++++++ .../components/ImportComponentForm/index.ts | 17 ++ .../ComponentConfigDisplay.tsx | 98 ++++++++++ .../ImportComponentPage.tsx | 95 ++++++++++ .../ImportComponentPage/ImportFinished.tsx | 73 ++++++++ .../ImportComponentPage/ImportStepper.tsx | 42 +++++ .../components/ImportComponentPage/index.ts | 17 ++ .../ImportComponentPage/useGithubRepos.ts | 74 ++++++++ .../src/components/Router.tsx | 26 +++ plugins/import-component/src/index.ts | 18 ++ plugins/import-component/src/plugin.test.ts | 23 +++ plugins/import-component/src/plugin.ts | 54 ++++++ plugins/import-component/src/setupTests.ts | 17 ++ plugins/import-component/src/util/types.ts | 22 +++ .../src/util/validate.test.ts | 33 ++++ plugins/import-component/src/util/validate.ts | 21 +++ plugins/import-component/tsconfig.json | 5 + 26 files changed, 1157 insertions(+), 8 deletions(-) create mode 100644 plugins/import-component/.eslintrc.js create mode 100644 plugins/import-component/README.md create mode 100644 plugins/import-component/dev/index.tsx create mode 100644 plugins/import-component/package.json create mode 100644 plugins/import-component/src/api/CatalogImportApi.ts create mode 100644 plugins/import-component/src/api/CatalogImportClient.ts create mode 100644 plugins/import-component/src/api/index.ts create mode 100644 plugins/import-component/src/api/types.ts create mode 100644 plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx create mode 100644 plugins/import-component/src/components/ImportComponentForm/index.ts create mode 100644 plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx create mode 100644 plugins/import-component/src/components/ImportComponentPage/index.ts create mode 100644 plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts create mode 100644 plugins/import-component/src/components/Router.tsx create mode 100644 plugins/import-component/src/index.ts create mode 100644 plugins/import-component/src/plugin.test.ts create mode 100644 plugins/import-component/src/plugin.ts create mode 100644 plugins/import-component/src/setupTests.ts create mode 100644 plugins/import-component/src/util/types.ts create mode 100644 plugins/import-component/src/util/validate.test.ts create mode 100644 plugins/import-component/src/util/validate.ts create mode 100644 plugins/import-component/tsconfig.json diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index 4ebb2edc79..fad4ae34f7 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -16,12 +16,5 @@ export { HigherOrderOperations } from './HigherOrderOperations'; export { LocationReaders } from './LocationReaders'; -export type { - AddLocationResult, - HigherOrderOperation, - LocationReader, - ReadLocationEntity, - ReadLocationError, - ReadLocationResult, -} from './types'; +export * from './types'; export * from './processors'; diff --git a/plugins/import-component/.eslintrc.js b/plugins/import-component/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/import-component/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/import-component/README.md b/plugins/import-component/README.md new file mode 100644 index 0000000000..682212d98d --- /dev/null +++ b/plugins/import-component/README.md @@ -0,0 +1,7 @@ +# Register component plugin + +Welcome to the import-component plugin! + +This plugin allows you to create a component-config YAML file for your repository. + +When installed it is accessible on [localhost:3000/import-component](localhost:3000/import-component). diff --git a/plugins/import-component/dev/index.tsx b/plugins/import-component/dev/index.tsx new file mode 100644 index 0000000000..d97643057b --- /dev/null +++ b/plugins/import-component/dev/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { createDevApp } from '@backstage/dev-utils'; +import { plugin } from '../src/plugin'; + +createDevApp() + .registerPlugin(plugin) + .render(); diff --git a/plugins/import-component/package.json b/plugins/import-component/package.json new file mode 100644 index 0000000000..73a9aa007f --- /dev/null +++ b/plugins/import-component/package.json @@ -0,0 +1,56 @@ +{ + "name": "@backstage/plugin-import-component", + "version": "0.1.1-alpha.26", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^0.1.1-alpha.26", + "@backstage/core": "^0.1.1-alpha.26", + "@backstage/plugin-catalog": "^0.1.1-alpha.26", + "@backstage/plugin-catalog-backend": "^0.1.1-alpha.26", + "@backstage/theme": "^0.1.1-alpha.26", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "@octokit/rest": "^18.0.6", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-hook-form": "^6.6.0", + "react-router": "^5.2.0", + "react-router-dom": "6.0.0-beta.0", + "react-use": "^15.3.3", + "yaml": "^1.10.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.26", + "@backstage/dev-utils": "^0.1.1-alpha.26", + "@backstage/test-utils": "^0.1.1-alpha.26", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^10.4.1", + "@testing-library/user-event": "^12.0.7", + "@types/jest": "^26.0.7", + "@types/node": "^12.0.0", + "cross-fetch": "^3.0.6", + "msw": "^0.21.2" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/import-component/src/api/CatalogImportApi.ts b/plugins/import-component/src/api/CatalogImportApi.ts new file mode 100644 index 0000000000..58d1f87f00 --- /dev/null +++ b/plugins/import-component/src/api/CatalogImportApi.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 Roadie AB + * + * 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 { createApiRef } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { RecursivePartial } from '../util/types'; + +export const catalogImportApiRef = createApiRef({ + id: 'plugin.catalogimport.service', + description: 'Used by the catalog import plugin to make requests', +}); + +export interface CatalogImportApi { + submitPRToRepo(options: { + token: string; + owner: string; + repo: string; + fileContent: string; + }): Promise<{ errorMessage: string | null; link: string }>; + createRepositoryLocation(options: { + token: string; + owner: string; + repo: string; + }): Promise<{ errorMessage: string | null }>; + generateEntityDefinitions(options: { + repo: string; + }): Promise[]>; +} diff --git a/plugins/import-component/src/api/CatalogImportClient.ts b/plugins/import-component/src/api/CatalogImportClient.ts new file mode 100644 index 0000000000..d67febe2c8 --- /dev/null +++ b/plugins/import-component/src/api/CatalogImportClient.ts @@ -0,0 +1,135 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 Roadie AB + * + * 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 { Octokit } from '@octokit/rest'; +import { Entity } from '@backstage/catalog-model'; +import { DiscoveryApi } from '@backstage/core'; +import { CatalogImportApi } from './CatalogImportApi'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; +import { RecursivePartial } from '../util/types'; + +export const API_BASE_URL = '/api/catalog/locations'; + +export class CatalogImportClient implements CatalogImportApi { + private readonly discoveryApi: DiscoveryApi; + + constructor(options: { discoveryApi: DiscoveryApi }) { + this.discoveryApi = options.discoveryApi; + } + + async generateEntityDefinitions({ + repo, + }: { + repo: string; + }): Promise[]> { + const response = await fetch( + `${await this.discoveryApi.getBaseUrl('catalog')}/analyze-location`, + { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + location: { type: 'github', target: repo }, + }), + }, + ); + const payload = (await response.json()) as AnalyzeLocationResponse; + return payload.generateEntities.map(x => x.entity); + } + + async createRepositoryLocation({ + owner, + repo, + }: { + token: string; + owner: string; + repo: string; + }): Promise<{ errorMessage: string | null }> { + await fetch(`${await this.discoveryApi.getBaseUrl('catalog')}/locations`, { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + type: 'github', + target: `https://github.com/${owner}/${repo}/blob/master/catalog-info.yaml`, + presence: 'optional', + }), + }); + return { errorMessage: null }; + } + + async submitPRToRepo({ + token, + owner, + repo, + fileContent, + }: { + token: string; + owner: string; + repo: string; + fileContent: string; + }): Promise<{ errorMessage: string | null; link: string }> { + const octo = new Octokit({ + auth: token, + }); + + const parentRef = await octo.git.getRef({ + owner, + repo, + ref: 'heads/master', + }); + await octo.git.createRef({ + owner, + repo, + ref: 'refs/heads/backstage-integration', + sha: parentRef.data.object.sha, + }); + await octo.repos.createOrUpdateFileContents({ + owner, + repo, + path: 'backstage.yaml', + message: 'Add backstage.yaml config file', + content: btoa(fileContent), + branch: 'backstage-integration', + }); + const pullRequestRespone = await octo.pulls.create({ + owner, + repo, + title: 'Add backstage.yaml config file', + head: 'backstage-integration', + base: 'master', + }); + + return { errorMessage: null, link: pullRequestRespone.data.html_url }; + } +} diff --git a/plugins/import-component/src/api/index.ts b/plugins/import-component/src/api/index.ts new file mode 100644 index 0000000000..9ebc110843 --- /dev/null +++ b/plugins/import-component/src/api/index.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 Roadie AB + * + * 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 './CatalogImportApi'; +export * from './CatalogImportClient'; +export * from './types'; diff --git a/plugins/import-component/src/api/types.ts b/plugins/import-component/src/api/types.ts new file mode 100644 index 0000000000..c18d0cf547 --- /dev/null +++ b/plugins/import-component/src/api/types.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 Roadie AB + * + * 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 GithubRepoDto = { + full_name: string; + private: boolean; + description: string; + html_url: string; + language: string; +}; diff --git a/plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx b/plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx new file mode 100644 index 0000000000..c28e5889ac --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentForm/ImportComponentForm.tsx @@ -0,0 +1,170 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import { errorApiRef, useApi } from '@backstage/core'; +import { BackstageTheme } from '@backstage/theme'; +import { + Button, + FormControl, + FormHelperText, + InputLabel, + MenuItem, + Select, + TextField, +} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { useMountedState } from 'react-use'; +import { RecursivePartial } from '../../util/types'; +import { ComponentIdValidators } from '../../util/validate'; +import { useGithubRepos } from '../ImportComponentPage/useGithubRepos'; + +const useStyles = makeStyles(theme => ({ + form: { + alignItems: 'flex-start', + display: 'flex', + flexFlow: 'column nowrap', + }, + submit: { + marginTop: theme.spacing(1), + }, + select: { + minWidth: 120, + }, +})); + +type Props = { + nextStep: () => void; + saveConfig: (configFile: { + repo: string; + config: RecursivePartial[]; + }) => void; +}; + +export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { + const { control, register, handleSubmit, errors, formState } = useForm({ + mode: 'onChange', + }); + const classes = useStyles(); + const hasErrors = !!errors.componentLocation; + const dirty = formState?.isDirty; + + const isMounted = useMountedState(); + const errorApi = useApi(errorApiRef); + const { generateEntityDefinitions } = useGithubRepos(); + + const onSubmit = async (formData: Record) => { + const { componentLocation: target } = formData; + try { + // const typeMapping = [ + // { url: /^https:\/\/gitlab\.com\/.*/, type: 'gitlab/api' }, + // { url: /^https:\/\/bitbucket\.org\/.*/, type: 'bitbucket/api' }, + // { url: /^https:\/\/dev\.azure\.com\/.*/, type: 'azure/api' }, + // { url: /.*/, type: 'github' }, + // ]; + + // const type = + // scmType === 'AUTO' + // ? typeMapping.filter(item => item.url.test(target))[0].type + // : scmType; + + if (!isMounted()) return; + + const repo = target + .split('/') + .slice(-2) + .join('/'); + + const config = await generateEntityDefinitions(repo); + saveConfig({ + repo, + config, + }); + nextStep(); + } catch (e) { + errorApi.post(e); + } + }; + + return ( +
+ + + + {errors.componentLocation && ( + + {errors.componentLocation.message} + + )} + + + + Host type + ( + + )} + /> + + +
+ ); +}; diff --git a/plugins/import-component/src/components/ImportComponentForm/index.ts b/plugins/import-component/src/components/ImportComponentForm/index.ts new file mode 100644 index 0000000000..ed118f5412 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentForm/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 './ImportComponentForm'; diff --git a/plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx b/plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx new file mode 100644 index 0000000000..22d3dac322 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ComponentConfigDisplay.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 RoadieHQ + * + * 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, { useCallback, useState } from 'react'; +import { Button, CircularProgress, Grid, Tooltip } from '@material-ui/core'; +import Alert from '@material-ui/lab/Alert'; +import { useGithubRepos } from './useGithubRepos'; +import { Entity } from '@backstage/catalog-model'; +import { RecursivePartial } from '../../util/types'; + +type Props = { + nextStep: () => void; + configFile: { repo: string; config: RecursivePartial[] }; + savePRLink: (PRLink: string) => void; +}; + +const ComponentConfigDisplay: React.FC = ({ + nextStep, + configFile, + savePRLink, +}) => { + const [submitting, setSubmitting] = useState(false); + const { submitPRToRepo } = useGithubRepos(); + const onNext = useCallback(async () => { + setSubmitting(true); + const result = await submitPRToRepo(configFile); + savePRLink(result.link); + setSubmitting(false); + nextStep(); + }, [submitPRToRepo, configFile, nextStep, savePRLink]); + + return ( + + + + config file has been generated. You can optionally edit it before + submitting Pull Request + + + + + + + + + + + {submitting ? ( + + + + ) : null} + + + + + + ); +}; + +export default ComponentConfigDisplay; diff --git a/plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx b/plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx new file mode 100644 index 0000000000..719d5e93f4 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ImportComponentPage.tsx @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { useState } from 'react'; +import { Grid } from '@material-ui/core'; +import { + InfoCard, + Page, + Content, + Header, + SupportButton, + ContentHeader, +} from '@backstage/core'; +import { RegisterComponentForm } from '../ImportComponentForm'; +import { Entity } from '@backstage/catalog-model'; +import ImportStepper from './ImportStepper'; +import ComponentConfigDisplay from './ComponentConfigDisplay'; +import { ImportFinished } from './ImportFinished'; +import { RecursivePartial } from '../../util/types'; + +export const ImportComponentPage = () => { + const [activeStep, setActiveStep] = useState(0); + const [configFile, setConfigFile] = useState<{ + repo: string; + config: RecursivePartial[]; + }>({ repo: '', config: [] }); + const [PRLink, setPRLink] = useState(''); + const nextStep = (options?: { reset: boolean }) => { + setActiveStep(step => (options?.reset ? 0 : step + 1)); + }; + + return ( + +
+ + + + Generate a component definition file and automatically submit it as + a pull request. TODO: Add more information about what this is. + + + + + + + ), + }, + { + step: 'Review generated component config files', + content: ( + + ), + }, + { + step: 'Finish', + content: ( + + ), + }, + ]} + activeStep={activeStep} + nextStep={nextStep} + /> + + + + + + ); +}; diff --git a/plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx b/plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx new file mode 100644 index 0000000000..6abe551f96 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ImportFinished.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 Button from '@material-ui/core/Button'; +import { + Grid, + Link, + Typography, + createStyles, + makeStyles, + Theme, +} from '@material-ui/core'; +import { Alert } from '@material-ui/lab'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + width: '100%', + }, + heading: { + fontSize: theme.typography.pxToRem(15), + fontWeight: theme.typography.fontWeightRegular, + }, + linkList: { + display: 'flex', + flexDirection: 'column', + }, + }), +); +type Props = { + nextStep: (options?: { reset: boolean }) => void; + PRLink: string; +}; + +export const ImportFinished: React.FC = ({ nextStep, PRLink }) => { + const classes = useStyles(); + return ( + + + + Pull requests have been successfully opened. You can start again to + import more repositories + + + + Pull request link: + {PRLink} + + + + + + ); +}; diff --git a/plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx b/plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx new file mode 100644 index 0000000000..03752fa7d4 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/ImportStepper.tsx @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 Stepper from '@material-ui/core/Stepper'; +import Step from '@material-ui/core/Step'; +import StepLabel from '@material-ui/core/StepLabel'; +import { StepContent } from '@material-ui/core'; + +type Props = { + steps: { step: string; content: React.ReactNode }[]; + activeStep: number; + nextStep: (options?: { reset: boolean }) => void; +}; + +export default function ImportStepper({ steps, activeStep }: Props) { + return ( + + {steps.map(({ step }) => { + const stepProps: { completed?: boolean } = {}; + return ( + + {step} + {steps[activeStep].content} + + ); + })} + + ); +} diff --git a/plugins/import-component/src/components/ImportComponentPage/index.ts b/plugins/import-component/src/components/ImportComponentPage/index.ts new file mode 100644 index 0000000000..dee2816011 --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 './ImportComponentPage'; diff --git a/plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts b/plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts new file mode 100644 index 0000000000..f28d5a444e --- /dev/null +++ b/plugins/import-component/src/components/ImportComponentPage/useGithubRepos.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 Roadie + * + * 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 * as YAML from 'yaml'; +import { useApi, githubAuthApiRef } from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { catalogImportApiRef } from '../../api/CatalogImportApi'; +import { RecursivePartial } from '../../util/types'; + +export function useGithubRepos() { + const api = useApi(catalogImportApiRef); + const auth = useApi(githubAuthApiRef); + + const submitPRToRepo = async (selectedRepo: { + repo: string; + config: RecursivePartial[]; + }) => { + const token = await auth.getAccessToken(['repo']); + + const [ownerName, repoName] = [ + selectedRepo.repo.split('/')[0], + selectedRepo.repo.split('/')[1], + ]; + const submitPRResponse = await api.submitPRToRepo({ + token, + owner: ownerName, + repo: repoName, + fileContent: selectedRepo.config + .map(entity => `---\n${YAML.stringify(entity)}`) + .join('\n'), + }); + + await api.createRepositoryLocation({ + token, + owner: selectedRepo.repo.split('/')[0], + repo: selectedRepo.repo.split('/')[1], + }); + + return submitPRResponse; + }; + + return { + submitPRToRepo, + generateEntityDefinitions: (repo: string) => + api.generateEntityDefinitions({ repo }), + }; +} diff --git a/plugins/import-component/src/components/Router.tsx b/plugins/import-component/src/components/Router.tsx new file mode 100644 index 0000000000..c877d3a208 --- /dev/null +++ b/plugins/import-component/src/components/Router.tsx @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { Route, Routes } from 'react-router'; +import { ImportComponentPage } from './ImportComponentPage'; + +// As we don't know which path the catalog's router mounted on +// We need to inject this from the app +export const Router = () => ( + + } /> + +); diff --git a/plugins/import-component/src/index.ts b/plugins/import-component/src/index.ts new file mode 100644 index 0000000000..ff7857cacd --- /dev/null +++ b/plugins/import-component/src/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { plugin } from './plugin'; +export { Router } from './components/Router'; diff --git a/plugins/import-component/src/plugin.test.ts b/plugins/import-component/src/plugin.test.ts new file mode 100644 index 0000000000..dd10754a87 --- /dev/null +++ b/plugins/import-component/src/plugin.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { plugin } from './plugin'; + +describe('import-component', () => { + it('should export plugin', () => { + expect(plugin).toBeDefined(); + }); +}); diff --git a/plugins/import-component/src/plugin.ts b/plugins/import-component/src/plugin.ts new file mode 100644 index 0000000000..a5db8337da --- /dev/null +++ b/plugins/import-component/src/plugin.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ +/* + * Copyright 2020 Roadie AB + * + * 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 { + createApiFactory, + createPlugin, + createRouteRef, + discoveryApiRef, +} from '@backstage/core'; +import { catalogImportApiRef } from './api/CatalogImportApi'; +import { CatalogImportClient } from './api/CatalogImportClient'; + +export const rootRouteRef = createRouteRef({ + path: '', + title: 'import-component', +}); + +export const plugin = createPlugin({ + id: 'import-component', + apis: [ + createApiFactory({ + api: catalogImportApiRef, + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new CatalogImportClient({ discoveryApi }), + }), + ], +}); diff --git a/plugins/import-component/src/setupTests.ts b/plugins/import-component/src/setupTests.ts new file mode 100644 index 0000000000..825bcd4115 --- /dev/null +++ b/plugins/import-component/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 '@testing-library/jest-dom'; diff --git a/plugins/import-component/src/util/types.ts b/plugins/import-component/src/util/types.ts new file mode 100644 index 0000000000..77b96f5036 --- /dev/null +++ b/plugins/import-component/src/util/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 RecursivePartial = { + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; +}; diff --git a/plugins/import-component/src/util/validate.test.ts b/plugins/import-component/src/util/validate.test.ts new file mode 100644 index 0000000000..139c7fb0f8 --- /dev/null +++ b/plugins/import-component/src/util/validate.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { ComponentIdValidators } from './validate'; + +describe('ComponentIdValidators', () => { + describe('httpsValidator validator', () => { + const errorMessage = 'Must start with https://.'; + test.each([ + [true, 'https://example.com'], + [errorMessage, 'http://example.com'], + [errorMessage, 'example.com'], + [errorMessage, 'www.example.com'], + [errorMessage, ''], + [errorMessage, undefined], + ])('should return %p for %s', (expected: string | boolean, arg: any) => { + expect(ComponentIdValidators.httpsValidator(arg)).toBe(expected); + }); + }); +}); diff --git a/plugins/import-component/src/util/validate.ts b/plugins/import-component/src/util/validate.ts new file mode 100644 index 0000000000..056131aaae --- /dev/null +++ b/plugins/import-component/src/util/validate.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 const ComponentIdValidators = { + httpsValidator: (value: any) => + (typeof value === 'string' && value.match(/^https:\/\//) !== null) || + 'Must start with https://.', +}; diff --git a/plugins/import-component/tsconfig.json b/plugins/import-component/tsconfig.json new file mode 100644 index 0000000000..b663b01fa2 --- /dev/null +++ b/plugins/import-component/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src", "dev"], + "compilerOptions": {} +} From 29a3a074c6394b22913bb6bd91b70be9efbe9dda Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 29 Oct 2020 10:00:16 +0100 Subject: [PATCH 007/195] Add import component button to the create page --- packages/app/package.json | 1 + packages/app/src/App.tsx | 4 +-- packages/app/src/plugins.ts | 2 +- .../src/ingestion/LocationAnalyzer.ts | 1 + .../catalog-backend/src/ingestion/types.ts | 4 +-- .../src/components/Router.tsx | 2 +- .../ScaffolderPage/ScaffolderPage.tsx | 9 +++++++ yarn.lock | 27 ++++++++++++++++++- 8 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 656de82ade..5a344a98b8 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -22,6 +22,7 @@ "@backstage/plugin-lighthouse": "^0.1.1-alpha.26", "@backstage/plugin-newrelic": "^0.1.1-alpha.26", "@backstage/plugin-register-component": "^0.1.1-alpha.26", + "@backstage/plugin-import-component": "^0.1.1-alpha.26", "@backstage/plugin-rollbar": "^0.1.1-alpha.26", "@backstage/plugin-scaffolder": "^0.1.1-alpha.26", "@backstage/plugin-sentry": "^0.1.1-alpha.26", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 8b10366c6e..cb85d9afc2 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,7 +27,6 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; -import { Router as CatalogImportRouter } from '@roadiehq/backstage-plugin-catalog-import'; import { Router as CatalogRouter } from '@backstage/plugin-catalog'; import { Router as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql'; @@ -35,6 +34,7 @@ import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar'; import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse'; import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component'; import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; +import { Router as ImportComponentRouter } from '@backstage/plugin-import-component'; import { Route, Routes, Navigate } from 'react-router'; import { EntityPage } from './components/catalog/EntityPage'; @@ -83,8 +83,8 @@ const AppRoutes = () => ( path="/register-component" element={} /> + } /> } /> - } /> {...deprecatedAppRoutes} ); diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 64c49eceba..30715b617a 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -37,5 +37,5 @@ export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; -export { plugin as CatalogImport } from '@roadiehq/backstage-plugin-catalog-import'; +export { plugin as CatalogImport } from '@backstage/plugin-import-component'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index f6991b1c90..435ed4f872 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -41,6 +41,7 @@ export class LocationAnalyzerClient implements LocationAnalyzer { spec: { type: 'service', owner: ownerName, lifecycle: 'experimental' }, }; + this.logger.silly(`entity created for ${request.location.target}`); return { existingEntityFiles: [], generateEntities: [{ entity, fields: [] }], diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 19d472b3ab..4f229cbc6c 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 type { +import { Entity, EntityRelationSpec, Location, @@ -68,7 +68,7 @@ export type ReadLocationError = { }; // -// ConfigGenerator +// LocationAnalyzer // export type LocationAnalyzer = { diff --git a/plugins/import-component/src/components/Router.tsx b/plugins/import-component/src/components/Router.tsx index c877d3a208..9b9c445bb8 100644 --- a/plugins/import-component/src/components/Router.tsx +++ b/plugins/import-component/src/components/Router.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { Route, Routes } from 'react-router'; +import { Route, Routes } from 'react-router-dom'; import { ImportComponentPage } from './ImportComponentPage'; // As we don't know which path the catalog's router mounted on diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index d0cb25f254..306db4db84 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -77,6 +77,15 @@ export const ScaffolderPage = () => { /> + - - - + {submitting ? ( diff --git a/plugins/catalog-import/src/components/ImportComponentForm.tsx b/plugins/catalog-import/src/components/ImportComponentForm.tsx index da078e10f0..9a4fd38a06 100644 --- a/plugins/catalog-import/src/components/ImportComponentForm.tsx +++ b/plugins/catalog-import/src/components/ImportComponentForm.tsx @@ -26,9 +26,11 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { useForm } from 'react-hook-form'; import { useMountedState } from 'react-use'; +import parseGitUri from 'git-url-parse'; import { ComponentIdValidators } from '../util/validate'; import { useGithubRepos } from '../util/useGithubRepos'; import { ConfigSpec } from './ImportComponentPage'; +import { catalogApiRef } from '@backstage/plugin-catalog'; const useStyles = makeStyles(theme => ({ form: { @@ -53,6 +55,7 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { const classes = useStyles(); const hasErrors = !!errors.componentLocation; const dirty = formState?.isDirty; + const catalogApi = useApi(catalogApiRef); const isMounted = useMountedState(); const errorApi = useApi(errorApiRef); @@ -62,12 +65,22 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { const { componentLocation: target } = formData; try { if (!isMounted()) return; + const type = !parseGitUri(target).filepathtype ? 'repo' : 'file'; - const config = await generateEntityDefinitions(target); - saveConfig({ - repo: target, - config, - }); + if (type === 'repo') { + saveConfig({ + type, + location: target, + config: await generateEntityDefinitions(target), + }); + } else { + const data = await catalogApi.addLocation({ target }); + saveConfig({ + type, + location: data.location.target, + config: data.entities, + }); + } nextStep(); } catch (e) { errorApi.post(e); @@ -111,7 +124,7 @@ export const RegisterComponentForm = ({ nextStep, saveConfig }: Props) => { disabled={!dirty || hasErrors} className={classes.submit} > - Submit + Next ); diff --git a/plugins/catalog-import/src/components/ImportComponentPage.tsx b/plugins/catalog-import/src/components/ImportComponentPage.tsx index 8371eda927..c2eb935e54 100644 --- a/plugins/catalog-import/src/components/ImportComponentPage.tsx +++ b/plugins/catalog-import/src/components/ImportComponentPage.tsx @@ -23,6 +23,7 @@ import { Header, SupportButton, ContentHeader, + RouteRef, } from '@backstage/core'; import { RegisterComponentForm } from './ImportComponentForm'; import ImportStepper from './ImportStepper'; @@ -31,29 +32,35 @@ import { ImportFinished } from './ImportFinished'; import { PartialEntity } from '../util/types'; export type ConfigSpec = { - repo: string; + type: 'repo' | 'file'; + location: string; config: PartialEntity[]; }; -export const ImportComponentPage = () => { +export const ImportComponentPage = ({ + catalogRouteRef, +}: { + catalogRouteRef: RouteRef; +}) => { const [activeStep, setActiveStep] = useState(0); const [configFile, setConfigFile] = useState({ - repo: '', + type: 'repo', + location: '', config: [], }); - const [PRLink, setPRLink] = useState(''); + const [endLink, setEndLink] = useState(''); const nextStep = (options?: { reset: boolean }) => { setActiveStep(step => (options?.reset ? 0 : step + 1)); }; return ( -
+
- + - Generate a component definition file and automatically submit it as - a pull request. TODO: Add more information about what this is. + Start tracking your component in Backstage. TODO: Add more + information about what this is. @@ -62,7 +69,7 @@ export const ImportComponentPage = () => { { ), }, { - step: 'Review generated component config files', + step: 'Review', content: ( ), }, { step: 'Finish', content: ( - + ), }, ]} diff --git a/plugins/catalog-import/src/components/ImportFinished.tsx b/plugins/catalog-import/src/components/ImportFinished.tsx index e7680dbf90..d4ff66ddab 100644 --- a/plugins/catalog-import/src/components/ImportFinished.tsx +++ b/plugins/catalog-import/src/components/ImportFinished.tsx @@ -13,52 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import Button from '@material-ui/core/Button'; -import { - Grid, - Link, - Typography, - createStyles, - makeStyles, - Theme, -} from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; -const useStyles = makeStyles((theme: Theme) => - createStyles({ - heading: { - fontSize: theme.typography.pxToRem(15), - fontWeight: theme.typography.fontWeightRegular, - }, - }), -); +import React from 'react'; +import { Alert } from '@material-ui/lab'; +import { Link as RouterLink } from 'react-router-dom'; +import { Button, Grid, Link } from '@material-ui/core'; + type Props = { + type: 'repo' | 'file'; nextStep: (options?: { reset: boolean }) => void; PRLink: string; }; -export const ImportFinished = ({ nextStep, PRLink }: Props) => { - const classes = useStyles(); +export const ImportFinished = ({ nextStep, PRLink, type }: Props) => { return ( - Pull requests have been successfully opened. You can start again to - import more repositories + {type === 'repo' + ? 'Pull requests have been successfully opened. You can start again to import more repositories' + : 'Entity added to catalog successfully'} - Pull request link: - {PRLink} - - + {type === 'repo' ? ( + + 'View pull request on GitHub' + + ) : null} diff --git a/plugins/catalog-import/src/components/Router.tsx b/plugins/catalog-import/src/components/Router.tsx index bc3c776d0b..5175f8f077 100644 --- a/plugins/catalog-import/src/components/Router.tsx +++ b/plugins/catalog-import/src/components/Router.tsx @@ -14,12 +14,15 @@ * limitations under the License. */ +import { RouteRef } from '@backstage/core'; import React from 'react'; import { Route, Routes } from 'react-router-dom'; import { ImportComponentPage } from './ImportComponentPage'; -export const Router = () => ( +export const Router = ({ catalogRouteRef }: { catalogRouteRef: RouteRef }) => ( - } /> + } + /> ); diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index 3ec4f56c68..84c594c913 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -26,7 +26,7 @@ export function useGithubRepos() { const submitPrToRepo = async (selectedRepo: ConfigSpec) => { const token = await auth.getAccessToken(['repo']); - const [ownerName, repoName] = selectedRepo.repo.split('/').slice(-2); + const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2); const submitPRResponse = await api .submitPrToRepo({ oAuthToken: token, @@ -55,5 +55,7 @@ export function useGithubRepos() { submitPrToRepo, generateEntityDefinitions: (repo: string) => api.generateEntityDefinitions({ repo }), + addLocation: (location: string) => + api.createRepositoryLocation({ location }), }; } diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index cc57db2161..8ab8c10ddb 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -82,15 +82,6 @@ export const ScaffolderPage = () => { color="primary" component={RouterLink} to="/catalog-import" - style={{ marginRight: '8px' }} - > - Import Repository - - From 7008e042f93079ef84a6e09799a7bf48a1a9a7f4 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 12 Nov 2020 10:13:18 +0100 Subject: [PATCH 014/195] Fix link to PR --- plugins/catalog-import/src/components/ImportFinished.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-import/src/components/ImportFinished.tsx b/plugins/catalog-import/src/components/ImportFinished.tsx index d4ff66ddab..6ea5c9ab82 100644 --- a/plugins/catalog-import/src/components/ImportFinished.tsx +++ b/plugins/catalog-import/src/components/ImportFinished.tsx @@ -37,12 +37,8 @@ export const ImportFinished = ({ nextStep, PRLink, type }: Props) => { {type === 'repo' ? ( - - 'View pull request on GitHub' + + View pull request on GitHub ) : null} From ecd89ca8a8f97101f729f5ddaacceee49cdd378c Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 12 Nov 2020 22:59:40 +0100 Subject: [PATCH 016/195] Fix leftover codefrom otherwork --- packages/app/src/plugins.ts | 1 - yarn.lock | 355 ++---------------------------------- 2 files changed, 16 insertions(+), 340 deletions(-) diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 81d2c2cdec..90c0287b08 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -38,5 +38,4 @@ export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; export { plugin as CatalogImport } from '@backstage/plugin-catalog-import'; -export { plugin as BulkCatalogImport } from '@roadiehq/backstage-plugin-bulk-catalog-import'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; diff --git a/yarn.lock b/yarn.lock index f0c7c3df0f..695157637c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3228,7 +3228,7 @@ resolved "https://registry.npmjs.org/@material-icons/font/-/font-1.0.3.tgz#f722e5a69a03f20ef47d015cb69420bebeeaabe5" integrity sha512-aIRd0Z9b/HJ/O24KOaP7dNsXypMnXhpWrpLVYvQB/JesVqXYSbioYND200sR+C14a0LSCp+qWnWCnSXmN1hWGw== -"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1", "@material-ui/core@^4.9.10": +"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1": version "4.11.0" resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a" integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g== @@ -3264,7 +3264,7 @@ prop-types "^15.7.2" react-is "^16.8.0" -"@material-ui/lab@^4.0.0-alpha.49", "@material-ui/lab@^4.0.0-alpha.56": +"@material-ui/lab@^4.0.0-alpha.56": version "4.0.0-alpha.56" resolved "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.56.tgz#ff63080949b55b40625e056bbda05e130d216d34" integrity sha512-xPlkK+z/6y/24ka4gVJgwPfoCF4RCh8dXb1BNE7MtF9bXEBLN/lBxNTK8VAa0qm3V2oinA6xtUIdcRh0aeRtVw== @@ -3582,7 +3582,7 @@ dependencies: "@types/node" ">= 8" -"@open-draft/until@^1.0.0", "@open-draft/until@^1.0.3": +"@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== @@ -3663,11 +3663,6 @@ prop-types "^15.6.1" react-lifecycles-compat "^3.0.4" -"@rehooks/local-storage@^2.4.0": - version "2.4.0" - resolved "https://registry.npmjs.org/@rehooks/local-storage/-/local-storage-2.4.0.tgz#fb884b2b657cad5f77aa6ab60bb3532f0e0725d2" - integrity sha512-LoXDbEHsuIckVgBsFAv8SuU/M7memjyfWut9Zf36TQXqqCHBRFv8bweg9PymQCa1aWIMjNrZQflFdo55FDlXYg== - "@rjsf/core@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.4.0.tgz#c50bcff0d8178948ce08123177399d8816d51929" @@ -3776,19 +3771,6 @@ dependencies: "@rollup/pluginutils" "^3.0.8" -"@rollup/plugin-node-resolve@^8.4.0": - version "8.4.0" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.4.0.tgz#261d79a680e9dc3d86761c14462f24126ba83575" - integrity sha512-LFqKdRLn0ShtQyf6SBYO69bGE1upV6wUhBX0vFOUnLAyzx5cwp8svA0eHUnu8+YU57XOkrMtfG63QOpQx25pHQ== - dependencies: - "@rollup/pluginutils" "^3.1.0" - "@types/resolve" "1.17.1" - builtin-modules "^3.1.0" - deep-freeze "^0.0.1" - deepmerge "^4.2.2" - is-module "^1.0.0" - resolve "^1.17.0" - "@rollup/plugin-node-resolve@^9.0.0": version "9.0.0" resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz#39bd0034ce9126b39c1699695f440b4b7d2b62e6" @@ -3819,15 +3801,6 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@rollup/pluginutils@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.0.0.tgz#e18e9f5a3925779fc15209dd316c1bd260d195ef" - integrity sha512-b5QiJRye4JlSg29bKNEECoKbLuPXZkPEHSgEjjP1CJV1CPdDBybfYHfm6kyq8yK51h/Zsyl8OvWUrp0FUBukEQ== - dependencies: - "@types/estree" "0.0.45" - estree-walker "^2.0.1" - picomatch "^2.2.2" - "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -4759,7 +4732,7 @@ "@babel/runtime" "^7.5.4" "@types/testing-library__react-hooks" "^3.3.0" -"@testing-library/react-hooks@^3.4.1", "@testing-library/react-hooks@^3.4.2": +"@testing-library/react-hooks@^3.4.2": version "3.4.2" resolved "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-3.4.2.tgz#8deb94f7684e0d896edd84a4c90e5b79a0810bc2" integrity sha512-RfPG0ckOzUIVeIqlOc1YztKgFW+ON8Y5xaSPbiBkfj9nMkkiLhLeBXT5icfPX65oJV/zCZu4z8EVnUc6GY9C5A== @@ -4999,11 +4972,6 @@ dependencies: "@types/express" "*" -"@types/cookie@^0.3.3": - version "0.3.3" - resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz#85bc74ba782fb7aa3a514d11767832b0e3bc6803" - integrity sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow== - "@types/cookie@^0.4.0": version "0.4.0" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" @@ -5095,11 +5063,6 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== -"@types/estree@0.0.45": - version "0.0.45" - resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" - integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== - "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.5": version "4.17.9" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1" @@ -5190,11 +5153,6 @@ resolved "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== -"@types/history@^4.7.7": - version "4.7.8" - resolved "https://registry.npmjs.org/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - "@types/html-minifier-terser@^5.0.0": version "5.1.0" resolved "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz#551a4589b6ee2cc9c1dff08056128aec29b94880" @@ -6068,7 +6026,7 @@ resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^3.8.0", "@typescript-eslint/eslint-plugin@^v3.10.1": +"@typescript-eslint/eslint-plugin@^v3.10.1": version "3.10.1" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== @@ -6103,7 +6061,7 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.8.0", "@typescript-eslint/parser@^v3.10.1": +"@typescript-eslint/parser@^v3.10.1": version "3.10.1" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== @@ -9324,7 +9282,7 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6, cross-fetch@^3.0.5, cross-fetch@^3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== @@ -9690,7 +9648,7 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@*, cypress@^4.11.0, cypress@^4.2.0: +cypress@*, cypress@^4.2.0: version "4.12.1" resolved "https://registry.npmjs.org/cypress/-/cypress-4.12.1.tgz#0ead1b9f4c0917d69d8b57f996b6e01fe693b6ec" integrity sha512-9SGIPEmqU8vuRA6xst2CMTYd9sCFCxKSzrHt0wr+w2iAQMCIIsXsQ5Gplns1sT6LDbZcmLv6uehabAOl3fhc9Q== @@ -9890,7 +9848,7 @@ dateformat@^3.0.0: resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.9.1, dayjs@^1.9.4: +dayjs@^1.9.4: version "1.9.4" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.9.4.tgz#fcde984e227f4296f04e7b05720adad2e1071f1b" integrity sha512-ABSF3alrldf7nM9sQ2U+Ln67NRwmzlLOqG7kK03kck0mw3wlSSEKv/XhKGGxUjQcS57QeiCyNdrFgtj9nWlrng== @@ -10023,11 +9981,6 @@ deep-extend@0.6.0, deep-extend@^0.6.0, deep-extend@~0.6.0: resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-freeze@^0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" - integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ= - deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -10774,41 +10727,6 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es- string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" -es-abstract@^1.17.5: - version "1.17.7" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: - version "1.18.0-next.1" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" @@ -10899,11 +10817,6 @@ es6-weak-map@^2.0.2: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -esbuild@^0.6.18: - version "0.6.34" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.6.34.tgz#76565a60e006f45d5f273b6e59e61ed0816551f5" - integrity sha512-InRdL/Q96pUucPqovJzvuLhquZr6jOn81FDVwFjCKz1rYKIm9OdOC+7Fs4vr6x48vKBl5LzKgtjU39BUpO636A== - esbuild@^0.7.7: version "0.7.7" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.7.tgz#fd86332d1c0a047231bd6da930666028c40c14bf" @@ -10992,13 +10905,6 @@ eslint-plugin-cypress@^2.10.3: dependencies: globals "^11.12.0" -eslint-plugin-cypress@^2.11.1: - version "2.11.2" - resolved "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.11.2.tgz#a8f3fe7ec840f55e4cea37671f93293e6c3e76a0" - integrity sha512-1SergF1sGbVhsf7MYfOLiBhdOg6wqyeV9pXUAIDIffYTGMN3dTBQS9nFAzhLsHhO+Bn0GaVM1Ecm71XUidQ7VA== - dependencies: - globals "^11.12.0" - eslint-plugin-graphql@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/eslint-plugin-graphql/-/eslint-plugin-graphql-4.0.0.tgz#d238ff2baee4d632cfcbe787a7a70a1f50428358" @@ -11095,23 +11001,6 @@ eslint-plugin-react@^7.12.4: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" -eslint-plugin-react@^7.20.5: - version "7.21.5" - resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" - integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== - dependencies: - array-includes "^3.1.1" - array.prototype.flatmap "^1.2.3" - doctrine "^2.1.0" - has "^1.0.3" - jsx-ast-utils "^2.4.1 || ^3.0.0" - object.entries "^1.1.2" - object.fromentries "^2.0.2" - object.values "^1.1.1" - prop-types "^15.7.2" - resolve "^1.18.1" - string.prototype.matchall "^4.0.2" - eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -11140,11 +11029,6 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint-visitor-keys@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - eslint-visitor-keys@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" @@ -11193,49 +11077,6 @@ eslint@^7.1.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -eslint@^7.6.0: - version "7.12.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-7.12.0.tgz#7b6a85f87a9adc239e979bb721cde5ce0dc27da6" - integrity sha512-n5pEU27DRxCSlOhJ2rO57GDLcNsxO0LPpAbpFdh7xmcDmjmlGUfoyrsB3I7yYdQXO5N3gkSTiDrPSPNFiiirXA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.2.0" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.0.1" - doctrine "^3.0.0" - enquirer "^2.3.5" - eslint-scope "^5.1.1" - eslint-utils "^2.1.0" - eslint-visitor-keys "^2.0.0" - espree "^7.3.0" - esquery "^1.2.0" - esutils "^2.0.2" - file-entry-cache "^5.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash "^4.17.19" - minimatch "^3.0.4" - natural-compare "^1.4.0" - optionator "^0.9.1" - progress "^2.0.0" - regexpp "^3.1.0" - semver "^7.2.1" - strip-ansi "^6.0.0" - strip-json-comments "^3.1.0" - table "^5.2.3" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - esm@^3.2.25: version "3.2.25" resolved "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" @@ -11250,15 +11091,6 @@ espree@^7.3.0: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.3.0" -espree@^7.3.0: - version "7.3.0" - resolved "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz#dc30437cf67947cf576121ebd780f15eeac72348" - integrity sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw== - dependencies: - acorn "^7.4.0" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.3.0" - esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -12935,7 +12767,7 @@ graphql-upload@^8.0.2: http-errors "^1.7.3" object-path "^0.11.4" -graphql@15.3.0, graphql@^15.0.0, graphql@^15.3.0: +graphql@15.3.0, graphql@^15.3.0: version "15.3.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== @@ -13111,7 +12943,7 @@ he@^1.1.0, he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -headers-utils@^1.1.9, headers-utils@^1.2.0: +headers-utils@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz#5e10d1bc9d2bccf789547afca5b991a3167241e8" integrity sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A== @@ -13919,11 +13751,6 @@ is-callable@^1.1.4, is-callable@^1.1.5: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== -is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== - is-ci@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -13943,13 +13770,6 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" -is-core-module@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" - integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -14118,11 +13938,6 @@ is-module@^1.0.0: resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= - is-npm@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -14225,7 +14040,7 @@ is-promise@^2.1, is-promise@^2.1.0: resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-reference@^1.1.2, is-reference@^1.2.1: +is-reference@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== @@ -14646,14 +14461,6 @@ jest-esm-transformer@^1.0.0: "@babel/core" "^7.4.4" "@babel/plugin-transform-modules-commonjs" "^7.4.4" -jest-fetch-mock@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" - integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== - dependencies: - cross-fetch "^3.0.4" - promise-polyfill "^8.1.3" - jest-get-type@^25.2.6: version "25.2.6" resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" @@ -14948,11 +14755,6 @@ jose@^2.0.2: dependencies: "@panva/asn1.js" "^1.0.0" -joycon@^2.2.5: - version "2.2.5" - resolved "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615" - integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ== - js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -15358,14 +15160,6 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" -"jsx-ast-utils@^2.4.1 || ^3.0.0": - version "3.1.0" - resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" - integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== - dependencies: - array-includes "^3.1.1" - object.assign "^4.1.1" - jwa@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -16126,7 +15920,7 @@ macos-release@^2.2.0: resolved "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz#eb1930b036c0800adebccd5f17bc4c12de8bb71f" integrity sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA== -magic-string@^0.25.2, magic-string@^0.25.7: +magic-string@^0.25.7: version "0.25.7" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -16919,22 +16713,6 @@ ms@2.1.2, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -msw@^0.19.5: - version "0.19.5" - resolved "https://registry.npmjs.org/msw/-/msw-0.19.5.tgz#7d2a1a852ccf1644d3db6735d69fff6777aac33f" - integrity sha512-J5eQ++gDVZoHPC8gVXtWcakLjgmPipvFj/sEnlRV/WViXuiq2CamSqO3Wbh6H8bAmj+k2vUWCfcVT1HjMdKB2Q== - dependencies: - "@open-draft/until" "^1.0.0" - "@types/cookie" "^0.3.3" - chalk "^4.0.0" - cookie "^0.4.1" - graphql "^15.0.0" - headers-utils "^1.1.9" - node-match-path "^0.4.2" - node-request-interceptor "^0.2.5" - statuses "^2.0.0" - yargs "^15.3.1" - msw@^0.20.5: version "0.20.5" resolved "https://registry.npmjs.org/msw/-/msw-0.20.5.tgz#b6141080c0d8b17c451d9ca36c28cc47b4ac487a" @@ -17248,7 +17026,7 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-match-path@^0.4.2, node-match-path@^0.4.4: +node-match-path@^0.4.4: version "0.4.4" resolved "https://registry.npmjs.org/node-match-path/-/node-match-path-0.4.4.tgz#516a10926093c0cc6f237d020685b593b19baebb" integrity sha512-pBq9gp7TG0r0VXuy/oeZmQsjBSnYQo7G886Ly/B3azRwZuEtHCY155dzmfoKWcDPGgyfIGD8WKVC7h3+6y7yTg== @@ -17307,14 +17085,6 @@ node-releases@^1.1.52, node-releases@^1.1.58: resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== -node-request-interceptor@^0.2.5: - version "0.2.6" - resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.2.6.tgz#541278d7033bb6a8befb5dd793f83428cf6446a2" - integrity sha512-aJW1tPSM7nzuZFRe+C/KSz22GJO3CVFMxHHmMGX8Z+tjP7TCIVbzeckLFVfJG68BdVgrdOOP7Ejc57ag820eyA== - dependencies: - debug "^4.1.1" - headers-utils "^1.2.0" - node-request-interceptor@^0.3.5: version "0.3.5" resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.3.5.tgz#4b26159617829c9a70643012c0fdc3ae4c78ae43" @@ -17577,11 +17347,6 @@ object-inspect@^1.7.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== - object-is@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" @@ -17614,16 +17379,6 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" -object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" - has-symbols "^1.0.1" - object-keys "^1.1.1" - object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" @@ -17644,15 +17399,6 @@ object.entries@^1.1.0, object.entries@^1.1.1: function-bind "^1.1.1" has "^1.0.3" -object.entries@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" - integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - has "^1.0.3" - "object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" @@ -19303,11 +19049,6 @@ promise-inflight@^1.0.1: resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -promise-polyfill@^8.1.3: - version "8.2.0" - resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.2.0.tgz#367394726da7561457aba2133c9ceefbd6267da0" - integrity sha512-k/TC0mIcPVF6yHhUvwAp7cvL6I2fFV7TzF1DuGPI8mBh4QQazf36xCKEHKTZKRysEoTQoQdKyP25J8MPJp7j5g== - promise-retry@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" @@ -20062,7 +19803,7 @@ react-router-dom@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.2.0, react-router@^5.2.0: +react-router@5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== @@ -20931,14 +20672,6 @@ resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0 dependencies: path-parse "^1.0.6" -resolve@^1.11.0, resolve@^1.18.1: - version "1.18.1" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" - integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== - dependencies: - is-core-module "^2.0.0" - path-parse "^1.0.6" - responselike@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -21053,20 +20786,13 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rollup-plugin-dts@1.4.13, rollup-plugin-dts@^1.4.10: +rollup-plugin-dts@1.4.13: version "1.4.13" resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.13.tgz#4f086e84f4fdcc1f49160799ebc66f6b09db292b" integrity sha512-7mxoQ6PcmCkBE5ZhrjGDL4k42XLy8BkSqpiRi1MipwiGs+7lwi4mQkp2afX+OzzLjJp/TGM8llfe8uayIUhPEw== optionalDependencies: "@babel/code-frame" "^7.10.4" -rollup-plugin-dts@1.4.8: - version "1.4.8" - resolved "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-1.4.8.tgz#3b0eadc285aede11c3f5ced72b56b5f7735df95c" - integrity sha512-2qHB4B3oaTyi1mDmqDzsRGKlev32v3EhMUAmc45Cn9eB22R7FsYDiigvT9XdM/oQzfd0qv5MkeZju8DP0nauiA== - optionalDependencies: - "@babel/code-frame" "^7.10.4" - rollup-plugin-esbuild@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" @@ -21074,25 +20800,11 @@ rollup-plugin-esbuild@^2.0.0: dependencies: "@rollup/pluginutils" "^3.1.0" -rollup-plugin-esbuild@^2.4.2: - version "2.5.2" - resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.5.2.tgz#fd7d4a88518898012a9d07e4c3ef8b4009c244d3" - integrity sha512-E4q3ac1AlMd0m0ZRYffdiorOt2eZcxfbdPaqBLs7JLnPE8krgIAihOD6cTUc54UJjoOMA9WcY63TR+JKWLzYNw== - dependencies: - "@rollup/pluginutils" "^4.0.0" - joycon "^2.2.5" - strip-json-comments "^3.1.1" - rollup-plugin-peer-deps-external@^2.2.2: version "2.2.3" resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.3.tgz#059a8aec1eefb48a475e9fcedc3b9e3deb521213" integrity sha512-W6IePXTExGXVDAlfZbNUUrx3GxUOZP248u5n4a4ID1XZMrbQ+uGeNiEfapvdzwx0qZi5DNH/hDLiPUP+pzFIxg== -rollup-plugin-peer-deps-external@^2.2.3: - version "2.2.4" - resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d" - integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g== - rollup-plugin-postcss@^3.1.1: version "3.1.8" resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-3.1.8.tgz#d1bcaf8eb0fcb0936e3684c22dd8628d13a82fd1" @@ -21131,13 +20843,6 @@ rollup-pluginutils@^2.8.2: dependencies: estree-walker "^0.6.1" -rollup@2.23.1: - version "2.23.1" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.1.tgz#d458d28386dc7660c2e8a4978bea6f9494046c20" - integrity sha512-Heyl885+lyN/giQwxA8AYT2GY3U+gOlTqVLrMQYno8Z1X9lAOpfXPiKiZCyPc25e9BLJM3Zlh957dpTlO4pa8A== - optionalDependencies: - fsevents "~2.1.2" - rollup@2.23.x: version "2.23.0" resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" @@ -21153,13 +20858,6 @@ rollup@^0.63.4: "@types/estree" "0.0.39" "@types/node" "*" -rollup@^2.23.1: - version "2.32.1" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz#625a92c54f5b4d28ada12d618641491d4dbb548c" - integrity sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw== - optionalDependencies: - fsevents "~2.1.2" - rsvp@^4.8.4: version "4.8.5" resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -22281,14 +21979,6 @@ string.prototype.padstart@^3.0.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -string.prototype.trimend@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.2.tgz#6ddd9a8796bc714b489a3ae22246a208f37bfa46" - integrity sha512-8oAG/hi14Z4nOVP0z6mdiVZ/wqjDtWSLygMigTzAb+7aPEDTleeFf+WrF+alzecxIRkckkJVn+dTlwzJXORATw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - string.prototype.trimleft@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" @@ -22305,14 +21995,6 @@ string.prototype.trimright@^2.1.1: define-properties "^1.1.3" function-bind "^1.1.1" -string.prototype.trimstart@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.2.tgz#22d45da81015309cd0cdd79787e8919fc5c613e7" - integrity sha512-7F6CdBTl5zyu30BJFdzSTlSlLPwODC23Od+iLoVH8X6+3fvDPPuBVVj9iaB1GOsSTSIgVfsfm27R2FGrAPznWg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.18.0-next.1" - string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -22415,11 +22097,6 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" From c6332546e6d8445db5d8dbccabfb73d2cd809035 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 18 Nov 2020 23:23:54 +0100 Subject: [PATCH 017/195] Apply Code review fixes --- plugins/catalog-backend/src/service/router.ts | 2 +- plugins/catalog-import/package.json | 12 +++++------ .../src/api/CatalogImportApi.ts | 1 - .../src/api/CatalogImportClient.ts | 20 ++++++++++++++----- plugins/catalog-import/src/plugin.ts | 6 ++++-- .../catalog-import/src/util/useGithubRepos.ts | 6 +----- 6 files changed, 27 insertions(+), 20 deletions(-) diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index fd9062bf94..e850792936 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -144,7 +144,7 @@ export async function createRouter( router.post('/analyze-location', async (req, res) => { const input = await validateRequestBody(req, analyzeLocationSchema); const output = await locationAnalyzer.generateConfig(input); - res.status(201).send(output); + res.status(200).send(output); }); } diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index d6a08a35c7..ee157235ca 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.1.1-alpha.26", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.26", - "@backstage/core": "^0.1.1-alpha.26", - "@backstage/plugin-catalog": "^0.1.1-alpha.26", - "@backstage/plugin-catalog-backend": "^0.1.1-alpha.26", - "@backstage/theme": "^0.1.1-alpha.26", + "@backstage/catalog-model": "^0.2.0", + "@backstage/core": "^0.2.0", + "@backstage/plugin-catalog": "^0.2.0", + "@backstage/plugin-catalog-backend": "^0.2.0", + "@backstage/theme": "^0.2.0", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/plugins/catalog-import/src/api/CatalogImportApi.ts b/plugins/catalog-import/src/api/CatalogImportApi.ts index 7ecb4a543b..49012764e2 100644 --- a/plugins/catalog-import/src/api/CatalogImportApi.ts +++ b/plugins/catalog-import/src/api/CatalogImportApi.ts @@ -24,7 +24,6 @@ export const catalogImportApiRef = createApiRef({ export interface CatalogImportApi { submitPrToRepo(options: { - oAuthToken: string; owner: string; repo: string; fileContent: string; diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index cef4b2da0e..f8b4e5d17e 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,16 +15,26 @@ */ import { Octokit } from '@octokit/rest'; -import { DiscoveryApi } from '@backstage/core'; +import { + DiscoveryApi, + githubAuthApiRef, + OAuthApi, + useApi, +} from '@backstage/core'; import { CatalogImportApi } from './CatalogImportApi'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; import { PartialEntity } from '../util/types'; export class CatalogImportClient implements CatalogImportApi { private readonly discoveryApi: DiscoveryApi; + private readonly githubAuthApi: OAuthApi; - constructor(options: { discoveryApi: DiscoveryApi }) { + constructor(options: { + discoveryApi: DiscoveryApi; + githubAuthApi: OAuthApi; + }) { this.discoveryApi = options.discoveryApi; + this.githubAuthApi = options.githubAuthApi; } async generateEntityDefinitions({ @@ -83,18 +93,18 @@ export class CatalogImportClient implements CatalogImportApi { } async submitPrToRepo({ - oAuthToken, owner, repo, fileContent, }: { - oAuthToken: string; owner: string; repo: string; fileContent: string; }): Promise<{ link: string; location: string }> { + const token = await this.githubAuthApi.getAccessToken(['repo']); + const octo = new Octokit({ - auth: oAuthToken, + auth: token, }); const branchName = 'backstage-integration'; diff --git a/plugins/catalog-import/src/plugin.ts b/plugins/catalog-import/src/plugin.ts index caac764513..3ce4918553 100644 --- a/plugins/catalog-import/src/plugin.ts +++ b/plugins/catalog-import/src/plugin.ts @@ -19,6 +19,7 @@ import { createPlugin, createRouteRef, discoveryApiRef, + githubAuthApiRef, } from '@backstage/core'; import { catalogImportApiRef } from './api/CatalogImportApi'; import { CatalogImportClient } from './api/CatalogImportClient'; @@ -33,8 +34,9 @@ export const plugin = createPlugin({ apis: [ createApiFactory({ api: catalogImportApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new CatalogImportClient({ discoveryApi }), + deps: { discoveryApi: discoveryApiRef, githubAuthApi: githubAuthApiRef }, + factory: ({ discoveryApi, githubAuthApi }) => + new CatalogImportClient({ discoveryApi, githubAuthApi }), }), ], }); diff --git a/plugins/catalog-import/src/util/useGithubRepos.ts b/plugins/catalog-import/src/util/useGithubRepos.ts index 84c594c913..a18edc2777 100644 --- a/plugins/catalog-import/src/util/useGithubRepos.ts +++ b/plugins/catalog-import/src/util/useGithubRepos.ts @@ -15,21 +15,17 @@ */ import * as YAML from 'yaml'; -import { useApi, githubAuthApiRef } from '@backstage/core'; +import { useApi } from '@backstage/core'; import { catalogImportApiRef } from '../api/CatalogImportApi'; import { ConfigSpec } from '../components/ImportComponentPage'; export function useGithubRepos() { const api = useApi(catalogImportApiRef); - const auth = useApi(githubAuthApiRef); const submitPrToRepo = async (selectedRepo: ConfigSpec) => { - const token = await auth.getAccessToken(['repo']); - const [ownerName, repoName] = selectedRepo.location.split('/').slice(-2); const submitPRResponse = await api .submitPrToRepo({ - oAuthToken: token, owner: ownerName, repo: repoName, fileContent: selectedRepo.config From eb422ee1bf2c888ee68aca0ea94e4b8e83ddc3ea Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 19 Nov 2020 15:35:25 +0100 Subject: [PATCH 018/195] Fixes after merge --- packages/app/package.json | 1 + plugins/catalog-backend/src/ingestion/types.ts | 2 +- plugins/catalog-import/package.json | 8 ++++---- plugins/catalog-import/src/api/CatalogImportClient.ts | 7 +------ 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index de6b61684a..6d31adfa65 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -9,6 +9,7 @@ "@backstage/core": "^0.3.0", "@backstage/plugin-api-docs": "^0.2.1", "@backstage/plugin-catalog": "^0.2.1", + "@backstage/plugin-catalog-import": "^0.2.0", "@backstage/plugin-circleci": "^0.2.1", "@backstage/plugin-cloudbuild": "^0.2.1", "@backstage/plugin-cost-insights": "^0.3.0", diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 29308fdd92..79a5387247 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -20,7 +20,7 @@ import { Location, LocationSpec, } from '@backstage/catalog-model'; -import { RecursivePartial } from './processors/ldap/util'; +import { RecursivePartial } from '../util/RecursivePartial'; // // HigherOrderOperation diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ee157235ca..4666e22f10 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.2.0", + "@backstage/core": "^0.3.0", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.0", "@backstage/theme": "^0.2.0", @@ -40,9 +40,9 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.26", - "@backstage/dev-utils": "^0.1.1-alpha.26", - "@backstage/test-utils": "^0.1.1-alpha.26", + "@backstage/cli": "^0.2.0", + "@backstage/dev-utils": "^0.1.0", + "@backstage/test-utils": "^0.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index f8b4e5d17e..94ba260d55 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -15,12 +15,7 @@ */ import { Octokit } from '@octokit/rest'; -import { - DiscoveryApi, - githubAuthApiRef, - OAuthApi, - useApi, -} from '@backstage/core'; +import { DiscoveryApi, OAuthApi } from '@backstage/core'; import { CatalogImportApi } from './CatalogImportApi'; import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-backend'; import { PartialEntity } from '../util/types'; From 46e9cf5b63b8a10c32bf830a00339d035bf570d8 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 19 Nov 2020 15:55:27 +0100 Subject: [PATCH 019/195] Bump package versions --- plugins/catalog-import/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 4666e22f10..b53c4ec00d 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -25,7 +25,7 @@ "@backstage/core": "^0.3.0", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.0", - "@backstage/theme": "^0.2.0", + "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,8 +41,8 @@ }, "devDependencies": { "@backstage/cli": "^0.2.0", - "@backstage/dev-utils": "^0.1.0", - "@backstage/test-utils": "^0.1.0", + "@backstage/dev-utils": "^0.1.3", + "@backstage/test-utils": "^0.1.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From 9183042be7a4ffb5c10d7ae16819f2b4e295caa3 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 09:37:56 +0100 Subject: [PATCH 020/195] Bump backstage-cli dependency --- plugins/catalog-import/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index b53c4ec00d..25d2523dd8 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -40,7 +40,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.2.0", + "@backstage/cli": "^0.3.0", "@backstage/dev-utils": "^0.1.3", "@backstage/test-utils": "^0.1.2", "@testing-library/jest-dom": "^5.10.1", From cb27aac51506134c5be4a91eed8837444f154b1e Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 12:42:10 +0100 Subject: [PATCH 021/195] Bump package versions --- plugins/catalog-import/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 25d2523dd8..2fbea6a17c 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.0", + "@backstage/core": "^0.3.1", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.0", "@backstage/theme": "^0.2.1", @@ -41,8 +41,8 @@ }, "devDependencies": { "@backstage/cli": "^0.3.0", - "@backstage/dev-utils": "^0.1.3", - "@backstage/test-utils": "^0.1.2", + "@backstage/dev-utils": "^0.1.4", + "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", From bd613dd8509b5173e050fff196e480f46b0f0d42 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 14:30:49 +0100 Subject: [PATCH 022/195] Code review adjustments --- plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts | 6 +++--- plugins/catalog-backend/src/ingestion/types.ts | 8 +++++++- plugins/catalog-backend/src/service/CatalogBuilder.ts | 4 ++-- plugins/catalog-backend/src/service/router.ts | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 611505d707..97df326fc5 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -22,13 +22,13 @@ import { LocationAnalyzer, } from './types'; -export class LocationAnalyzerClient implements LocationAnalyzer { +export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; constructor(logger: Logger) { this.logger = logger; } - async generateConfig( + async analyzeLocation( request: AnalyzeLocationRequest, ): Promise { const { owner, name, source } = parseGitUri(request.location.target); @@ -40,7 +40,7 @@ export class LocationAnalyzerClient implements LocationAnalyzer { // Probably won't handle properly self-hosted git providers with custom url annotations: { [`${source}/project-slug`]: `${owner}/${name}` }, }, - spec: { type: 'other', owner: owner, lifecycle: 'unknown' }, + spec: { type: 'other', lifecycle: 'unknown' }, }; this.logger.debug(`entity created for ${request.location.target}`); diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 79a5387247..c8d642b6b5 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -75,7 +75,13 @@ export type ReadLocationError = { // export type LocationAnalyzer = { - generateConfig( + /** + * Generates an entity configuration for given git repository. It's used for + * importing new component to the backstage app. + * + * @param location Git repository to analyze and generate config for. + */ + analyzeLocation( location: AnalyzeLocationRequest, ): Promise; }; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 439b85591f..0fee610ce6 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -52,7 +52,7 @@ import { UrlReaderProcessor, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; -import { LocationAnalyzerClient } from '../ingestion/LocationAnalyzer'; +import { RepoLocationAnalyzer } from '../ingestion/LocationAnalyzer'; import { BuiltinKindsEntityProcessor } from '../ingestion/processors/BuiltinKindsEntityProcessor'; import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { @@ -232,7 +232,7 @@ export class CatalogBuilder { locationReader, logger, ); - const locationAnalyzer = new LocationAnalyzerClient(logger); + const locationAnalyzer = new RepoLocationAnalyzer(logger); return { entitiesCatalog, diff --git a/plugins/catalog-backend/src/service/router.ts b/plugins/catalog-backend/src/service/router.ts index d11ed49253..43c2967233 100644 --- a/plugins/catalog-backend/src/service/router.ts +++ b/plugins/catalog-backend/src/service/router.ts @@ -139,7 +139,7 @@ export async function createRouter( if (locationAnalyzer) { router.post('/analyze-location', async (req, res) => { const input = await validateRequestBody(req, analyzeLocationSchema); - const output = await locationAnalyzer.generateConfig(input); + const output = await locationAnalyzer.analyzeLocation(input); res.status(200).send(output); }); } From a9fd599f7a374ec14c83c964ed7c6054813feb33 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 15:41:47 +0100 Subject: [PATCH 023/195] Add chengeset --- .changeset/smart-turkeys-bathe.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/smart-turkeys-bathe.md diff --git a/.changeset/smart-turkeys-bathe.md b/.changeset/smart-turkeys-bathe.md new file mode 100644 index 0000000000..9e8ca1270e --- /dev/null +++ b/.changeset/smart-turkeys-bathe.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-catalog-import': minor +'example-app': patch +'example-backend': patch +'@backstage/catalog-model': patch +'@backstage/plugin-scaffolder': patch +--- + +Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. From d74f239d5cf4e29af2767ebf28016b3242e5a2d3 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 20 Nov 2020 16:03:51 +0100 Subject: [PATCH 024/195] Bump packages and add migration instruction Migration instruction added to the changeset file. --- .changeset/smart-turkeys-bathe.md | 22 ++++++++++++++++++++- packages/backend/package.json | 2 +- plugins/catalog-backend/package.json | 2 +- plugins/catalog-import/package.json | 2 +- yarn.lock | 29 ++++++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/.changeset/smart-turkeys-bathe.md b/.changeset/smart-turkeys-bathe.md index 9e8ca1270e..f7776a81aa 100644 --- a/.changeset/smart-turkeys-bathe.md +++ b/.changeset/smart-turkeys-bathe.md @@ -7,4 +7,24 @@ '@backstage/plugin-scaffolder': patch --- -Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. +Add Analyze location endpoint to catalog backend. Add catalog-import plugin and replace import-component with it. To start using Analyze location endpoint, you have add it to the `createRouter` function options in the `\backstage\packages\backend\src\plugins\catalog.ts` file: + +```ts +export default async function createPlugin(env: PluginEnvironment) { + const builder = new CatalogBuilder(env); + const { + entitiesCatalog, + locationsCatalog, + higherOrderOperation, + locationAnalyzer, //<-- + } = await builder.build(); + + return await createRouter({ + entitiesCatalog, + locationsCatalog, + higherOrderOperation, + locationAnalyzer, //<-- + logger: env.logger, + }); +} +``` diff --git a/packages/backend/package.json b/packages/backend/package.json index 014a3f78d5..5011c21c14 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -23,7 +23,7 @@ "@backstage/config": "^0.1.1", "@backstage/plugin-app-backend": "^0.3.0", "@backstage/plugin-auth-backend": "^0.2.2", - "@backstage/plugin-catalog-backend": "^0.2.1", + "@backstage/plugin-catalog-backend": "^0.3.0", "@backstage/plugin-graphql-backend": "^0.1.3", "@backstage/plugin-kubernetes-backend": "^0.1.3", "@backstage/plugin-proxy-backend": "^0.2.1", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 711a91dd8d..36ebbedab4 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.1", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 2fbea6a17c..ac41d70c30 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -24,7 +24,7 @@ "@backstage/catalog-model": "^0.2.0", "@backstage/core": "^0.3.1", "@backstage/plugin-catalog": "^0.2.0", - "@backstage/plugin-catalog-backend": "^0.2.0", + "@backstage/plugin-catalog-backend": "^0.3.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/yarn.lock b/yarn.lock index c7e518ede3..23e422b9ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,6 +1325,35 @@ react-use "^15.3.3" remark-gfm "^1.0.0" +"@backstage/plugin-catalog-backend@^0.2.1": + version "0.2.1" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog-backend/-/plugin-catalog-backend-0.2.1.tgz#d63d1d145d97a5f1021c3e6c2ab680729be43e74" + integrity sha512-9OwM7JobjCvmNomsT2HbMyzgoWX+ztVGG4ebN3x/r75+sI55Dj3J29O9CRP7r86BIJ7bi1RWaLTycXX+CmuQ0Q== + dependencies: + "@backstage/backend-common" "^0.3.0" + "@backstage/catalog-model" "^0.2.0" + "@backstage/config" "^0.1.1" + "@octokit/graphql" "^4.5.6" + "@types/express" "^4.17.6" + codeowners-utils "^1.0.2" + core-js "^3.6.5" + cross-fetch "^3.0.6" + express "^4.17.1" + express-promise-router "^3.0.3" + fs-extra "^9.0.0" + git-url-parse "^11.4.0" + knex "^0.21.6" + ldapjs "^2.2.0" + lodash "^4.17.15" + morgan "^1.10.0" + p-limit "^3.0.2" + sqlite3 "^5.0.0" + uuid "^8.0.0" + winston "^3.2.1" + yaml "^1.9.2" + yn "^4.0.0" + yup "^0.29.3" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From cb16f9ebfc61caaa65d5c3d3f3fdfe81977c1b97 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Mon, 23 Nov 2020 19:34:15 +0100 Subject: [PATCH 025/195] Code review fixes --- packages/app/src/App.tsx | 2 +- .../src/components/ComponentConfigDisplay.tsx | 32 ++++++++++++++++--- .../src/components/ImportFinished.tsx | 7 +++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index c02d83841f..28aebeeb79 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -69,7 +69,7 @@ const AppRoutes = () => ( } /> void; + nextStep: (options?: { reset: boolean }) => void; configFile: ConfigSpec; savePRLink: (PRLink: string) => void; catalogRouteRef: RouteRef; @@ -74,6 +74,7 @@ const ComponentConfigDisplay = ({ savePRLink, catalogRouteRef, }: Props) => { + const [errorOccured, setErrorOccured] = useState(false); const [submitting, setSubmitting] = useState(false); const errorApi = useApi(errorApiRef); const { submitPrToRepo, addLocation } = useGithubRepos(); @@ -91,6 +92,7 @@ const ComponentConfigDisplay = ({ nextStep(); } } catch (e) { + setErrorOccured(true); setSubmitting(false); errorApi.post(e); } @@ -102,13 +104,25 @@ const ComponentConfigDisplay = ({ Following config object will be submitted in a pull request to the repository{' '} - {configFile.location} and - added as a new location to the backend + + {configFile.location} + {' '} + and added as a new location to the backend ) : ( Following config object will be added as a new location to the backend{' '} - {configFile.location} + + {configFile.location} + )} @@ -164,6 +178,16 @@ const ComponentConfigDisplay = ({ > Next + {errorOccured ? ( + + ) : null} diff --git a/plugins/catalog-import/src/components/ImportFinished.tsx b/plugins/catalog-import/src/components/ImportFinished.tsx index 9a06de234b..caba678999 100644 --- a/plugins/catalog-import/src/components/ImportFinished.tsx +++ b/plugins/catalog-import/src/components/ImportFinished.tsx @@ -36,7 +36,12 @@ export const ImportFinished = ({ nextStep, PRLink, type }: Props) => { {type === 'repo' ? ( - + View pull request on GitHub ) : null} From 8072bc85c35a3d46b39f1cb85cb3ad473d31cfc4 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:45:02 -0500 Subject: [PATCH 026/195] Update daily cost data to return groupedCosts --- plugins/cost-insights/src/client.ts | 6 ++++-- plugins/cost-insights/src/types/Cost.ts | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index 2180385424..c78a3dc2dc 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -35,7 +35,7 @@ import { UnlabeledDataflowAlert, } from '../src/utils/alerts'; import { inclusiveStartDateOf } from '../src/utils/duration'; -import { trendlineOf, changeOf } from './utils/mockData'; +import { trendlineOf, changeOf, getGroupedProducts } from './utils/mockData'; type IntervalFields = { duration: Duration; @@ -56,7 +56,7 @@ function parseIntervals(intervals: string): IntervalFields { }; } -function aggregationFor( +export function aggregationFor( intervals: string, baseline: number, ): DateAggregation[] { @@ -140,6 +140,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + groupedCosts: getGroupedProducts(intervals), }, ); @@ -155,6 +156,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + groupedCosts: getGroupedProducts(intervals), }, ); diff --git a/plugins/cost-insights/src/types/Cost.ts b/plugins/cost-insights/src/types/Cost.ts index 84a8bfae51..dd28bba6b0 100644 --- a/plugins/cost-insights/src/types/Cost.ts +++ b/plugins/cost-insights/src/types/Cost.ts @@ -21,6 +21,7 @@ import { Trendline } from './Trendline'; export interface Cost { id: string; aggregation: DateAggregation[]; - change: ChangeStatistic; - trendline: Trendline; + change?: ChangeStatistic; + trendline?: Trendline; + groupedCosts?: Cost[]; } From f1272644191124975c9e00c0b5e25d701ceaed6d Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:45:40 -0500 Subject: [PATCH 027/195] Add aggregation sum util --- plugins/cost-insights/src/utils/change.ts | 9 +++++---- plugins/cost-insights/src/utils/sum.ts | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 plugins/cost-insights/src/utils/sum.ts diff --git a/plugins/cost-insights/src/utils/change.ts b/plugins/cost-insights/src/utils/change.ts index 1718462948..fc50439aad 100644 --- a/plugins/cost-insights/src/utils/change.ts +++ b/plugins/cost-insights/src/utils/change.ts @@ -23,6 +23,7 @@ import { MetricData, Duration, DEFAULT_DATE_FORMAT, + DateAggregation, } from '../types'; import dayjs, { OpUnitType } from 'dayjs'; import duration from 'dayjs/plugin/duration'; @@ -54,9 +55,9 @@ export function getComparedChange( duration: Duration, lastCompleteBillingDate: string, // YYYY-MM-DD, ): ChangeStatistic { - const ratio = dailyCost.change.ratio - metricData.change.ratio; + const ratio = dailyCost.change!.ratio - metricData.change.ratio; const previousPeriodTotal = getPreviousPeriodTotalCost( - dailyCost, + dailyCost.aggregation, duration, lastCompleteBillingDate, ); @@ -67,7 +68,7 @@ export function getComparedChange( } export function getPreviousPeriodTotalCost( - dailyCost: Cost, + aggregation: DateAggregation[], duration: Duration, inclusiveEndDate: string, ): number { @@ -83,7 +84,7 @@ export function getPreviousPeriodTotalCost( const nextPeriodStart = dayjs(startDate).add(amount, type); // Add up costs that incurred before the start of the next period. - return dailyCost.aggregation.reduce((acc, costByDate) => { + return aggregation.reduce((acc, costByDate) => { return dayjs(costByDate.date).isBefore(nextPeriodStart) ? acc + costByDate.amount : acc; diff --git a/plugins/cost-insights/src/utils/sum.ts b/plugins/cost-insights/src/utils/sum.ts new file mode 100644 index 0000000000..6fc181fea5 --- /dev/null +++ b/plugins/cost-insights/src/utils/sum.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { DateAggregation } from '../types'; + +export const aggregationSum = (aggregation: DateAggregation[]) => + aggregation.reduce((total, curAgg) => total + curAgg.amount, 0); From b9705638e969278c5bb9d456d177288b9f7c20cc Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:46:20 -0500 Subject: [PATCH 028/195] Add top panel breakdown view --- .../CostOverviewByProduct.tsx | 211 ++++++++++++++++++ .../CostOverviewCard/CostOverviewChart.tsx | 2 +- .../CostOverviewCard/CostOverviewHeader.tsx | 4 +- .../cost-insights/src/utils/change.test.ts | 2 +- 4 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx new file mode 100644 index 0000000000..1496fcdaf8 --- /dev/null +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx @@ -0,0 +1,211 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; +import { useTheme, Box } from '@material-ui/core'; +import { + AreaChart, + ContentRenderer, + TooltipProps, + XAxis, + YAxis, + Tooltip as RechartsTooltip, + Area, + ResponsiveContainer, + CartesianGrid, +} from 'recharts'; +import { Cost, DEFAULT_DATE_FORMAT, CostInsightsTheme } from '../../types'; +import { + BarChartTooltip as Tooltip, + BarChartTooltipItem as TooltipItem, +} from '../BarChart'; +import { + overviewGraphTickFormatter, + formatGraphValue, + isInvalid, +} from '../../utils/graphs'; +import { + useCostOverviewStyles as useStyles, + DataVizColors, +} from '../../utils/styles'; +import { useFilters, useLastCompleteBillingDate } from '../../hooks'; +import { mapFiltersToProps } from './selector'; +import { getPreviousPeriodTotalCost } from '../../utils/change'; +import { LegendItem } from '../LegendItem'; +import { currencyFormatter } from '../../utils/formatters'; +import { aggregationSum } from '../../utils/sum'; + +dayjs.extend(utc); + +export type CostOverviewByProductProps = { + dailyCostData: Cost; +}; + +const LOW_COST_THRESHOLD = 0.01; + +export const CostOverviewByProduct = ({ + dailyCostData, +}: CostOverviewByProductProps) => { + const theme = useTheme(); + const styles = useStyles(theme); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const { duration } = useFilters(mapFiltersToProps); + const groupedCosts = dailyCostData.groupedCosts; + + if (!groupedCosts) { + return null; + } + + const flattenedAggregation = groupedCosts + .map(cost => cost.aggregation) + .flat(); + const totalCost = flattenedAggregation.reduce( + (acc, agg) => acc + agg.amount, + 0, + ); + + const productsByDate = groupedCosts.reduce((prodByDate, group) => { + const product = group.id; + group.aggregation.forEach(curAggregation => { + const productCostsForDate = prodByDate[curAggregation.date] || {}; + // Group products with less than 10% of the total cost into "Other" category + if (aggregationSum(group.aggregation) < totalCost * LOW_COST_THRESHOLD) { + prodByDate[curAggregation.date] = { + ...productCostsForDate, + Other: + (prodByDate[curAggregation.date].Other || 0) + + curAggregation.amount, + }; + } else { + prodByDate[curAggregation.date] = { + ...productCostsForDate, + [product]: curAggregation.amount, + }; + } + }); + + return prodByDate; + }, {} as Record>); + + const chartData: any[] = Object.keys(productsByDate).map(date => { + return { + ...productsByDate[date], + date: Date.parse(date), + }; + }); + + const tooltipRenderer: ContentRenderer = ({ + label, + payload = [], + }) => { + if (isInvalid({ label, payload })) return null; + + const title = dayjs(label).utc().format(DEFAULT_DATE_FORMAT); + const items = payload.map(p => ({ + label: p.dataKey as string, + value: formatGraphValue(p.value as number), + fill: p.fill!, + })); + + return ( + + {items.reverse().map((item, index) => ( + + ))} + + ); + }; + + const previousPeriodTotal = getPreviousPeriodTotalCost( + flattenedAggregation, + duration, + lastCompleteBillingDate, + ); + const currentPeriodTotal = totalCost - previousPeriodTotal; + + const renderAreas = () => { + const highCostGroups = groupedCosts.filter( + group => + aggregationSum(group.aggregation) >= totalCost * LOW_COST_THRESHOLD, + ); + const sortedCostGroups = highCostGroups + .sort( + (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), + ) + .map(group => group.id); + // Keep 'Other' category at the bottom of the stack + return ['Other', ...sortedCostGroups].map((group, i) => ( + + )); + }; + + return ( + + + + + {currencyFormatter.format(previousPeriodTotal)} + + + + + {currencyFormatter.format(currentPeriodTotal)} + + + + + + + + 0, 'dataMax']} + tick={{ fill: styles.axis.fill }} + tickFormatter={formatGraphValue} + width={styles.yAxis.width} + /> + {renderAreas()} + + + + + + ); +}; diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx index 1910013581..4311081f20 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx @@ -93,7 +93,7 @@ export const CostOverviewChart = ({ .sort(aggregationSort) .map(entry => ({ date: Date.parse(entry.date), - trend: trendFrom(data.dailyCost.data.trendline, Date.parse(entry.date)), + trend: trendFrom(data.dailyCost.data.trendline!, Date.parse(entry.date)), dailyCost: entry.amount, ...(metric && data.metric.data ? { [data.metric.dataKey]: metricsByDate[`${entry.date}`] } diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx index e46b0c3a78..b23dd2041d 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewHeader.tsx @@ -27,7 +27,9 @@ export const CostOverviewHeader = ({ children, }: PropsWithChildren) => ( { const exclusiveEndDate = '2020-09-30'; expect( getPreviousPeriodTotalCost( - mockGroupDailyCost, + mockGroupDailyCost.aggregation, Duration.P1M, exclusiveEndDate, ), From 2ca1cc32aa7158fb17e1e113a391968fd3ebe3e3 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:47:32 -0500 Subject: [PATCH 029/195] Add top panel tabs --- .../CostOverviewCard/CostOverviewCard.tsx | 128 ++++++++++++------ plugins/cost-insights/src/utils/styles.ts | 26 ++++ 2 files changed, 115 insertions(+), 39 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index 81dc118ea9..742b89af7a 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -14,10 +14,19 @@ * limitations under the License. */ -import React from 'react'; -import { Box, Card, CardContent, Divider, useTheme } from '@material-ui/core'; +import React, { useState } from 'react'; +import { + Box, + Card, + CardContent, + Divider, + useTheme, + Tab, + Tabs, +} from '@material-ui/core'; import { CostGrowth } from '../CostGrowth'; import { CostOverviewChart } from './CostOverviewChart'; +import { CostOverviewByProduct } from './CostOverviewByProduct'; import { CostOverviewHeader } from './CostOverviewHeader'; import { LegendItem } from '../LegendItem'; import { MetricSelect } from '../MetricSelect'; @@ -34,6 +43,7 @@ import { formatPercent } from '../../utils/formatters'; import { findAlways } from '../../utils/assert'; import { getComparedChange } from '../../utils/change'; import { Cost, CostInsightsTheme, MetricData } from '../../types'; +import { useOverviewTabsStyles } from '../../utils/styles'; export type CostOverviewCardProps = { dailyCostData: Cost; @@ -47,6 +57,8 @@ export const CostOverviewCard = ({ const theme = useTheme(); const config = useConfig(); const lastCompleteBillingDate = useLastCompleteBillingDate(); + const [tabIndex, setTabIndex] = useState(0); + const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard); const { setDuration, setProject, setMetric, ...filters } = useFilters( mapFiltersToProps, @@ -63,53 +75,91 @@ export const CostOverviewCard = ({ lastCompleteBillingDate, ) : null; + const styles = useOverviewTabsStyles(theme); + + const tabs = [ + { id: 'overview', label: 'OVERVIEW' }, + { id: 'breakdown', label: 'BREAKDOWN BY PRODUCT' }, + ]; + + const OverviewTabs = () => { + return ( + <> + setTabIndex(index)} + value={tabIndex} + > + {tabs.map((tab, index) => ( + + ))} + + + ); + }; + + const OverviewLegend = () => { + return ( + + + + {formatPercent(dailyCostData.change!.ratio)} + + + {metric && metricData && comparedChange && ( + <> + + + {formatPercent(metricData.change.ratio)} + + + + + + + )} + + ); + }; return ( - + {dailyCostData.groupedCosts && } + - - - - - {formatPercent(dailyCostData.change.ratio)} - - - {metric && metricData && comparedChange && ( - <> - - - {formatPercent(metricData.change.ratio)} - - - - - - - )} - - + + {tabIndex === 0 ? ( + <> + + + + ) : ( + + )} - {config.metrics.length && ( + {config.metrics.length && tabIndex === 0 && ( ({ }, }); +export const useOverviewTabsStyles = makeStyles( + (theme: CostInsightsTheme) => ({ + default: { + padding: theme.spacing(2, 2), + fontWeight: 'bold', + color: theme.palette.text.secondary, + }, + selected: { + color: theme.palette.text.primary, + }, + }), +); + export const useBarChartStyles = (theme: CostInsightsTheme) => ({ axis: { fill: theme.palette.text.primary, From bed5c128da786022c83dec58e8af0ba347f23870 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 18:48:10 -0500 Subject: [PATCH 030/195] Add mock data for grouped Costs --- plugins/cost-insights/src/utils/mockData.ts | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 708ce733dd..4432753c32 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -34,6 +34,7 @@ import { getDefaultState as getDefaultLoadingState, } from '../utils/loading'; import { findAlways } from '../utils/assert'; +import { aggregationFor } from '../client'; type mockAlertRenderer = (alert: T) => T; type mockEntityRenderer = (entity: T) => T; @@ -462,3 +463,38 @@ export const MockAggregatedDailyCosts: DateAggregation[] = [ amount: 5500, }, ]; + +export const getGroupedProducts = (intervals: string) => [ + { + id: 'Cloud Dataflow', + aggregation: aggregationFor(intervals, 1_000), + }, + { + id: 'Compute Engine', + aggregation: aggregationFor(intervals, 100), + }, + { + id: 'Cloud Storage', + aggregation: aggregationFor(intervals, 500), + }, + { + id: 'BigQuery', + aggregation: aggregationFor(intervals, 2_000), + }, + { + id: 'Cloud SQL', + aggregation: aggregationFor(intervals, 10), + }, + { + id: 'Cloud Spanner', + aggregation: aggregationFor(intervals, 50), + }, + { + id: 'Cloud Pub/Sub', + aggregation: aggregationFor(intervals, 30), + }, + { + id: 'Cloud Bigtable', + aggregation: aggregationFor(intervals, 250), + }, +]; From da82e823a4241dc08ae515d93c899b5cc2a46283 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Mon, 23 Nov 2020 19:11:04 -0500 Subject: [PATCH 031/195] Add comments on groupedCosts --- plugins/cost-insights/src/client.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index c78a3dc2dc..96e9025f5d 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -140,6 +140,8 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + // Optional field on Cost which needs to be supplied in order to see + // the product breakdown view in the top panel. groupedCosts: getGroupedProducts(intervals), }, ); @@ -156,6 +158,8 @@ export class ExampleCostInsightsClient implements CostInsightsApi { aggregation: aggregation, change: changeOf(aggregation), trendline: trendlineOf(aggregation), + // Optional field on Cost which needs to be supplied in order to see + // the product breakdown view in the top panel. groupedCosts: getGroupedProducts(intervals), }, ); From f3bb55ee31ca217ccdaf1b9ced8dc5018e3952bf Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Tue, 20 Oct 2020 10:40:58 +0200 Subject: [PATCH 032/195] Make api-docs customizable --- .changeset/chilled-zoos-rush.md | 7 + .../app/src/components/catalog/EntityPage.tsx | 133 ++++++++++++------ plugins/api-docs/package.json | 2 +- .../ApiEntityPage/ApiEntityPage.test.tsx | 71 ---------- .../ApiEntityPage/ApiEntityPage.tsx | 127 ----------------- .../src/components/ApiEntityPage/index.ts | 17 --- .../ApiExplorerTable/ApiExplorerTable.tsx | 16 +-- plugins/api-docs/src/index.ts | 3 +- plugins/api-docs/src/plugin.ts | 4 +- plugins/api-docs/src/routes.ts | 6 - 10 files changed, 106 insertions(+), 280 deletions(-) create mode 100644 .changeset/chilled-zoos-rush.md delete mode 100644 plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx delete mode 100644 plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx delete mode 100644 plugins/api-docs/src/components/ApiEntityPage/index.ts diff --git a/.changeset/chilled-zoos-rush.md b/.changeset/chilled-zoos-rush.md new file mode 100644 index 0000000000..6830cb7fcf --- /dev/null +++ b/.changeset/chilled-zoos-rush.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-api-docs': minor +--- + +APIs now have real entity pages that are customizable in the app. +Therefore the old entity page from this plugin is removed. +See the `packages/app` on how to create and customize the API entity page. diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index c201fe88ad..d30de6abd1 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -13,63 +13,66 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { ApiEntity, Entity } from '@backstage/catalog-model'; +import { EmptyState } from '@backstage/core'; import { - isPluginApplicableToEntity as isTravisCIAvailable, - RecentTravisCIBuildsWidget, - Router as TravisCIRouter, -} from '@roadiehq/backstage-plugin-travis-ci'; + ApiDefinitionCard, + Router as ApiDocsRouter, +} from '@backstage/plugin-api-docs'; +import { + AboutCard, + EntityPageLayout, + useEntity, +} from '@backstage/plugin-catalog'; +import { + isPluginApplicableToEntity as isCircleCIAvailable, + Router as CircleCIRouter, +} from '@backstage/plugin-circleci'; +import { + isPluginApplicableToEntity as isCloudbuildAvailable, + Router as CloudbuildRouter, +} from '@backstage/plugin-cloudbuild'; import { isPluginApplicableToEntity as isGitHubActionsAvailable, RecentWorkflowRunsCard, Router as GitHubActionsRouter, } from '@backstage/plugin-github-actions'; import { - Router as CloudbuildRouter, - isPluginApplicableToEntity as isCloudbuildAvailable, -} from '@backstage/plugin-cloudbuild'; -import { - Router as JenkinsRouter, isPluginApplicableToEntity as isJenkinsAvailable, LatestRunCard as JenkinsLatestRunCard, + Router as JenkinsRouter, } from '@backstage/plugin-jenkins'; -import { - isPluginApplicableToEntity as isCircleCIAvailable, - Router as CircleCIRouter, -} from '@backstage/plugin-circleci'; -import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs'; -import { Router as SentryRouter } from '@backstage/plugin-sentry'; -import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes'; -import { - Router as GitHubInsightsRouter, - isPluginApplicableToEntity as isGitHubAvailable, - ReadMeCard, - LanguagesCard, - ReleasesCard, -} from '@roadiehq/backstage-plugin-github-insights'; -import React, { ReactNode } from 'react'; -import { - AboutCard, - EntityPageLayout, - useEntity, -} from '@backstage/plugin-catalog'; -import { Entity } from '@backstage/catalog-model'; -import { Button, Grid } from '@material-ui/core'; -import { EmptyState } from '@backstage/core'; import { EmbeddedRouter as LighthouseRouter, - LastLighthouseAuditCard, isPluginApplicableToEntity as isLighthouseAvailable, + LastLighthouseAuditCard, } from '@backstage/plugin-lighthouse/'; +import { Router as SentryRouter } from '@backstage/plugin-sentry'; +import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; +import { Button, Grid } from '@material-ui/core'; +import { + isPluginApplicableToEntity as isBuildkiteAvailable, + Router as BuildkiteRouter, +} from '@roadiehq/backstage-plugin-buildkite'; +import { + isPluginApplicableToEntity as isGitHubAvailable, + LanguagesCard, + ReadMeCard, + ReleasesCard, + Router as GitHubInsightsRouter, +} from '@roadiehq/backstage-plugin-github-insights'; import { - Router as PullRequestsRouter, isPluginApplicableToEntity as isPullRequestsAvailable, PullRequestsStatsCard, + Router as PullRequestsRouter, } from '@roadiehq/backstage-plugin-github-pull-requests'; import { - Router as BuildkiteRouter, - isPluginApplicableToEntity as isBuildkiteAvailable, -} from '@roadiehq/backstage-plugin-buildkite'; + isPluginApplicableToEntity as isTravisCIAvailable, + RecentTravisCIBuildsWidget, + Router as TravisCIRouter, +} from '@roadiehq/backstage-plugin-travis-ci'; +import React, { ReactNode } from 'react'; export const CICDSwitcher = ({ entity }: { entity: Entity }) => { // This component is just an example of how you can implement your company's logic in entity page. @@ -134,7 +137,7 @@ const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => { ); }; -const OverviewContent = ({ entity }: { entity: Entity }) => ( +const ComponentOverviewContent = ({ entity }: { entity: Entity }) => ( @@ -169,7 +172,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( } + element={} /> ( } + element={} /> ( /> ); + const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( } + element={} /> ( ); -export const EntityPage = () => { - const { entity } = useEntity(); +export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { switch (entity?.spec?.type) { case 'service': return ; @@ -279,3 +282,47 @@ export const EntityPage = () => { return ; } }; + +const ApiOverviewContent = ({ entity }: { entity: Entity }) => ( + + + + + +); + +const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => ( + + + + + +); + +const ApiEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + } + /> + +); + +export const EntityPage = () => { + const { entity } = useEntity(); + + switch (entity?.kind?.toLowerCase()) { + case 'component': + return ; + case 'api': + return ; + default: + return ; + } +}; diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index bfefaef3a5..e8127b7732 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -29,6 +29,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "@types/react": "^16.9", "graphiql": "^1.0.0-alpha.10", "graphql": "^15.3.0", "react": "^16.13.1", @@ -47,7 +48,6 @@ "@testing-library/user-event": "^12.0.7", "@types/jest": "^26.0.7", "@types/node": "^12.0.0", - "@types/react": "^16.9", "@types/swagger-ui-react": "^3.23.3", "cross-fetch": "^3.0.6", "msw": "^0.21.2" diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx deleted file mode 100644 index 9a2bc0cfb1..0000000000 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { ApiProvider, ApiRegistry, errorApiRef } from '@backstage/core'; -import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { render, waitFor } from '@testing-library/react'; -import * as React from 'react'; -import { ApiEntityPage } from './ApiEntityPage'; - -jest.mock('react-router-dom', () => { - const actual = jest.requireActual('react-router-dom'); - const mockNavigate = jest.fn(); - return { - ...actual, - useNavigate: jest.fn(() => mockNavigate), - useParams: jest.fn(), - }; -}); - -const { - useParams, - useNavigate, -}: { useParams: jest.Mock; useNavigate: () => jest.Mock } = jest.requireMock( - 'react-router-dom', -); - -const errorApi = { post: () => {} }; - -describe('ApiEntityPage', () => { - it('should redirect to catalog page when name is not provided', async () => { - useParams.mockReturnValue({ - kind: 'Component', - optionalNamespaceAndName: '', - }); - - render( - wrapInTestApp( - ) as CatalogApi, - ], - ])} - > - - , - ), - ); - - await waitFor(() => - expect(useNavigate()).toHaveBeenCalledWith('/api-docs'), - ); - }); -}); diff --git a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx b/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx deleted file mode 100644 index f755705ac7..0000000000 --- a/plugins/api-docs/src/components/ApiEntityPage/ApiEntityPage.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ApiEntity, Entity } from '@backstage/catalog-model'; -import { - Content, - errorApiRef, - Header, - Page, - Progress, - useApi, -} from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog'; -import { Box } from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; -import React, { useEffect } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import { useAsync } from 'react-use'; -import { ApiDefinitionCard } from '../ApiDefinitionCard'; - -const REDIRECT_DELAY = 1000; - -function headerProps( - kind: string, - namespace: string | undefined, - name: string, - entity: Entity | undefined, -): { headerTitle: string; headerType: string } { - return { - headerTitle: `${name}${namespace ? ` in ${namespace}` : ''}`, - headerType: (() => { - let t = kind.toLowerCase(); - if (entity && entity.spec && 'type' in entity.spec) { - t += ' — '; - t += (entity.spec as { type: string }).type.toLowerCase(); - } - return t; - })(), - }; -} - -type EntityPageTitleProps = { - title: string; - entity: Entity | undefined; -}; - -const EntityPageTitle = ({ title }: EntityPageTitleProps) => ( - - {title} - -); - -export const ApiEntityPage = () => { - const { optionalNamespaceAndName } = useParams() as { - optionalNamespaceAndName: string; - }; - const navigate = useNavigate(); - const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); - - const errorApi = useApi(errorApiRef); - const catalogApi = useApi(catalogApiRef); - - const { value: entity, error, loading } = useAsync( - () => catalogApi.getEntityByName({ kind: 'API', namespace, name }), - [catalogApi, namespace, name], - ); - - useEffect(() => { - if (!error && !loading && !entity) { - errorApi.post(new Error('Entity not found!')); - setTimeout(() => { - navigate('/'); - }, REDIRECT_DELAY); - } - }, [errorApi, navigate, error, loading, entity]); - - if (!name) { - navigate('/api-docs'); - return null; - } - - const { headerTitle, headerType } = headerProps( - 'API', - namespace, - name, - entity, - ); - - return ( - -
} - pageTitleOverride={headerTitle} - type={headerType} - /> - - {loading && } - - {error && ( - - {error.toString()} - - )} - - {entity && ( - <> - - - - - )} - - ); -}; diff --git a/plugins/api-docs/src/components/ApiEntityPage/index.ts b/plugins/api-docs/src/components/ApiEntityPage/index.ts deleted file mode 100644 index 561350744b..0000000000 --- a/plugins/api-docs/src/components/ApiEntityPage/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { ApiEntityPage } from './ApiEntityPage'; diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx index 9222623aed..f4a66f02f2 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.tsx @@ -23,12 +23,12 @@ import { useApi, useQueryParamState, } from '@backstage/core'; +import { entityRoute, entityRouteParams } from '@backstage/plugin-catalog'; import { Chip, Link } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { generatePath, Link as RouterLink } from 'react-router-dom'; import { apiDocsConfigRef } from '../../config'; -import { entityRoute } from '../../routes'; const ApiTypeTitle = ({ apiEntity }: { apiEntity: ApiEntityV1alpha1 }) => { const config = useApi(apiDocsConfigRef); @@ -46,16 +46,10 @@ const columns: TableColumn[] = [ render: (entity: any) => ( {entity.metadata.name} diff --git a/plugins/api-docs/src/index.ts b/plugins/api-docs/src/index.ts index 958355f063..dbb32cee7b 100644 --- a/plugins/api-docs/src/index.ts +++ b/plugins/api-docs/src/index.ts @@ -14,5 +14,6 @@ * limitations under the License. */ -export { Router } from './catalog'; +export * from './catalog'; +export * from './components'; export { plugin } from './plugin'; diff --git a/plugins/api-docs/src/plugin.ts b/plugins/api-docs/src/plugin.ts index 3fcbe8a1f4..06db03f2e1 100644 --- a/plugins/api-docs/src/plugin.ts +++ b/plugins/api-docs/src/plugin.ts @@ -18,8 +18,7 @@ import { ApiEntity } from '@backstage/catalog-model'; import { createApiFactory, createPlugin } from '@backstage/core'; import { ApiExplorerPage } from './components/ApiExplorerPage/ApiExplorerPage'; import { defaultDefinitionWidgets } from './components/ApiDefinitionCard'; -import { ApiEntityPage } from './components/ApiEntityPage/ApiEntityPage'; -import { entityRoute, rootRoute } from './routes'; +import { rootRoute } from './routes'; import { apiDocsConfigRef } from './config'; export const plugin = createPlugin({ @@ -40,6 +39,5 @@ export const plugin = createPlugin({ ], register({ router }) { router.addRoute(rootRoute, ApiExplorerPage); - router.addRoute(entityRoute, ApiEntityPage); }, }); diff --git a/plugins/api-docs/src/routes.ts b/plugins/api-docs/src/routes.ts index d2d8ef10c5..6adff78e47 100644 --- a/plugins/api-docs/src/routes.ts +++ b/plugins/api-docs/src/routes.ts @@ -24,12 +24,6 @@ export const rootRoute = createRouteRef({ title: 'APIs', }); -export const entityRoute = createRouteRef({ - icon: NoIcon, - path: '/api-docs/:optionalNamespaceAndName/', - title: 'API', -}); - export const catalogRoute = createRouteRef({ icon: NoIcon, path: '', From 5f1c1c2edf2b3f9c4463dc7d58afb1e8e7e843dd Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 13:34:44 -0800 Subject: [PATCH 033/195] Fixed the OIDC token refresh --- .../src/providers/oidc/provider.ts | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 5ebb859f2f..765fd8627e 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -33,10 +33,8 @@ import { OAuthRefreshRequest, } from '../../lib/oauth'; import { - executeFetchUserProfileStrategy, executeFrameHandlerStrategy, executeRedirectStrategy, - executeRefreshTokenStrategy, PassportDoneCallback, } from '../../lib/passport'; import { RedirectInfo, AuthProviderFactory, ProfileInfo } from '../types'; @@ -45,20 +43,25 @@ type PrivateInfo = { refreshToken: string; }; +type OidcImpl = { + strategy: OidcStrategy; + client: Client; +}; + export type OidcAuthProviderOptions = OAuthProviderOptions & { metadataUrl: string; tokenSignedResponseAlg?: string; }; export class OidcAuthProvider implements OAuthHandlers { - readonly _strategy: Promise>; + readonly _implementation: Promise; constructor(options: OidcAuthProviderOptions) { - this._strategy = this.setupStrategy(options); + this._implementation = this.setupStrategy(options); } async start(req: OAuthStartRequest): Promise { - const strategy = await this._strategy; + const { strategy } = await this._implementation; return await executeRedirectStrategy(req, strategy, { accessType: 'offline', prompt: 'none', @@ -70,7 +73,7 @@ export class OidcAuthProvider implements OAuthHandlers { async handler( req: express.Request, ): Promise<{ response: OAuthResponse; refreshToken: string }> { - const strategy = await this._strategy; + const { strategy } = await this._implementation; const { response, privateInfo } = await executeFrameHandlerStrategy< OAuthResponse, PrivateInfo @@ -83,31 +86,19 @@ export class OidcAuthProvider implements OAuthHandlers { } async refresh(req: OAuthRefreshRequest): Promise { - const strategy = await this._strategy; - const refreshTokenResponse = await executeRefreshTokenStrategy( - strategy, - req.refreshToken, - req.scope, - ); - const { - accessToken, - params, - refreshToken: updatedRefreshToken, - } = refreshTokenResponse; - - const profile = await executeFetchUserProfileStrategy( - strategy, - accessToken, - params.id_token, - ); + const { client } = await this._implementation; + const tokenset = await client.refresh(req.refreshToken); + if (!tokenset.access_token) { + throw new Error('Refresh failed'); + } + const profile = await client.userinfo(tokenset.access_token); return this.populateIdentity({ providerInfo: { - accessToken, - refreshToken: updatedRefreshToken, - idToken: params.id_token, - expiresInSeconds: params.expires_in, - scope: params.scope, + accessToken: tokenset.access_token, + refreshToken: tokenset.refresh_token, + expiresInSeconds: tokenset.expires_at, + scope: tokenset.scope || '', }, profile, }); @@ -115,7 +106,7 @@ export class OidcAuthProvider implements OAuthHandlers { private async setupStrategy( options: OidcAuthProviderOptions, - ): Promise> { + ): Promise { const issuer = await Issuer.discover(options.metadataUrl); const client = new issuer.Client({ client_id: options.clientId, @@ -159,7 +150,7 @@ export class OidcAuthProvider implements OAuthHandlers { }, ); strategy.error = console.error; - return strategy; + return { strategy, client }; } // Use this function to grab the user profile info from the token From 636cc58dca2ddd8c19ce25e128b682295f8071d8 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 13:41:37 -0800 Subject: [PATCH 034/195] Fixed a TS type error in the OIDC provider test --- plugins/auth-backend/src/providers/oidc/provider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index ade69255b7..5073cd88d1 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -54,7 +54,7 @@ describe('OidcAuthProvider', () => { .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); const provider = new OidcAuthProvider(clientMetadata); - const strategy = ((await provider._strategy) as any) as { + const strategy = ((await (provider as any)._strategy) as any) as { _client: ClientMetadata; _issuer: IssuerMetadata; }; From e3ad37e39d126a0c3dc9ccc8a22c061137ff0098 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 14:48:24 -0800 Subject: [PATCH 035/195] Fix broken tests --- .../src/providers/oidc/provider.test.ts | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 5073cd88d1..1851d1a473 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -22,7 +22,7 @@ import { createOidcProvider, OidcAuthProvider } from './provider'; import { JWT, JWK } from 'jose'; import { AuthProviderFactoryOptions } from '../types'; import { Config } from '@backstage/config'; -import { OAuthAdapter } from '../../lib/oauth'; +import { OAuthAdapter, OAuthStartRequest } from '../../lib/oauth'; const issuerMetadata = { issuer: 'https://oidc.test', @@ -54,9 +54,11 @@ describe('OidcAuthProvider', () => { .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); const provider = new OidcAuthProvider(clientMetadata); - const strategy = ((await (provider as any)._strategy) as any) as { - _client: ClientMetadata; - _issuer: IssuerMetadata; + const { strategy } = ((await provider._implementation) as any) as { + strategy: { + _client: ClientMetadata; + _issuer: IssuerMetadata; + }; }; // Assert that the expected request to the metadaurl was made. expect(scope.isDone()).toBeTruthy(); @@ -89,27 +91,38 @@ describe('OidcAuthProvider', () => { const provider = new OidcAuthProvider(clientMetadata); const req = { method: 'GET', - url: '/?code=test2', + url: 'https://oidc.test/?code=test2', session: ({ 'oidc:oidc.test': 'test' } as any) as Session, } as express.Request; await provider.handler(req); expect(scope.isDone()).toBeTruthy(); }); - const options = { - globalConfig: { - appUrl: 'https://oidc.test', - baseUrl: 'https://oidc.test', - }, - config: ({ - keys: jest.fn(() => ['test']), - getConfig: jest.fn(() => ({ getString: () => '' })), - } as any) as Config, - } as AuthProviderFactoryOptions; - - it('createOidcProvider', () => { + it('createOidcProvider', async () => { + const scope = nock('https://oidc.test') + .get('/.well-known/openid-configuration') + .reply(200, issuerMetadata); + const options = { + globalConfig: { + appUrl: 'https://oidc.test', + baseUrl: 'https://oidc.test', + }, + config: ({ + keys: jest.fn(() => ['test']), + getConfig: jest.fn(() => ({ + getString: (key: string) => { + const conf = { + ...clientMetadata, + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + } as any; + return conf[key] as string; + }, + })), + } as any) as Config, + } as AuthProviderFactoryOptions; const provider = createOidcProvider(options) as OAuthAdapter; - console.log(provider); expect(provider.start).toBeDefined(); + await new Promise(resolve => process.nextTick(resolve)); // advance a tick to give nock a chance to intercept the request + expect(scope.isDone()).toBeTruthy(); }); }); From 78177f24b7770e81ef414b64b29069606cac3ea8 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 14:54:24 -0800 Subject: [PATCH 036/195] Fixed a lint failure on the oidc provider test --- plugins/auth-backend/src/providers/oidc/provider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 1851d1a473..3732e7b1a7 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -22,7 +22,7 @@ import { createOidcProvider, OidcAuthProvider } from './provider'; import { JWT, JWK } from 'jose'; import { AuthProviderFactoryOptions } from '../types'; import { Config } from '@backstage/config'; -import { OAuthAdapter, OAuthStartRequest } from '../../lib/oauth'; +import { OAuthAdapter } from '../../lib/oauth'; const issuerMetadata = { issuer: 'https://oidc.test', From cec3da2c901998a71074936a62ed9383dd314bf6 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Mon, 23 Nov 2020 15:23:21 -0800 Subject: [PATCH 037/195] Used ts private instead of _ naming convention --- .../auth-backend/src/providers/oidc/provider.test.ts | 2 +- plugins/auth-backend/src/providers/oidc/provider.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 3732e7b1a7..f2bd8dcb90 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -54,7 +54,7 @@ describe('OidcAuthProvider', () => { .get('/.well-known/openid-configuration') .reply(200, issuerMetadata); const provider = new OidcAuthProvider(clientMetadata); - const { strategy } = ((await provider._implementation) as any) as { + const { strategy } = ((await (provider as any).implementation) as any) as { strategy: { _client: ClientMetadata; _issuer: IssuerMetadata; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 765fd8627e..4f2054c5f6 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -54,14 +54,14 @@ export type OidcAuthProviderOptions = OAuthProviderOptions & { }; export class OidcAuthProvider implements OAuthHandlers { - readonly _implementation: Promise; + private readonly implementation: Promise; constructor(options: OidcAuthProviderOptions) { - this._implementation = this.setupStrategy(options); + this.implementation = this.setupStrategy(options); } async start(req: OAuthStartRequest): Promise { - const { strategy } = await this._implementation; + const { strategy } = await this.implementation; return await executeRedirectStrategy(req, strategy, { accessType: 'offline', prompt: 'none', @@ -73,7 +73,7 @@ export class OidcAuthProvider implements OAuthHandlers { async handler( req: express.Request, ): Promise<{ response: OAuthResponse; refreshToken: string }> { - const { strategy } = await this._implementation; + const { strategy } = await this.implementation; const { response, privateInfo } = await executeFrameHandlerStrategy< OAuthResponse, PrivateInfo @@ -86,7 +86,7 @@ export class OidcAuthProvider implements OAuthHandlers { } async refresh(req: OAuthRefreshRequest): Promise { - const { client } = await this._implementation; + const { client } = await this.implementation; const tokenset = await client.refresh(req.refreshToken); if (!tokenset.access_token) { throw new Error('Refresh failed'); From 9c9d12e8993bd196165578abb083c883d29e3d96 Mon Sep 17 00:00:00 2001 From: Brian Leathem Date: Tue, 24 Nov 2020 11:30:25 -0800 Subject: [PATCH 038/195] Added back the id_token when calling refresh --- plugins/auth-backend/src/providers/oidc/provider.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index 4f2054c5f6..fbf38c69c4 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -97,7 +97,8 @@ export class OidcAuthProvider implements OAuthHandlers { providerInfo: { accessToken: tokenset.access_token, refreshToken: tokenset.refresh_token, - expiresInSeconds: tokenset.expires_at, + expiresInSeconds: tokenset.expires_in, + idToken: tokenset.id_token, scope: tokenset.scope || '', }, profile, From e338648910fc3348a7e9180845e95f32655b530b Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Tue, 24 Nov 2020 12:16:33 -0500 Subject: [PATCH 039/195] add a postMessage with targetOrigin * --- packages/core-api/src/lib/loginPopup.test.ts | 46 +++++++++++++++++++ packages/core-api/src/lib/loginPopup.ts | 15 +++++- .../src/lib/flow/authFlowHelpers.test.ts | 46 +++++++++++++++++++ .../src/lib/flow/authFlowHelpers.ts | 18 +++++++- 4 files changed, 122 insertions(+), 3 deletions(-) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index 55f1408e8d..6527618ef7 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -156,6 +156,15 @@ describe('showLoginPopup', () => { expect(addEventListenerSpy).toBeCalledTimes(1); expect(removeEventListenerSpy).toBeCalledTimes(0); + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + listener({ + source: popupMock, + origin: 'origin', + data: { + targetOrigin: 'http://localhost', + }, + } as MessageEvent); + setTimeout(() => { popupMock.closed = true; }, 150); @@ -167,4 +176,41 @@ describe('showLoginPopup', () => { expect(addEventListenerSpy).toBeCalledTimes(1); expect(removeEventListenerSpy).toBeCalledTimes(1); }); + + it('should indicate if origin does not match', async () => { + const openSpy = jest + .spyOn(window, 'open') + .mockReturnValue({ closed: false } as Window); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + const popupMock = { closed: false }; + + openSpy.mockReturnValue(popupMock as Window); + + const payloadPromise = showLoginPopup({ + url: 'url', + name: 'name', + origin: 'origin', + }); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + listener({ + source: popupMock, + origin: 'origin', + data: { + targetOrigin: 'http://differenthost', + }, + } as MessageEvent); + + setTimeout(() => { + popupMock.closed = true; + }, 150); + await expect(payloadPromise).rejects.toThrow( + 'Login failed, incorrect app origin', + ); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(1); + }); }); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 10d6efd7a4..5c6d424363 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -79,6 +79,8 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { `menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`, ); + let targetOrigin = ''; + if (!popup || typeof popup.closed === 'undefined' || popup.closed) { reject(new Error('Failed to open auth popup.')); return; @@ -92,6 +94,12 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { return; } const { data } = event; + + if (data.targetOrigin) { + targetOrigin = data.targetOrigin; + return; + } + if (data.type !== 'authorization_response') { return; } @@ -111,7 +119,12 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { const intervalId = setInterval(() => { if (popup.closed) { - const error = new Error('Login failed, popup was closed'); + const errMessage = `Login failed, ${ + targetOrigin !== window.location.origin + ? 'incorrect app origin' + : 'popup was closed' + }`; + const error = new Error(errMessage); error.name = 'PopupClosedError'; reject(error); done(); diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts index 6c49eaf351..5e4e5f875b 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts @@ -81,6 +81,52 @@ describe('oauth helpers', () => { expect(mockResponse.end).toBeCalledWith(expect.stringContaining(encoded)); }); + it('should call postMessage twice but only one of them with target *', () => { + let responseBody: String; + + const mockResponse = ({ + end: jest.fn(function (body) { + responseBody = body; + return this; + }), + setHeader: jest.fn().mockReturnThis(), + } as unknown) as express.Response; + + const data: WebMessageResponse = { + type: 'authorization_response', + response: { + providerInfo: { + accessToken: 'ACCESS_TOKEN', + idToken: 'ID_TOKEN', + expiresInSeconds: 10, + scope: 'email', + }, + profile: { + email: 'foo@bar.com', + }, + backstageIdentity: { + id: 'a', + idToken: 'a.b.c', + }, + }, + }; + postMessageResponse(mockResponse, appOrigin, data); + expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2); + expect( + responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g), + ).toHaveLength(1); + + const errData: WebMessageResponse = { + type: 'authorization_response', + error: new Error('Unknown error occurred'), + }; + postMessageResponse(mockResponse, appOrigin, errData); + expect(responseBody.match(/.postMessage\(/g)).toHaveLength(2); + expect( + responseBody.match(/.postMessage\([a-zA-z.()]*, \'\*\'\)/g), + ).toHaveLength(1); + }); + it('handles single quotes and unicode chars safely', () => { const mockResponse = ({ end: jest.fn().mockReturnThis(), diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts index d2a9557a28..5aba427905 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts @@ -38,10 +38,24 @@ export const postMessageResponse = ( // data. // TODO: Make target app origin configurable globally + + // + // postMessage fails silently if the targetOrigin is disallowed. + // So 2 postMessages are sent from the popup to the parent window. + // First, the origin being used to post the actual authorization response is + // shared with the parent window with a postMessage with targetOrigin '*'. + // Second, the actual authorization response is sent with the app origin + // as the targetOrigin. + // If the first message was received but the actual auth response was + // never received, the event listener can conclude that targetOrigin + // was disallowed, indicating potential misconfiguration. + // const script = ` - var json = decodeURIComponent('${base64Data}'); + var authResponse = decodeURIComponent('${base64Data}'); var origin = decodeURIComponent('${base64Origin}'); - (window.opener || window.parent).postMessage(JSON.parse(json), origin); + var originInfo = {'targetOrigin': origin}; + (window.opener || window.parent).postMessage(originInfo, '*'); + (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin); window.close(); `; const hash = crypto.createHash('sha256').update(script).digest('base64'); From 86ab16244a01b0183648c9e818179836e1f1c45e Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 16:55:41 -0500 Subject: [PATCH 040/195] Update wording to product in cost by product component --- ...uct.tsx => CostOverviewByProductChart.tsx} | 161 +++++++++--------- 1 file changed, 82 insertions(+), 79 deletions(-) rename plugins/cost-insights/src/components/CostOverviewCard/{CostOverviewByProduct.tsx => CostOverviewByProductChart.tsx} (63%) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx similarity index 63% rename from plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx rename to plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 1496fcdaf8..710c085b48 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProduct.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -32,6 +32,7 @@ import { Cost, DEFAULT_DATE_FORMAT, CostInsightsTheme } from '../../types'; import { BarChartTooltip as Tooltip, BarChartTooltipItem as TooltipItem, + BarChartLegend, } from '../BarChart'; import { overviewGraphTickFormatter, @@ -45,68 +46,100 @@ import { import { useFilters, useLastCompleteBillingDate } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { getPreviousPeriodTotalCost } from '../../utils/change'; -import { LegendItem } from '../LegendItem'; -import { currencyFormatter } from '../../utils/formatters'; +import { formatPeriod } from '../../utils/formatters'; import { aggregationSum } from '../../utils/sum'; +import { BarChartLegendOptions } from '../BarChart/BarChartLegend'; dayjs.extend(utc); -export type CostOverviewByProductProps = { - dailyCostData: Cost; +export type CostOverviewByProductChartProps = { + costsByProduct: Cost[]; }; -const LOW_COST_THRESHOLD = 0.01; +const LOW_COST_THRESHOLD = 0.1; -export const CostOverviewByProduct = ({ - dailyCostData, -}: CostOverviewByProductProps) => { +export const CostOverviewByProductChart = ({ + costsByProduct, +}: CostOverviewByProductChartProps) => { const theme = useTheme(); const styles = useStyles(theme); const lastCompleteBillingDate = useLastCompleteBillingDate(); const { duration } = useFilters(mapFiltersToProps); - const groupedCosts = dailyCostData.groupedCosts; - if (!groupedCosts) { + if (!costsByProduct) { return null; } - const flattenedAggregation = groupedCosts + const flattenedAggregation = costsByProduct .map(cost => cost.aggregation) .flat(); - const totalCost = flattenedAggregation.reduce( - (acc, agg) => acc + agg.amount, - 0, - ); - const productsByDate = groupedCosts.reduce((prodByDate, group) => { - const product = group.id; - group.aggregation.forEach(curAggregation => { - const productCostsForDate = prodByDate[curAggregation.date] || {}; - // Group products with less than 10% of the total cost into "Other" category - if (aggregationSum(group.aggregation) < totalCost * LOW_COST_THRESHOLD) { - prodByDate[curAggregation.date] = { - ...productCostsForDate, - Other: - (prodByDate[curAggregation.date].Other || 0) + - curAggregation.amount, - }; - } else { - prodByDate[curAggregation.date] = { - ...productCostsForDate, - [product]: curAggregation.amount, - }; - } + const totalCost = aggregationSum(flattenedAggregation); + + const previousPeriodTotal = getPreviousPeriodTotalCost( + flattenedAggregation, + duration, + lastCompleteBillingDate, + ); + const currentPeriodTotal = totalCost - previousPeriodTotal; + const productsByDate = costsByProduct.reduce((prodByDate, product) => { + const productTotal = aggregationSum(product.aggregation); + // Group products with less than 10% of the total cost into "Other" category + // when there we have >= 5 products. + const isOtherProduct = + costsByProduct.length >= 5 && + productTotal < totalCost * LOW_COST_THRESHOLD; + const productName = isOtherProduct ? 'Other' : product.id; + const updatedProdByDate = { ...prodByDate }; + + product.aggregation.forEach(curAggregation => { + const productCostsForDate = updatedProdByDate[curAggregation.date] || {}; + + updatedProdByDate[curAggregation.date] = { + ...productCostsForDate, + [productName]: + (productCostsForDate[productName] || 0) + curAggregation.amount, + }; }); - return prodByDate; + return updatedProdByDate; }, {} as Record>); - const chartData: any[] = Object.keys(productsByDate).map(date => { - return { - ...productsByDate[date], - date: Date.parse(date), - }; - }); + const chartData: Record[] = Object.keys(productsByDate).map( + date => { + return { + ...productsByDate[date], + date: Date.parse(date), + }; + }, + ); + + const renderAreas = () => { + const productGroupNames = new Set( + Object.values(productsByDate) + .map(d => Object.keys(d)) + .flat(), + ); + const sortedProducts = costsByProduct + // Check that product is a separate group and hasn't been added to 'Other' + .filter( + product => product.id !== 'Other' && productGroupNames.has(product.id), + ) + .sort( + (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), + ) + .map(product => product.id); + // Keep 'Other' category at the bottom of the stack + return ['Other', ...sortedProducts].map((product, i) => ( + + )); + }; const tooltipRenderer: ContentRenderer = ({ label, @@ -130,53 +163,24 @@ export const CostOverviewByProduct = ({ ); }; - const previousPeriodTotal = getPreviousPeriodTotalCost( - flattenedAggregation, - duration, - lastCompleteBillingDate, - ); - const currentPeriodTotal = totalCost - previousPeriodTotal; - - const renderAreas = () => { - const highCostGroups = groupedCosts.filter( - group => - aggregationSum(group.aggregation) >= totalCost * LOW_COST_THRESHOLD, - ); - const sortedCostGroups = highCostGroups - .sort( - (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), - ) - .map(group => group.id); - // Keep 'Other' category at the bottom of the stack - return ['Other', ...sortedCostGroups].map((group, i) => ( - - )); + const options: Partial = { + previousName: formatPeriod(duration, lastCompleteBillingDate, false), + currentName: formatPeriod(duration, lastCompleteBillingDate, true), + hideMarker: true, }; return ( - - - {currencyFormatter.format(previousPeriodTotal)} - - - - - {currencyFormatter.format(currentPeriodTotal)} - - + {renderAreas()} - From bf9d87dc16336cfe83f1c0855f46239ff026557d Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 16:56:20 -0500 Subject: [PATCH 041/195] Move cost overview chart legend to separate component --- .../CostOverviewCard/CostOverviewCard.tsx | 75 +++-------- .../CostOverviewCard/CostOverviewChart.tsx | 120 ++++++++++-------- .../CostOverviewCard/CostOverviewLegend.tsx | 84 ++++++++++++ 3 files changed, 163 insertions(+), 116 deletions(-) create mode 100644 plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index 742b89af7a..c385d5d424 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -24,24 +24,15 @@ import { Tab, Tabs, } from '@material-ui/core'; -import { CostGrowth } from '../CostGrowth'; import { CostOverviewChart } from './CostOverviewChart'; -import { CostOverviewByProduct } from './CostOverviewByProduct'; +import { CostOverviewByProductChart } from './CostOverviewByProductChart'; import { CostOverviewHeader } from './CostOverviewHeader'; -import { LegendItem } from '../LegendItem'; import { MetricSelect } from '../MetricSelect'; import { PeriodSelect } from '../PeriodSelect'; -import { - useScroll, - useFilters, - useConfig, - useLastCompleteBillingDate, -} from '../../hooks'; +import { useScroll, useFilters, useConfig } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { DefaultNavigation } from '../../utils/navigation'; -import { formatPercent } from '../../utils/formatters'; import { findAlways } from '../../utils/assert'; -import { getComparedChange } from '../../utils/change'; import { Cost, CostInsightsTheme, MetricData } from '../../types'; import { useOverviewTabsStyles } from '../../utils/styles'; @@ -56,7 +47,6 @@ export const CostOverviewCard = ({ }: CostOverviewCardProps) => { const theme = useTheme(); const config = useConfig(); - const lastCompleteBillingDate = useLastCompleteBillingDate(); const [tabIndex, setTabIndex] = useState(0); const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard); @@ -67,19 +57,11 @@ export const CostOverviewCard = ({ const metric = filters.metric ? findAlways(config.metrics, m => m.kind === filters.metric) : null; - const comparedChange = metricData - ? getComparedChange( - dailyCostData, - metricData, - filters.duration, - lastCompleteBillingDate, - ) - : null; const styles = useOverviewTabsStyles(theme); const tabs = [ - { id: 'overview', label: 'OVERVIEW' }, - { id: 'breakdown', label: 'BREAKDOWN BY PRODUCT' }, + { id: 'overview', label: 'Total cost' }, + { id: 'breakdown', label: 'Breakdown by product' }, ]; const OverviewTabs = () => { @@ -104,34 +86,8 @@ export const CostOverviewCard = ({ ); }; - const OverviewLegend = () => { - return ( - - - - {formatPercent(dailyCostData.change!.ratio)} - - - {metric && metricData && comparedChange && ( - <> - - - {formatPercent(metricData.change.ratio)} - - - - - - - )} - - ); - }; + // Metrics can only be selected on the total cost graph + const showMetricSelect = config.metrics.length && tabIndex === 0; return ( @@ -146,20 +102,19 @@ export const CostOverviewCard = ({ {tabIndex === 0 ? ( - <> - - - + ) : ( - + )} - {config.metrics.length && tabIndex === 0 && ( + {showMetricSelect && ( - - - - 0, 'dataMax']} - tick={{ fill: styles.axis.fill }} - tickFormatter={formatGraphValue} - width={styles.yAxis.width} - yAxisId={data.dailyCost.dataKey} - /> - {metric && ( - 0, toDataMax(data.metric.dataKey, chartData)]} - width={styles.yAxis.width} - yAxisId={data.metric.dataKey} + + + + + + + 0, 'dataMax']} + tick={{ fill: styles.axis.fill }} + tickFormatter={formatGraphValue} + width={styles.yAxis.width} + yAxisId={data.dailyCost.dataKey} + /> + {metric && ( + 0, toDataMax(data.metric.dataKey, chartData)]} + width={styles.yAxis.width} + yAxisId={data.metric.dataKey} + /> + )} + - )} - - - {metric && ( - )} - - - + {metric && ( + + )} + + + + ); }; diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx new file mode 100644 index 0000000000..ebba5173a9 --- /dev/null +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewLegend.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { PropsWithChildren } from 'react'; +import { Box, useTheme } from '@material-ui/core'; +import { LegendItem } from '../LegendItem'; +import { + CostInsightsTheme, + MetricData, + Maybe, + Cost, + Metric, +} from '../../types'; +import { useLastCompleteBillingDate, useFilters } from '../../hooks'; +import { getComparedChange } from '../../utils/change'; +import { mapFiltersToProps } from './selector'; +import { formatPercent } from '../../utils/formatters'; +import { CostGrowth } from '../CostGrowth'; + +type CostOverviewLegendProps = { + metric: Maybe; + metricData: Maybe; + dailyCostData: Cost; +}; + +export const CostOverviewLegend = ({ + dailyCostData, + metric, + metricData, +}: PropsWithChildren) => { + const theme = useTheme(); + + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const { duration } = useFilters(mapFiltersToProps); + + const comparedChange = metricData + ? getComparedChange( + dailyCostData, + metricData, + duration, + lastCompleteBillingDate, + ) + : null; + + return ( + + + + {formatPercent(dailyCostData.change!.ratio)} + + + {metric && metricData && comparedChange && ( + <> + + + {formatPercent(metricData.change.ratio)} + + + + + + + )} + + ); +}; From 62f1c825cf2fd0630b160e876bb412ae8c0e9a50 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 16:56:43 -0500 Subject: [PATCH 042/195] Move mock data utils --- plugins/cost-insights/src/client.ts | 54 +++---------------- .../components/BarChart/BarChartLegend.tsx | 13 +++-- plugins/cost-insights/src/utils/styles.ts | 5 +- 3 files changed, 19 insertions(+), 53 deletions(-) diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index 96e9025f5d..a20344ffb3 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -20,9 +20,7 @@ import { CostInsightsApi, ProductInsightsOptions } from '../src/api'; import { Alert, Cost, - DateAggregation, DEFAULT_DATE_FORMAT, - Duration, Entity, Group, MetricData, @@ -34,52 +32,12 @@ import { ProjectGrowthAlert, UnlabeledDataflowAlert, } from '../src/utils/alerts'; -import { inclusiveStartDateOf } from '../src/utils/duration'; -import { trendlineOf, changeOf, getGroupedProducts } from './utils/mockData'; - -type IntervalFields = { - duration: Duration; - endDate: string; -}; - -function parseIntervals(intervals: string): IntervalFields { - const match = intervals.match( - /\/(?P\d+[DM])\/(?\d{4}-\d{2}-\d{2})/, - ); - if (Object.keys(match?.groups || {}).length !== 2) { - throw new Error(`Invalid intervals: ${intervals}`); - } - const { duration, date } = match!.groups!; - return { - duration: duration as Duration, - endDate: date, - }; -} - -export function aggregationFor( - intervals: string, - baseline: number, -): DateAggregation[] { - const { duration, endDate } = parseIntervals(intervals); - const days = dayjs(endDate).diff( - inclusiveStartDateOf(duration, endDate), - 'day', - ); - - return [...Array(days).keys()].reduce( - (values: DateAggregation[], i: number): DateAggregation[] => { - const last = values.length ? values[values.length - 1].amount : baseline; - values.push({ - date: dayjs(inclusiveStartDateOf(duration, endDate)) - .add(i, 'day') - .format(DEFAULT_DATE_FORMAT), - amount: Math.max(0, last + (baseline / 20) * (Math.random() * 2 - 1)), - }); - return values; - }, - [], - ); -} +import { + trendlineOf, + changeOf, + getGroupedProducts, + aggregationFor, +} from './utils/mockData'; export class ExampleCostInsightsClient implements CostInsightsApi { private request(_: any, res: any): Promise { diff --git a/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx b/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx index 6db0e13572..7ed6854d35 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartLegend.tsx @@ -21,11 +21,12 @@ import { currencyFormatter } from '../../utils/formatters'; import { CostInsightsTheme } from '../../types'; import { useBarChartLayoutStyles as useStyles } from '../../utils/styles'; -type BarChartLegendOptions = { +export type BarChartLegendOptions = { previousName: string; previousFill: string; currentName: string; currentFill: string; + hideMarker?: boolean; }; export type BarChartLegendProps = { @@ -56,12 +57,18 @@ export const BarChartLegend = ({ return ( - + {currencyFormatter.format(costStart)} - + {currencyFormatter.format(costEnd)} diff --git a/plugins/cost-insights/src/utils/styles.ts b/plugins/cost-insights/src/utils/styles.ts index f149ac9be7..b0378b6c98 100644 --- a/plugins/cost-insights/src/utils/styles.ts +++ b/plugins/cost-insights/src/utils/styles.ts @@ -103,9 +103,10 @@ export const useCostOverviewStyles = (theme: CostInsightsTheme) => ({ export const useOverviewTabsStyles = makeStyles( (theme: CostInsightsTheme) => ({ default: { - padding: theme.spacing(2, 2), - fontWeight: 'bold', + padding: theme.spacing(2), + fontWeight: theme.typography.fontWeightBold, color: theme.palette.text.secondary, + textTransform: 'uppercase', }, selected: { color: theme.palette.text.primary, From 3c66f864b2f10695708a3e4ccebcd5e91d3e4351 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Tue, 24 Nov 2020 17:12:22 -0500 Subject: [PATCH 043/195] changeset for bugfix issue 3223 --- .changeset/auth-backend-ten-doors-exist.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/auth-backend-ten-doors-exist.md diff --git a/.changeset/auth-backend-ten-doors-exist.md b/.changeset/auth-backend-ten-doors-exist.md new file mode 100644 index 0000000000..97a3a421ee --- /dev/null +++ b/.changeset/auth-backend-ten-doors-exist.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-api': patch +'@backstage/plugin-auth-backend': patch +--- + +bugfix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure From 7e49365080205c97e4937b9109bf681270c029e0 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Tue, 24 Nov 2020 17:24:03 -0500 Subject: [PATCH 044/195] fix ci --- .changeset/auth-backend-ten-doors-exist.md | 2 +- plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/auth-backend-ten-doors-exist.md b/.changeset/auth-backend-ten-doors-exist.md index 97a3a421ee..2dafaf728d 100644 --- a/.changeset/auth-backend-ten-doors-exist.md +++ b/.changeset/auth-backend-ten-doors-exist.md @@ -3,4 +3,4 @@ '@backstage/plugin-auth-backend': patch --- -bugfix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure +bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts index 5e4e5f875b..10fb82eb36 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.test.ts @@ -82,10 +82,10 @@ describe('oauth helpers', () => { }); it('should call postMessage twice but only one of them with target *', () => { - let responseBody: String; + let responseBody = ''; const mockResponse = ({ - end: jest.fn(function (body) { + end: jest.fn(body => { responseBody = body; return this; }), From 1b5e8fbae61dff3e0b6ced8f8b103242b1c7ef4e Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 17:48:01 -0500 Subject: [PATCH 045/195] Add data viz colors for both themes --- .../CostOverviewByProductChart.tsx | 9 ++--- .../CostOverviewCard/CostOverviewCard.tsx | 12 +++--- plugins/cost-insights/src/types/Theme.ts | 1 + plugins/cost-insights/src/utils/styles.ts | 37 ++++++++++++------- 4 files changed, 35 insertions(+), 24 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 710c085b48..1a466fb4d7 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -39,10 +39,7 @@ import { formatGraphValue, isInvalid, } from '../../utils/graphs'; -import { - useCostOverviewStyles as useStyles, - DataVizColors, -} from '../../utils/styles'; +import { useCostOverviewStyles as useStyles } from '../../utils/styles'; import { useFilters, useLastCompleteBillingDate } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { getPreviousPeriodTotalCost } from '../../utils/change'; @@ -135,8 +132,8 @@ export const CostOverviewByProductChart = ({ dataKey={product} stackId="1" fillOpacity="1" - stroke={DataVizColors[i]} - fill={DataVizColors[i]} + stroke={theme.palette.dataViz[i]} + fill={theme.palette.dataViz[i]} /> )); }; diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index c385d5d424..39adc4bbf1 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -60,8 +60,12 @@ export const CostOverviewCard = ({ const styles = useOverviewTabsStyles(theme); const tabs = [ - { id: 'overview', label: 'Total cost' }, - { id: 'breakdown', label: 'Breakdown by product' }, + { id: 'overview', label: 'Total cost', title: 'Cloud Cost' }, + { + id: 'breakdown', + label: 'Breakdown by product', + title: 'Cloud Cost By Product', + }, ]; const OverviewTabs = () => { @@ -94,9 +98,7 @@ export const CostOverviewCard = ({ {dailyCostData.groupedCosts && } - + diff --git a/plugins/cost-insights/src/types/Theme.ts b/plugins/cost-insights/src/types/Theme.ts index 9053283351..0f4096adc3 100644 --- a/plugins/cost-insights/src/types/Theme.ts +++ b/plugins/cost-insights/src/types/Theme.ts @@ -30,6 +30,7 @@ type CostInsightsPaletteAdditions = { tooltip: CostInsightsTooltipOptions; navigationText: string; alertBackground: string; + dataViz: string[]; }; export type CostInsightsPalette = BackstagePalette & diff --git a/plugins/cost-insights/src/utils/styles.ts b/plugins/cost-insights/src/utils/styles.ts index b0378b6c98..f318f347f9 100644 --- a/plugins/cost-insights/src/utils/styles.ts +++ b/plugins/cost-insights/src/utils/styles.ts @@ -24,19 +24,6 @@ import { import { BackstageTheme } from '@backstage/theme'; import { CostInsightsTheme, CostInsightsThemeOptions } from '../types'; -export const DataVizColors = [ - '#509BF5', - '#FF6437', - '#4B917D', - '#F573A0', - '#F59B23', - '#B49BC8', - '#C39687', - '#A0C3D2', - '#FFC864', - '#BABABA', -]; - export const costInsightsLightTheme = { palette: { blue: '#509AF5', @@ -50,6 +37,18 @@ export const costInsightsLightTheme = { }, navigationText: '#b5b5b5', alertBackground: 'rgba(219, 219, 219, 0.13)', + dataViz: [ + '#509BF5', + '#FF6437', + '#4B917D', + '#F573A0', + '#F59B23', + '#B49BC8', + '#C39687', + '#A0C3D2', + '#FFC864', + '#BABABA', + ], }, } as CostInsightsThemeOptions; @@ -68,6 +67,18 @@ export const costInsightsDarkTheme = { }, navigationText: '#b5b5b5', alertBackground: 'rgba(32, 32, 32, 0.13)', + dataViz: [ + '#B9D6FB', + '#FFC1AF', + '#B7D3CB', + '#FBC7D9', + '#FBD6A7', + '#E1D7E9', + '#E7D5CF', + '#D9E7ED', + '#FFE9C1', + '#E3E3E3', + ], }, } as CostInsightsThemeOptions; From fa5ce266dbbfd98011e967e5202695a8ca080af9 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 17:49:49 -0500 Subject: [PATCH 046/195] Move mock data utils --- plugins/cost-insights/src/utils/mockData.ts | 58 ++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 4432753c32..cf9f13daa7 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import dayjs from 'dayjs'; import regression, { DataPoint } from 'regression'; import { Config } from '@backstage/config'; import { ConfigApi } from '@backstage/core'; @@ -28,17 +29,23 @@ import { UnlabeledDataflowAlertProject, UnlabeledDataflowData, DateAggregation, + DEFAULT_DATE_FORMAT, } from '../types'; import { DefaultLoadingAction, getDefaultState as getDefaultLoadingState, } from '../utils/loading'; import { findAlways } from '../utils/assert'; -import { aggregationFor } from '../client'; +import { inclusiveStartDateOf } from './duration'; type mockAlertRenderer = (alert: T) => T; type mockEntityRenderer = (entity: T) => T; +type IntervalFields = { + duration: Duration; + endDate: string; +}; + export const createMockEntity = ( callback?: mockEntityRenderer, ): Entity => { @@ -217,6 +224,45 @@ export function changeOf(aggregation: DateAggregation[]): ChangeStatistic { }; } +export function aggregationFor( + intervals: string, + baseline: number, +): DateAggregation[] { + const { duration, endDate } = parseIntervals(intervals); + const days = dayjs(endDate).diff( + inclusiveStartDateOf(duration, endDate), + 'day', + ); + + return [...Array(days).keys()].reduce( + (values: DateAggregation[], i: number): DateAggregation[] => { + const last = values.length ? values[values.length - 1].amount : baseline; + values.push({ + date: dayjs(inclusiveStartDateOf(duration, endDate)) + .add(i, 'day') + .format(DEFAULT_DATE_FORMAT), + amount: Math.max(0, last + (baseline / 20) * (Math.random() * 2 - 1)), + }); + return values; + }, + [], + ); +} + +function parseIntervals(intervals: string): IntervalFields { + const match = intervals.match( + /\/(?P\d+[DM])\/(?\d{4}-\d{2}-\d{2})/, + ); + if (Object.keys(match?.groups || {}).length !== 2) { + throw new Error(`Invalid intervals: ${intervals}`); + } + const { duration, date } = match!.groups!; + return { + duration: duration as Duration, + endDate: date, + }; +} + export const MockAggregatedDailyCosts: DateAggregation[] = [ { date: '2020-08-07', @@ -467,15 +513,15 @@ export const MockAggregatedDailyCosts: DateAggregation[] = [ export const getGroupedProducts = (intervals: string) => [ { id: 'Cloud Dataflow', - aggregation: aggregationFor(intervals, 1_000), + aggregation: aggregationFor(intervals, 1_700), }, { id: 'Compute Engine', - aggregation: aggregationFor(intervals, 100), + aggregation: aggregationFor(intervals, 350), }, { id: 'Cloud Storage', - aggregation: aggregationFor(intervals, 500), + aggregation: aggregationFor(intervals, 1_300), }, { id: 'BigQuery', @@ -483,7 +529,7 @@ export const getGroupedProducts = (intervals: string) => [ }, { id: 'Cloud SQL', - aggregation: aggregationFor(intervals, 10), + aggregation: aggregationFor(intervals, 750), }, { id: 'Cloud Spanner', @@ -491,7 +537,7 @@ export const getGroupedProducts = (intervals: string) => [ }, { id: 'Cloud Pub/Sub', - aggregation: aggregationFor(intervals, 30), + aggregation: aggregationFor(intervals, 1_000), }, { id: 'Cloud Bigtable', From 8972be02324e8d9546ae104a6b1033d6b5e536c6 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 18:08:50 -0500 Subject: [PATCH 047/195] Update data viz dark theme colors --- .../CostOverviewByProductChart.tsx | 4 ++-- plugins/cost-insights/src/utils/styles.ts | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx index 1a466fb4d7..6cc576c04f 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewByProductChart.tsx @@ -132,8 +132,8 @@ export const CostOverviewByProductChart = ({ dataKey={product} stackId="1" fillOpacity="1" - stroke={theme.palette.dataViz[i]} - fill={theme.palette.dataViz[i]} + stroke={theme.palette.dataViz[sortedProducts.length - i]} + fill={theme.palette.dataViz[sortedProducts.length - i]} /> )); }; diff --git a/plugins/cost-insights/src/utils/styles.ts b/plugins/cost-insights/src/utils/styles.ts index f318f347f9..3f762491e1 100644 --- a/plugins/cost-insights/src/utils/styles.ts +++ b/plugins/cost-insights/src/utils/styles.ts @@ -39,8 +39,8 @@ export const costInsightsLightTheme = { alertBackground: 'rgba(219, 219, 219, 0.13)', dataViz: [ '#509BF5', - '#FF6437', '#4B917D', + '#FF6437', '#F573A0', '#F59B23', '#B49BC8', @@ -68,16 +68,16 @@ export const costInsightsDarkTheme = { navigationText: '#b5b5b5', alertBackground: 'rgba(32, 32, 32, 0.13)', dataViz: [ - '#B9D6FB', - '#FFC1AF', - '#B7D3CB', - '#FBC7D9', - '#FBD6A7', - '#E1D7E9', - '#E7D5CF', - '#D9E7ED', - '#FFE9C1', - '#E3E3E3', + '#c1dffd', + '#baddd5', + '#ff9664', + '#ffa5d1', + '#ffcc57', + '#e6ccfb', + '#f7c7b7', + '#d2f6ff', + '#fffb94', + '#ececec', ], }, } as CostInsightsThemeOptions; From 2739de3fdc308e153043bbc1c656c3e537038c5a Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Tue, 24 Nov 2020 18:16:13 -0500 Subject: [PATCH 048/195] stack rank product panels --- plugins/cost-insights/src/client.ts | 6 +- .../ProductInsights/ProductInsights.tsx | 8 +- .../ProductInsightsCard.test.tsx | 43 ++-- .../ProductInsightsCard.tsx | 156 +++++++-------- .../ProductInsightsCardList.test.tsx | 156 +++++++++++++++ .../ProductInsightsCardList.tsx | 188 ++++++++++++++++++ .../ProductInsightsErrorCard.tsx | 50 +++++ .../components/ProductInsightsCard/index.ts | 2 +- .../cost-insights/src/hooks/useLoading.tsx | 9 +- plugins/cost-insights/src/utils/loading.ts | 10 +- plugins/cost-insights/src/utils/mockData.ts | 4 +- 11 files changed, 499 insertions(+), 133 deletions(-) create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx create mode 100644 plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx diff --git a/plugins/cost-insights/src/client.ts b/plugins/cost-insights/src/client.ts index 2180385424..db999584a0 100644 --- a/plugins/cost-insights/src/client.ts +++ b/plugins/cost-insights/src/client.ts @@ -332,14 +332,14 @@ export class ExampleCostInsightsClient implements CostInsightsApi { project: 'example-project', periodStart: '2020-Q2', periodEnd: '2020-Q3', - aggregation: [60_000, 120_000], + aggregation: [100, 120_000], change: { - ratio: 1, + ratio: 10.123123, amount: 60000, }, products: [ { id: 'Compute Engine', aggregation: [58_000, 118_000] }, - { id: 'Cloud Dataflow', aggregation: [1200, 1500] }, + { id: 'Cloud Dataflow', aggregation: [0, 15_000] }, { id: 'Cloud Storage', aggregation: [800, 500] }, ], }; diff --git a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx index c5d39a5139..affef5d182 100644 --- a/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx +++ b/plugins/cost-insights/src/components/ProductInsights/ProductInsights.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { Box, Typography, Grid } from '@material-ui/core'; -import { ProductInsightsCard } from '../ProductInsightsCard'; +import { ProductInsightsCardList } from '../ProductInsightsCard'; import { useConfig } from '../../hooks'; export const ProductInsights = ({}) => { @@ -30,11 +30,7 @@ export const ProductInsights = ({}) => { - {config.products.map(product => ( - - - - ))} + ); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx index cdbbb79806..385ff82f9b 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx @@ -22,19 +22,19 @@ import { createMockEntity, mockDefaultLoadingState, MockComputeEngine, - MockProductFilters, } from '../../utils/mockData'; import { - MockCostInsightsApiProvider, - MockBillingDateProvider, MockConfigProvider, + MockCostInsightsApiProvider, MockCurrencyProvider, - MockFilterProvider, - MockGroupsProvider, + MockBillingDateProvider, MockScrollProvider, MockLoadingProvider, } from '../../utils/tests'; -import { Duration, Entity, Product, ProductPeriod } from '../../types'; +import { Duration, Entity, Product } from '../../types'; + +// suppress recharts componentDidUpdate warnings +jest.spyOn(console, 'warn').mockImplementation(() => {}); const costInsightsApi = (entity: Entity): Partial => ({ getProductInsights: () => Promise.resolve(entity), @@ -53,29 +53,25 @@ const mockProductCost = createMockEntity(() => ({ const renderProductInsightsCardInTestApp = async ( entity: Entity, product: Product, - duration: Duration, + duration = Duration.P30D, + onSelectAsync = jest.fn(() => Promise.resolve(mockProductCost)), ) => await renderInTestApp( - - + + - ({ - ...p, - duration: duration, - }))} - > - - - - - - + + + - - + + , ); @@ -100,7 +96,6 @@ describe('', () => { const rendered = await renderProductInsightsCardInTestApp( entity, MockComputeEngine, - Duration.P1M, ); const subheader = 'entities, sorted by cost'; const subheaderRgx = new RegExp(`${entity.entities.length} ${subheader}`); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx index 2a1f962000..6d6d8d5cd9 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx @@ -14,46 +14,84 @@ * limitations under the License. */ -import React, { useCallback, useEffect, useState } from 'react'; -import { InfoCard, useApi } from '@backstage/core'; -import Alert from '@material-ui/lab/Alert'; +import React, { + PropsWithChildren, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { InfoCard } from '@backstage/core'; import { Typography } from '@material-ui/core'; -import { costInsightsApiRef } from '../../api'; -import { ProductInsightsChart } from './ProductInsightsChart'; import { PeriodSelect } from '../PeriodSelect'; -import { - useFilters, - useLastCompleteBillingDate, - useLoading, - useScroll, -} from '../../hooks'; +import { ProductInsightsChart } from './ProductInsightsChart'; +import { ProductInsightsErrorCard } from './ProductInsightsErrorCard'; import { useProductInsightsCardStyles as useStyles } from '../../utils/styles'; -import { mapFiltersToProps, mapLoadingToProps } from './selector'; -import { Duration, Maybe, Product, Entity } from '../../types'; +import { DefaultLoadingAction } from '../../utils/loading'; +import { Duration, Entity, Maybe, Product } from '../../types'; +import { + useLastCompleteBillingDate, + useScroll, + useLoading, + MapLoadingToProps, +} from '../../hooks'; import { pluralOf } from '../../utils/grammar'; -type ProductInsightsCardProps = { +type LoadingProps = (isLoading: boolean) => void; + +export type ProductInsightsCardProps = { product: Product; + initialState: { + entity: Maybe; + duration: Duration; + }; + onSelectAsync: (product: Product, duration: Duration) => Promise; }; -export const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { - const client = useApi(costInsightsApiRef); +const mapLoadingToProps: MapLoadingToProps = ({ dispatch }) => ( + isLoading: boolean, +) => dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading }); + +export const ProductInsightsCard = ({ + initialState, + product, + onSelectAsync, +}: PropsWithChildren) => { const classes = useStyles(); + const mountedRef = useRef(false); const { ScrollAnchor } = useScroll(product.kind); - const lastCompleteBillingDate = useLastCompleteBillingDate(); - const [entity, setEntity] = useState>(null); const [error, setError] = useState>(null); + const dispatchLoading = useLoading(mapLoadingToProps); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const [entity, setEntity] = useState>(initialState.entity); + const [duration, setDuration] = useState(initialState.duration); - const { group, product: productFilter, setProduct, project } = useFilters( - mapFiltersToProps(product.kind), - ); - const { loadingProduct, dispatchLoading } = useLoading( - mapLoadingToProps(product.kind), - ); + /* eslint-disable react-hooks/exhaustive-deps */ + const dispatchLoadingProduct = useCallback(dispatchLoading, []); + /* eslint-enable react-hooks/exhaustive-deps */ - // @see CostInsightsPage - // eslint-disable-next-line react-hooks/exhaustive-deps - const dispatchLoadingProduct = useCallback(dispatchLoading, [product.kind]); + useEffect(() => { + async function handleOnSelectAsync() { + dispatchLoadingProduct(true); + try { + const e = await onSelectAsync(product, duration); + setEntity(e); + } catch (e) { + setEntity(null); + setError(e); + } finally { + dispatchLoadingProduct(false); + } + } + + if (mountedRef.current) { + handleOnSelectAsync(); + } + }, [product, duration, onSelectAsync, dispatchLoadingProduct]); + + useEffect(function hasComponentMounted() { + mountedRef.current = true; + }, []); const amount = entity?.entities?.length || 0; const hasCostsWithinTimeframe = !!(entity?.change && amount); @@ -62,77 +100,31 @@ export const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { ? `${amount} ${pluralOf(amount, 'entity', 'entities')}, sorted by cost` : null; - useEffect(() => { - async function load() { - if (loadingProduct) { - try { - const e: Entity = await client.getProductInsights({ - product: product.kind, - group: group!, - duration: productFilter!.duration, - lastCompleteBillingDate, - project, - }); - setEntity(e); - } catch (e) { - setError(e); - } finally { - dispatchLoadingProduct(false); - } - } - } - load(); - }, [ - client, - product, - setEntity, - loadingProduct, - dispatchLoadingProduct, - productFilter, - group, - product.kind, - project, - lastCompleteBillingDate, - ]); - - const onPeriodSelect = (duration: Duration) => { - dispatchLoadingProduct(true); - setProduct(duration); - }; - const infoCardProps = { headerProps: { classes: classes, - action: ( - - ), + action: , }, }; - if (error) { + if (error || !entity) { return ( - - - {`Error: Could not fetch product insights for ${product.name}`} - + ); } - if (!entity) { - return null; - } - return ( {hasCostsWithinTimeframe ? ( ) : ( diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx new file mode 100644 index 0000000000..256ee8ba96 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.test.tsx @@ -0,0 +1,156 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { ProductInsightsCardList } from './ProductInsightsCardList'; +import { ProductInsightsOptions } from '../../api'; +import { mockDefaultLoadingState, MockProducts } from '../../utils/mockData'; +import { + MockConfigProvider, + MockCostInsightsApiProvider, + MockCurrencyProvider, + MockFilterProvider, + MockBillingDateProvider, + MockScrollProvider, + MockLoadingProvider, +} from '../../utils/tests'; +import { Entity } from '../../types'; + +// suppress recharts componentDidUpdate warnings +jest.spyOn(console, 'warn').mockImplementation(() => {}); + +const MockComputeEngineInsights: Entity = { + id: 'compute-engine', + entities: [], + aggregation: [0, 0], + change: { + ratio: 0, + amount: 0, + }, +}; + +const MockCloudDataflowInsights: Entity = { + id: 'cloud-dataflow', + entities: [], + aggregation: [1_000, 2_000], + change: { + ratio: 1, + amount: 1_000, + }, +}; + +const MockCloudStorageInsights: Entity = { + id: 'cloud-storage', + entities: [], + aggregation: [2_000, 4_000], + change: { + ratio: 1, + amount: 2_000, + }, +}; + +const MockBigQueryInsights: Entity = { + id: 'big-query', + entities: [], + aggregation: [8_000, 16_000], + change: { + ratio: 1, + amount: 8_000, + }, +}; + +const MockBigTableInsights: Entity = { + id: 'big-table', + entities: [], + aggregation: [16_000, 32_000], + change: { + ratio: 1, + amount: 16_000, + }, +}; + +const MockCloudPubSubInsights: Entity = { + id: 'cloud-pub-sub', + entities: [], + aggregation: [32_000, 64_000], + change: { + ratio: 1, + amount: 32_000, + }, +}; + +const ProductEntityMap = { + [MockBigQueryInsights.id!]: MockBigQueryInsights, + [MockBigTableInsights.id!]: MockBigTableInsights, + [MockCloudPubSubInsights.id!]: MockCloudPubSubInsights, + [MockCloudStorageInsights.id!]: MockCloudStorageInsights, + [MockCloudDataflowInsights.id!]: MockCloudDataflowInsights, + [MockComputeEngineInsights.id!]: MockComputeEngineInsights, +}; + +const costInsightsApi = { + getProductInsights: ({ product }: ProductInsightsOptions): Promise => + Promise.resolve(ProductEntityMap[product]), +}; + +function renderInContext(children: JSX.Element) { + return renderInTestApp( + + + + + + + {children} + + + + + + , + ); +} + +describe('', () => { + it('should render each product panel', async () => { + const noComputeEngineCostsRgx = /There are no Compute Engine costs within this timeframe for your team's projects./; + const { getByText } = await renderInContext( + , + ); + expect(getByText(noComputeEngineCostsRgx)).toBeInTheDocument(); + MockProducts.forEach(product => + expect(getByText(product.name)).toBeInTheDocument(), + ); + }); + + it('product panels should be sorted by total aggregated cost', async () => { + const { queryAllByTestId } = await renderInContext( + , + ); + const expectedOrder = MockProducts.map( + product => `product-list-item-${product.kind}`, + ).reverse(); + const productPanels = queryAllByTestId(/^product-list-item/); + + expect(productPanels.length).toBe(MockProducts.length); + Array.from(productPanels) + .map(el => el.getAttribute('data-testid')) + .forEach((id, i) => { + expect(id).toBe(expectedOrder[i]); + }); + }); +}); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx new file mode 100644 index 0000000000..5c096553e1 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCardList.tsx @@ -0,0 +1,188 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { useCallback, useEffect, useReducer, useRef } from 'react'; +import { Box, CircularProgress } from '@material-ui/core'; +import { useApi } from '@backstage/core'; +import { costInsightsApiRef } from '../../api'; +import { ProductInsightsCard } from './ProductInsightsCard'; +import { Duration, Entity, Maybe, Product } from '../../types'; +import { DefaultLoadingAction } from '../../utils/loading'; +import { + useFilters, + useLoading, + useLastCompleteBillingDate, + MapLoadingToProps, +} from '../../hooks'; + +type State = { + [kind: string]: ProductState; +}; + +type ProductState = { + product: Product; + entity: Maybe; + duration: Duration; +}; + +type LoadingProps = (isLoading: boolean) => void; + +type ProductInsightsCardListProps = { + products: Product[]; +}; + +const DEFAULT_DURATION = Duration.P30D; + +function totalAggregationSort(a: ProductState, b: ProductState): number { + const [prevA, currA] = a.entity?.aggregation ?? [0, 0]; + const [prevB, currB] = b.entity?.aggregation ?? [0, 0]; + return prevB + currB - (prevA + currA); +} + +const mapLoadingToProps: MapLoadingToProps = ({ dispatch }) => ( + isLoading: boolean, +) => dispatch({ [DefaultLoadingAction.CostInsightsProducts]: isLoading }); + +function reducer(prevState: State, action: State): State { + return { + ...prevState, + ...action, + }; +} + +export const ProductInsightsCardList = ({ + products, +}: ProductInsightsCardListProps) => { + const onceRef = useRef(false); + const client = useApi(costInsightsApiRef); + const filters = useFilters(p => p.pageFilters); + const [state, dispatch] = useReducer(reducer, {}); + const lastCompleteBillingDate = useLastCompleteBillingDate(); + const dispatchLoading = useLoading(mapLoadingToProps); + + /* eslint-disable react-hooks/exhaustive-deps */ + // See @CostInsightsPage + const dispatchLoadingProducts = useCallback(dispatchLoading, []); + /* eslint-enable react-hooks/exhaustive-deps */ + + const initialProductStates = onceRef.current + ? Object.values(state).sort(totalAggregationSort) + : Object.values(state); + + const onSelectAsyncMemo = useCallback( + async function onSelectAsync( + product: Product, + duration: Duration, + ): Promise { + if (filters.group) { + return client.getProductInsights({ + group: filters.group, + project: filters.project, + product: product.kind, + duration: duration, + lastCompleteBillingDate: lastCompleteBillingDate, + }); + } + return Promise.reject( + new Error( + `Cannot fetch insights for ${product.name}: Expected to have a group but found none.`, + ), + ); + }, + [client, filters.group, filters.project, lastCompleteBillingDate], + ); + + useEffect(() => { + async function getAllProductInsights( + products: Product[], + group: string, + project: Maybe, + lastCompleteBillingDate: string, + ) { + const requests = products.map(product => + client.getProductInsights({ + group: group, + project: project, + product: product.kind, + duration: DEFAULT_DURATION, + lastCompleteBillingDate: lastCompleteBillingDate, + }), + ); + + try { + dispatchLoadingProducts(true); + const responses = await Promise.allSettled(requests); + const results = responses.map(response => + response.status === 'fulfilled' ? response.value : null, + ); + const initialState = results.reduce((acc, entity, index) => { + const product = products[index]; + return { + ...acc, + [product.kind]: { + product: product, + entity: entity, + duration: DEFAULT_DURATION, + }, + }; + }, {}); + dispatch(initialState); + } finally { + dispatchLoadingProducts(false); + } + } + + if (filters.group) { + onceRef.current = true; + getAllProductInsights( + products, + filters.group, + filters.project, + lastCompleteBillingDate, + ); + } + }, [ + client, + products, + filters.group, + filters.project, + lastCompleteBillingDate, + dispatchLoadingProducts, + ]); + + if (!initialProductStates.length) { + return ; + } + + return ( + <> + {initialProductStates.map(({ product, entity, duration }) => ( + + + + ))} + + ); +}; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx new file mode 100644 index 0000000000..6f6c5a21c8 --- /dev/null +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsErrorCard.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { InfoCard } from '@backstage/core'; +import { default as Alert } from '@material-ui/lab/Alert'; +import { PeriodSelect } from '../PeriodSelect'; +import { useScroll } from '../../hooks'; +import { useProductInsightsCardStyles as useStyles } from '../../utils/styles'; +import { Duration, Product } from '../../types'; + +export type ProductInsightsErrorCardProps = { + product: Product; + duration: Duration; + onSelect: (duration: Duration) => void; +}; + +export const ProductInsightsErrorCard = ({ + product, + duration, + onSelect, +}: ProductInsightsErrorCardProps) => { + const classes = useStyles(); + const { ScrollAnchor } = useScroll(product.kind); + + const headerProps = { + classes: classes, + action: , + }; + + return ( + + + {`Error: Could not fetch product insights for ${product.name}`} + + ); +}; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/index.ts b/plugins/cost-insights/src/components/ProductInsightsCard/index.ts index 33a402234c..0a67bf459e 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/index.ts +++ b/plugins/cost-insights/src/components/ProductInsightsCard/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { ProductInsightsCard } from './ProductInsightsCard'; +export { ProductInsightsCardList } from './ProductInsightsCardList'; export { ProductInsightsChart } from './ProductInsightsChart'; diff --git a/plugins/cost-insights/src/hooks/useLoading.tsx b/plugins/cost-insights/src/hooks/useLoading.tsx index 420ac7ed56..07f8a0f4e7 100644 --- a/plugins/cost-insights/src/hooks/useLoading.tsx +++ b/plugins/cost-insights/src/hooks/useLoading.tsx @@ -21,7 +21,6 @@ import React, { SetStateAction, useContext, useEffect, - useMemo, useReducer, useState, } from 'react'; @@ -30,10 +29,9 @@ import { Loading } from '../types'; import { DefaultLoadingAction, getDefaultState, - getLoadingActions, + INITIAL_LOADING_ACTIONS, } from '../utils/loading'; import { useBackdropStyles as useStyles } from '../utils/styles'; -import { useConfig } from './useConfig'; export type LoadingContextProps = { state: Loading; @@ -56,10 +54,7 @@ function reducer(prevState: Loading, action: Partial): Loading { export const LoadingProvider = ({ children }: PropsWithChildren<{}>) => { const classes = useStyles(); - const { products } = useConfig(); - const actions = useMemo(() => getLoadingActions(products.map(p => p.kind)), [ - products, - ]); + const actions = INITIAL_LOADING_ACTIONS; const [state, dispatch] = useReducer(reducer, getDefaultState(actions)); const [isBackdropVisible, setBackdropVisible] = useState(false); diff --git a/plugins/cost-insights/src/utils/loading.ts b/plugins/cost-insights/src/utils/loading.ts index 4652603926..40101051b8 100644 --- a/plugins/cost-insights/src/utils/loading.ts +++ b/plugins/cost-insights/src/utils/loading.ts @@ -21,11 +21,13 @@ export enum DefaultLoadingAction { LastCompleteBillingDate = 'billing-date', CostInsightsInitial = 'cost-insights-initial', CostInsightsPage = 'cost-insights-page', + CostInsightsProducts = 'cost-insights-products', } export const INITIAL_LOADING_ACTIONS = [ DefaultLoadingAction.UserGroups, DefaultLoadingAction.CostInsightsInitial, + DefaultLoadingAction.CostInsightsProducts, ]; export const getDefaultState = (loadingActions: string[]): Loading => { @@ -54,11 +56,3 @@ export const getResetStateWithoutInitial = ( return { ...defaultState, [action]: loadingActionState }; }, {}); }; - -export function getLoadingActions(products: string[]): string[] { - return ([ - DefaultLoadingAction.UserGroups, - DefaultLoadingAction.CostInsightsInitial, - DefaultLoadingAction.CostInsightsPage, - ] as string[]).concat(products); -} diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index 708ce733dd..e40e5e1ddc 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -145,14 +145,14 @@ export const MockProducts: Product[] = Object.keys(MockProductTypes).map( })), ); -export const MockLoadingActions = ([ +export const MockDefaultLoadingActions = ([ DefaultLoadingAction.UserGroups, DefaultLoadingAction.CostInsightsInitial, DefaultLoadingAction.CostInsightsPage, ] as string[]).concat(MockProducts.map(product => product.kind)); export const mockDefaultLoadingState = getDefaultLoadingState( - MockLoadingActions, + MockDefaultLoadingActions, ); export const MockComputeEngine = findAlways( From a2cfa311ab48a69b6bbdd3e2dfadefe4a5e6dc52 Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 18:46:05 -0500 Subject: [PATCH 049/195] Update bar chart legend type usage --- .changeset/cost-insights-cyan-nails-film.md | 5 +++++ plugins/cost-insights/src/components/BarChart/index.ts | 5 ++++- .../components/ProductInsightsCard/ProductInsightsChart.tsx | 5 +++-- .../ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx | 6 +++--- 4 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 .changeset/cost-insights-cyan-nails-film.md diff --git a/.changeset/cost-insights-cyan-nails-film.md b/.changeset/cost-insights-cyan-nails-film.md new file mode 100644 index 0000000000..69e21d87fe --- /dev/null +++ b/.changeset/cost-insights-cyan-nails-film.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Add breakdown view to the Cost Overview panel diff --git a/plugins/cost-insights/src/components/BarChart/index.ts b/plugins/cost-insights/src/components/BarChart/index.ts index 76f859636a..73676dfa81 100644 --- a/plugins/cost-insights/src/components/BarChart/index.ts +++ b/plugins/cost-insights/src/components/BarChart/index.ts @@ -17,7 +17,10 @@ export { BarChart } from './BarChart'; export type { BarChartProps } from './BarChart'; export { BarChartLegend } from './BarChartLegend'; -export type { BarChartLegendProps } from './BarChartLegend'; +export type { + BarChartLegendProps, + BarChartLegendOptions, +} from './BarChartLegend'; export { BarChartTooltip } from './BarChartTooltip'; export type { BarChartTooltipProps } from './BarChartTooltip'; export { BarChartTooltipItem } from './BarChartTooltipItem'; diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx index 6050dae9c7..1209493e0a 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx @@ -30,6 +30,7 @@ import { BarChartLegend, BarChartTooltip, BarChartTooltipItem, + BarChartLegendOptions, } from '../BarChart'; import { pluralOf } from '../../utils/grammar'; import { findAlways, notEmpty } from '../../utils/assert'; @@ -45,7 +46,7 @@ import { useProductInsightsChartStyles as useStyles, useBarChartLayoutStyles as useLayoutStyles, } from '../../utils/styles'; -import { BarChartOptions, Duration, Entity, Maybe } from '../../types'; +import { Duration, Entity, Maybe } from '../../types'; export type ProductInsightsChartProps = { billingDate: string; @@ -70,7 +71,7 @@ export const ProductInsightsChart = ({ const costEnd = entity.aggregation[1]; const resources = entity.entities.map(resourceOf); - const options: Partial = { + const options: Partial = { previousName: formatPeriod(duration, billingDate, false), currentName: formatPeriod(duration, billingDate, true), }; diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx index 53e52f18e8..f541719e4a 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx @@ -17,10 +17,10 @@ import React from 'react'; import moment from 'moment'; import { Box } from '@material-ui/core'; -import { BarChart, BarChartLegend } from '../BarChart'; +import { BarChart, BarChartLegend, BarChartLegendOptions } from '../BarChart'; import { LegendItem } from '../LegendItem'; import { CostGrowth } from '../CostGrowth'; -import { BarChartOptions, Duration, ProjectGrowthData } from '../../types'; +import { Duration, ProjectGrowthData } from '../../types'; import { useBarChartLayoutStyles as useStyles } from '../../utils/styles'; import { resourceOf } from '../../utils/graphs'; @@ -37,7 +37,7 @@ export const ProjectGrowthAlertChart = ({ const costEnd = alert.aggregation[1]; const resourceData = alert.products.map(resourceOf); - const options: Partial = { + const options: Partial = { previousName: moment(alert.periodStart, 'YYYY-[Q]Q').format('[Q]Q YYYY'), currentName: moment(alert.periodEnd, 'YYYY-[Q]Q').format('[Q]Q YYYY'), }; From b467106c1ffe7f60c2376764ca66429446605537 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 09:49:35 +0100 Subject: [PATCH 050/195] Reformat EntityPage --- packages/app/src/components/catalog/EntityPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index d30de6abd1..068019c622 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -47,7 +47,7 @@ import { EmbeddedRouter as LighthouseRouter, isPluginApplicableToEntity as isLighthouseAvailable, LastLighthouseAuditCard, -} from '@backstage/plugin-lighthouse/'; +} from '@backstage/plugin-lighthouse'; import { Router as SentryRouter } from '@backstage/plugin-sentry'; import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; import { Button, Grid } from '@material-ui/core'; From f538e2c560d4a4d9172cf643cb90235f3ae01453 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Nov 2020 10:33:45 +0100 Subject: [PATCH 051/195] cli: update versions:bump to install new deps for accepted version ranges --- .changeset/long-birds-rush.md | 5 ++ .../cli/src/commands/versions/bump.test.ts | 31 ++++++---- packages/cli/src/commands/versions/bump.ts | 59 +++++++++++++------ packages/cli/src/commands/versions/lint.ts | 12 +++- packages/cli/src/lib/versioning/Lockfile.ts | 13 ++++ 5 files changed, 86 insertions(+), 34 deletions(-) create mode 100644 .changeset/long-birds-rush.md diff --git a/.changeset/long-birds-rush.md b/.changeset/long-birds-rush.md new file mode 100644 index 0000000000..db4da34789 --- /dev/null +++ b/.changeset/long-birds-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Make versions:bump install new versions of dependencies that were within the specified range diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index 687708b074..f4d69c52be 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -24,7 +24,7 @@ import bump from './bump'; import { withLogCollector } from '@backstage/test-utils'; const REGISTRY_VERSIONS: { [name: string]: string } = { - '@backstage/core': '1.0.7', + '@backstage/core': '1.0.6', '@backstage/theme': '2.0.0', }; @@ -36,30 +36,36 @@ const HEADER = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. const lockfileMock = `${HEADER} "@backstage/core@^1.0.5": version "1.0.6" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + dependencies: + "@backstage/core-api" "^1.0.6" "@backstage/core@^1.0.3": version "1.0.3" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + dependencies: + "@backstage/core-api" "^1.0.3" "@backstage/theme@^1.0.0": version "1.0.0" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + +"@backstage/core-api@^1.0.6": + version "1.0.6" + +"@backstage/core-api@^1.0.3": + version "1.0.3" `; +// This resulting lockfile isn't a real world example, since it doesn't include the package bumps const lockfileMockResult = `${HEADER} -"@backstage/core@^1.0.3", "@backstage/core@^1.0.5": +"@backstage/core-api@^1.0.3", "@backstage/core-api@^1.0.6": version "1.0.6" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz + +"@backstage/core@^1.0.5": + version "1.0.6" + dependencies: + "@backstage/core-api" "^1.0.6" "@backstage/theme@^1.0.0": version "1.0.0" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz `; describe('bump', () => { @@ -116,6 +122,7 @@ describe('bump', () => { 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core', 'Some packages are outdated, updating', + 'Removing lockfile entry for @backstage/core@^1.0.3 to bump to 1.0.6', 'Bumping @backstage/theme in b to ^2.0.0', "Running 'yarn install' to install new versions", 'Removing duplicate dependencies from yarn.lock', diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 00944c5db9..1883d87bac 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -24,6 +24,7 @@ import { fetchPackageInfo, Lockfile, } from '../../lib/versioning'; +import { includedFilter, forbiddenDuplicatesFilter } from './lint'; const DEP_TYPES = [ 'dependencies', @@ -39,12 +40,16 @@ type PkgVersionInfo = { }; export default async () => { + const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + // First we discover all Backstage dependencies within our own repo const dependencyMap = await mapDependencies(paths.targetDir); - // Next check with the package registry what the latest version of all of those dependencies are - const targetVersions = new Map(); - await workerThreads(16, dependencyMap.keys(), async name => { + // Next check with the package registry to see which dependency ranges we need to bump + const versionBumps = new Map(); + // Track package versions that we want to remove from yarn.lock in order to trigger a bump + const unlocked = Array<{ name: string; range: string; latest: string }>(); + await workerThreads(16, dependencyMap.entries(), async ([name, pkgs]) => { console.log(`Checking for updates of ${name}`); const info = await fetchPackageInfo(name); const latest = info['dist-tags'].latest; @@ -52,38 +57,49 @@ export default async () => { throw new Error(`No latest version found for ${name}`); } - targetVersions.set(name, latest); - }); - - // Then figure out which local packages need to have their dependencies bumped - const versionBumps = new Map(); - for (const [name, pkgs] of dependencyMap) { - const targetVersion = targetVersions.get(name)!; for (const pkg of pkgs) { - if (semver.satisfies(targetVersion, pkg.range)) { + if (semver.satisfies(latest, pkg.range)) { + if (semver.minVersion(pkg.range)?.version !== latest) { + unlocked.push({ name, range: pkg.range, latest }); + } continue; } - versionBumps.set( pkg.name, (versionBumps.get(pkg.name) ?? []).concat({ name, location: pkg.location, - range: `^${targetVersion}`, // TODO(Rugvip): Option to use something else than ^? + range: `^${latest}`, // TODO(Rugvip): Option to use something else than ^? }), ); } - } + }); console.log(); // Write all discovered version bumps to package.json in this repo - if (versionBumps.size === 0) { + if (versionBumps.size === 0 && unlocked.length === 0) { console.log('All Backstage packages are up to date!'); } else { console.log('Some packages are outdated, updating'); console.log(); + if (unlocked.length > 0) { + const lockfile = await Lockfile.load(lockfilePath); + for (const { name, range, latest } of unlocked) { + // Don't bother removing lockfile entries if they're already on the correct version + const existingEntry = lockfile.get(name)?.find(e => e.range === range); + if (existingEntry?.version === latest) { + continue; + } + console.log( + `Removing lockfile entry for ${name}@${range} to bump to ${latest}`, + ); + lockfile.remove(name, range); + } + await lockfile.save(); + } + await workerThreads(16, versionBumps.entries(), async ([name, deps]) => { const pkgPath = resolvePath(deps[0].location, 'package.json'); const pkgJson = await fs.readJson(pkgPath); @@ -110,9 +126,9 @@ export default async () => { console.log(); // Finally we make sure the new lockfile doesn't have any duplicates - const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); + const lockfile = await Lockfile.load(lockfilePath); const result = lockfile.analyze({ - filter: name => dependencyMap.has(name), + filter: includedFilter, }); if (result.newVersions.length > 0) { @@ -130,9 +146,14 @@ export default async () => { console.log(); } - if (result.newRanges.length > 0) { + const forbiddenNewRanges = result.newRanges.filter(({ name }) => + forbiddenDuplicatesFilter(name), + ); + if (forbiddenNewRanges.length > 0) { throw new Error( - `Version bump failed for ${result.newRanges.map(i => i.name).join(', ')}`, + `Version bump failed for ${forbiddenNewRanges + .map(i => i.name) + .join(', ')}`, ); } }; diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index e2c36ba094..aa0bcd6f6a 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -22,6 +22,9 @@ import partition from 'lodash/partition'; // Packages that we try to avoid duplicates for const INCLUDED = [/^@backstage\//]; +export const includedFilter = (name: string) => + INCLUDED.some(pattern => pattern.test(name)); + // Packages that are not allowed to have any duplicates const FORBID_DUPLICATES = [ /^@backstage\/core$/, @@ -29,6 +32,9 @@ const FORBID_DUPLICATES = [ /^@backstage\/plugin-/, ]; +export const forbiddenDuplicatesFilter = (name: string) => + FORBID_DUPLICATES.some(pattern => pattern.test(name)); + export default async (cmd: Command) => { const fix = Boolean(cmd.fix); @@ -36,7 +42,7 @@ export default async (cmd: Command) => { const lockfile = await Lockfile.load(paths.resolveTargetRoot('yarn.lock')); const result = lockfile.analyze({ - filter: name => INCLUDED.some(pattern => pattern.test(name)), + filter: includedFilter, }); logArray( @@ -53,7 +59,7 @@ export default async (cmd: Command) => { newVersionsForbidden, newVersionsAllowed, ] = partition(result.newVersions, ({ name }) => - FORBID_DUPLICATES.some(pattern => pattern.test(name)), + forbiddenDuplicatesFilter(name), ); if (newVersionsForbidden.length && !fix) { success = false; @@ -75,7 +81,7 @@ export default async (cmd: Command) => { const [newRangesForbidden, newRangesAllowed] = partition( result.newRanges, - ({ name }) => FORBID_DUPLICATES.some(pattern => pattern.test(name)), + ({ name }) => forbiddenDuplicatesFilter(name), ); if (newRangesForbidden.length) { success = false; diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 893beca6e2..f9bc789d95 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -202,6 +202,19 @@ export class Lockfile { return result; } + remove(name: string, range: string): boolean { + const query = `${name}@${range}`; + const existed = Boolean(this.data[query]); + delete this.data[query]; + + const newEntries = this.packages.get(name)?.filter(e => e.range !== range); + if (newEntries) { + this.packages.set(name, newEntries); + } + + return existed; + } + /** Modifies the lockfile by bumping packages to the suggested versions */ replaceVersions(results: AnalyzeResultNewVersion[]) { for (const { name, range, oldVersion, newVersion } of results) { From 2daf18e8095c86ed71b0f059e9b895f3cdf7d6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 25 Nov 2020 10:51:22 +0100 Subject: [PATCH 052/195] catalog: emit all common relations based on specs (#3408) --- .changeset/pink-goats-sort.md | 6 + packages/catalog-model/src/entity/ref.ts | 16 +- packages/catalog-model/src/kinds/relations.ts | 2 + .../20201123205611_relations_table_uniq.js | 93 +++++++++ plugins/catalog-backend/package.json | 2 +- .../src/database/CommonDatabase.ts | 17 +- .../BuiltinKindsEntityProcessor.test.ts | 180 ++++++++++++++++++ .../processors/BuiltinKindsEntityProcessor.ts | 128 ++++++++++++- .../processors/OwnerRelationProcessor.ts | 74 ------- .../src/ingestion/processors/index.ts | 3 +- .../src/service/CatalogBuilder.test.ts | 2 +- .../src/service/CatalogBuilder.ts | 6 +- 12 files changed, 437 insertions(+), 92 deletions(-) create mode 100644 .changeset/pink-goats-sort.md create mode 100644 plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js delete mode 100644 plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts diff --git a/.changeset/pink-goats-sort.md b/.changeset/pink-goats-sort.md new file mode 100644 index 0000000000..828cccba84 --- /dev/null +++ b/.changeset/pink-goats-sort.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Start emitting all known relation types from the core entity kinds, based on their spec data. diff --git a/packages/catalog-model/src/entity/ref.ts b/packages/catalog-model/src/entity/ref.ts index 2cacfce53a..9f6f175d8d 100644 --- a/packages/catalog-model/src/entity/ref.ts +++ b/packages/catalog-model/src/entity/ref.ts @@ -84,6 +84,14 @@ export function parseEntityName( * @param context The context of defaults that the parsing happens within * @returns The compound form of the reference */ +export function parseEntityRef( + ref: EntityRef, + context?: { defaultKind: string; defaultNamespace: string }, +): { + kind: string; + namespace: string; + name: string; +}; export function parseEntityRef( ref: EntityRef, context?: { defaultKind: string }, @@ -100,14 +108,6 @@ export function parseEntityRef( namespace: string; name: string; }; -export function parseEntityRef( - ref: EntityRef, - context?: { defaultKind: string; defaultNamespace: string }, -): { - kind: string; - namespace: string; - name: string; -}; export function parseEntityRef( ref: EntityRef, context: EntityRefContext = {}, diff --git a/packages/catalog-model/src/kinds/relations.ts b/packages/catalog-model/src/kinds/relations.ts index 846313ec8c..3d5d629b9e 100644 --- a/packages/catalog-model/src/kinds/relations.ts +++ b/packages/catalog-model/src/kinds/relations.ts @@ -33,7 +33,9 @@ export const RELATION_OWNER_OF = 'ownerOf'; * A relation with an API entity, typically from a component or system */ export const RELATION_CONSUMES_API = 'consumesApi'; +export const RELATION_API_CONSUMED_BY = 'apiConsumedBy'; export const RELATION_PROVIDES_API = 'providesApi'; +export const RELATION_API_PROVIDED_BY = 'apiProvidedBy'; /** * A relation denoting a dependency on another entity. diff --git a/plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js b/plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js new file mode 100644 index 0000000000..a8cf62b4c4 --- /dev/null +++ b/plugins/catalog-backend/migrations/20201123205611_relations_table_uniq.js @@ -0,0 +1,93 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * @param {import('knex')} knex + */ +exports.up = async function up(knex) { + if (knex.client.config.client === 'sqlite3') { + // sqlite doesn't support dropPrimary so we recreate it properly instead + await knex.schema.dropTable('entities_relations'); + await knex.schema.createTable('entities_relations', table => { + table.comment('All relations between entities in the catalog'); + table + .uuid('originating_entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .notNullable() + .comment('The entity that provided the relation'); + table + .string('source_full_name') + .notNullable() + .comment('The full name of the source entity of the relation'); + table + .string('type') + .notNullable() + .comment('The type of the relation between the entities'); + table + .string('target_full_name') + .notNullable() + .comment('The full name of the target entity of the relation'); + table.index('source_full_name', 'source_full_name_idx'); + }); + } else { + await knex.schema.alterTable('entities_relations', table => { + table.dropPrimary(); + table.index('source_full_name', 'source_full_name_idx'); + }); + } +}; + +/** + * @param {import('knex')} knex + */ +exports.down = async function down(knex) { + if (knex.client.config.client === 'sqlite3') { + await knex.schema.dropTable('entities_relations'); + await knex.schema.createTable('entities_relations', table => { + table.comment('All relations between entities in the catalog'); + table + .uuid('originating_entity_id') + .references('id') + .inTable('entities') + .onDelete('CASCADE') + .notNullable() + .comment('The entity that provided the relation'); + table + .string('source_full_name') + .notNullable() + .comment('The full name of the source entity of the relation'); + table + .string('type') + .notNullable() + .comment('The type of the relation between the entities'); + table + .string('target_full_name') + .notNullable() + .comment('The full name of the target entity of the relation'); + + table.primary(['source_full_name', 'type', 'target_full_name']); + }); + } else { + await knex.schema.alterTable('entities_relations', table => { + table.dropIndex([], 'source_full_name_idx'); + table.primary(['source_full_name', 'type', 'target_full_name']); + }); + } +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 51a9978f30..26f7180d43 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -26,6 +26,7 @@ "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", + "@types/ldapjs": "^1.0.9", "codeowners-utils": "^1.0.2", "core-js": "^3.6.5", "cross-fetch": "^3.0.6", @@ -51,7 +52,6 @@ "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", - "@types/ldapjs": "^1.0.9", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts index f86ab9e598..287756b0a8 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.ts @@ -345,7 +345,11 @@ export class CommonDatabase implements Database { ); // TODO(blam): translate constraint failures to sane NotFoundError instead - await tx.batchInsert('entities_relations', relationsRows, BATCH_SIZE); + await tx.batchInsert( + 'entities_relations', + deduplicateRelations(relationsRows), + BATCH_SIZE, + ); } async addLocation( @@ -506,7 +510,7 @@ export class CommonDatabase implements Database { .orderBy(['type', 'target_full_name']) .select(); - entity.relations = relations.map(r => ({ + entity.relations = deduplicateRelations(relations).map(r => ({ target: parseEntityName(r.target_full_name), type: r.type, })); @@ -517,3 +521,12 @@ export class CommonDatabase implements Database { }; } } + +function deduplicateRelations( + rows: DbEntitiesRelationsRow[], +): DbEntitiesRelationsRow[] { + return lodash.uniqBy( + rows, + r => `${r.source_full_name}:${r.target_full_name}:${r.type}`, + ); +} diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index e1328e827a..94d35cc109 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +import { + ApiEntity, + ComponentEntity, + GroupEntity, + UserEntity, +} from '@backstage/catalog-model'; import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; describe('BuiltinKindsEntityProcessor', () => { @@ -44,4 +50,178 @@ describe('BuiltinKindsEntityProcessor', () => { }, }); }); + + describe('postProcessEntity', () => { + const processor = new BuiltinKindsEntityProcessor(); + const location = { type: 'a', target: 'b' }; + const emit = jest.fn(); + + afterEach(() => jest.resetAllMocks()); + + it('generates relations for component entities', async () => { + const entity: ComponentEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { name: 'n' }, + spec: { + type: 'service', + owner: 'o', + lifecycle: 'l', + implementsApis: ['a'], + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(4); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'o' }, + type: 'ownerOf', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'ownedBy', + target: { kind: 'Group', namespace: 'default', name: 'o' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'a' }, + type: 'apiProvidedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'providesApi', + target: { kind: 'API', namespace: 'default', name: 'a' }, + }, + }); + }); + + it('generates relations for api entities', async () => { + const entity: ApiEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { name: 'n' }, + spec: { + type: 'service', + owner: 'o', + lifecycle: 'l', + definition: 'd', + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(2); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'o' }, + type: 'ownerOf', + target: { kind: 'API', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'n' }, + type: 'ownedBy', + target: { kind: 'Group', namespace: 'default', name: 'o' }, + }, + }); + }); + + it('generates relations for user entities', async () => { + const entity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { name: 'n' }, + spec: { + memberOf: ['g'], + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(2); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'User', namespace: 'default', name: 'n' }, + type: 'memberOf', + target: { kind: 'Group', namespace: 'default', name: 'g' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'g' }, + type: 'hasMember', + target: { kind: 'User', namespace: 'default', name: 'n' }, + }, + }); + }); + + it('generates relations for group entities', async () => { + const entity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { name: 'n' }, + spec: { + type: 't', + parent: 'p', + ancestors: [], + children: ['c'], + descendants: [], + }, + }; + + await processor.postProcessEntity(entity, location, emit); + + expect(emit).toBeCalledTimes(4); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'n' }, + type: 'childOf', + target: { kind: 'Group', namespace: 'default', name: 'p' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'p' }, + type: 'parentOf', + target: { kind: 'Group', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'c' }, + type: 'childOf', + target: { kind: 'Group', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Group', namespace: 'default', name: 'n' }, + type: 'parentOf', + target: { kind: 'Group', namespace: 'default', name: 'c' }, + }, + }); + }); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 472dffeb16..a4e62e5b8c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -15,15 +15,31 @@ */ import { + ApiEntity, apiEntityV1alpha1Validator, + ComponentEntity, componentEntityV1alpha1Validator, Entity, + getEntityName, + GroupEntity, groupEntityV1alpha1Validator, locationEntityV1alpha1Validator, + LocationSpec, + parseEntityRef, + RELATION_API_PROVIDED_BY, + RELATION_CHILD_OF, + RELATION_HAS_MEMBER, + RELATION_MEMBER_OF, + RELATION_OWNED_BY, + RELATION_OWNER_OF, + RELATION_PARENT_OF, + RELATION_PROVIDES_API, templateEntityV1alpha1Validator, + UserEntity, userEntityV1alpha1Validator, } from '@backstage/catalog-model'; -import { CatalogProcessor } from './types'; +import * as result from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; export class BuiltinKindsEntityProcessor implements CatalogProcessor { private readonly validators = [ @@ -64,4 +80,114 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { return false; } + + async postProcessEntity( + entity: Entity, + _location: LocationSpec, + emit: CatalogProcessorEmit, + ): Promise { + const selfRef = getEntityName(entity); + + /* + * Utilities + */ + + function doEmit( + targets: string | string[] | undefined, + context: { defaultKind: string; defaultNamespace: string }, + outgoingRelation: string, + incomingRelation: string, + ): void { + if (!targets) { + return; + } + for (const target of [targets].flat()) { + const targetRef = parseEntityRef(target, context); + emit( + result.relation({ + source: selfRef, + type: outgoingRelation, + target: targetRef, + }), + ); + emit( + result.relation({ + source: targetRef, + type: incomingRelation, + target: selfRef, + }), + ); + } + } + + /* + * Emit relations for the Component kind + */ + + if (entity.kind === 'Component') { + const component = entity as ComponentEntity; + doEmit( + component.spec.owner, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_OWNED_BY, + RELATION_OWNER_OF, + ); + doEmit( + component.spec.implementsApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_PROVIDES_API, + RELATION_API_PROVIDED_BY, + ); + } + + /* + * Emit relations for the API kind + */ + + if (entity.kind === 'API') { + const api = entity as ApiEntity; + doEmit( + api.spec.owner, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_OWNED_BY, + RELATION_OWNER_OF, + ); + } + + /* + * Emit relations for the User kind + */ + + if (entity.kind === 'User') { + const user = entity as UserEntity; + doEmit( + user.spec.memberOf, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_MEMBER_OF, + RELATION_HAS_MEMBER, + ); + } + + /* + * Emit relations for the Group kind + */ + + if (entity.kind === 'Group') { + const group = entity as GroupEntity; + doEmit( + group.spec.parent, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_CHILD_OF, + RELATION_PARENT_OF, + ); + doEmit( + group.spec.children, + { defaultKind: 'Group', defaultNamespace: selfRef.namespace }, + RELATION_PARENT_OF, + RELATION_CHILD_OF, + ); + } + + return entity; + } } diff --git a/plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts deleted file mode 100644 index 4c67589789..0000000000 --- a/plugins/catalog-backend/src/ingestion/processors/OwnerRelationProcessor.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Entity, - ENTITY_DEFAULT_NAMESPACE, - LocationSpec, - parseEntityRef, - ApiEntityV1alpha1, - ComponentEntityV1alpha1, - RELATION_OWNED_BY, - RELATION_OWNER_OF, - getEntityName, -} from '@backstage/catalog-model'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; -import * as result from './results'; - -const includedKinds = new Set(['api', 'component']); - -export class OwnerRelationProcessor implements CatalogProcessor { - async postProcessEntity( - entity: Entity, - _location: LocationSpec, - emit: CatalogProcessorEmit, - ): Promise { - if (!includedKinds.has(entity.kind.toLowerCase())) { - return entity; - } - const apiOrComponentEntity = entity as - | ApiEntityV1alpha1 - | ComponentEntityV1alpha1; - - const owner = apiOrComponentEntity.spec?.owner; - if (owner) { - const namespace = entity.metadata.namespace ?? ENTITY_DEFAULT_NAMESPACE; - - const selfRef = getEntityName(entity); - const ownerRef = parseEntityRef(owner, { - defaultKind: 'group', - defaultNamespace: namespace, - }); - - emit( - result.relation({ - source: selfRef, - type: RELATION_OWNED_BY, - target: ownerRef, - }), - ); - emit( - result.relation({ - source: ownerRef, - type: RELATION_OWNER_OF, - target: selfRef, - }), - ); - } - - return entity; - } -} diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index 118f977125..49d4e03b64 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -22,10 +22,11 @@ export * from './types'; export { parseEntityYaml } from './util/parse'; export { AnnotateLocationEntityProcessor } from './AnnotateLocationEntityProcessor'; +export { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; export { CodeOwnersProcessor } from './CodeOwnersProcessor'; export { FileReaderProcessor } from './FileReaderProcessor'; export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor'; -export { OwnerRelationProcessor } from './OwnerRelationProcessor'; +export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor'; export { LocationRefProcessor } from './LocationEntityProcessor'; export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor'; export { PlaceholderProcessor } from './PlaceholderProcessor'; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts index ee7c6f2246..58897a10a7 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.test.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.test.ts @@ -144,7 +144,7 @@ describe('CatalogBuilder', () => { owner: 'o', lifecycle: 'l', }, - relations: [], + relations: expect.anything(), }, ]); }); diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 3f95dcd0d3..4be0f1799b 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -37,15 +37,16 @@ import { import { DatabaseManager } from '../database'; import { AnnotateLocationEntityProcessor, + BuiltinKindsEntityProcessor, CatalogProcessor, CodeOwnersProcessor, FileReaderProcessor, GithubOrgReaderProcessor, HigherOrderOperation, HigherOrderOperations, + LdapOrgReaderProcessor, LocationReaders, LocationRefProcessor, - OwnerRelationProcessor, MicrosoftGraphOrgReaderProcessor, PlaceholderProcessor, PlaceholderResolver, @@ -53,8 +54,6 @@ import { UrlReaderProcessor, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; -import { BuiltinKindsEntityProcessor } from '../ingestion/processors/BuiltinKindsEntityProcessor'; -import { LdapOrgReaderProcessor } from '../ingestion/processors/LdapOrgReaderProcessor'; import { jsonPlaceholderResolver, textPlaceholderResolver, @@ -283,7 +282,6 @@ export class CatalogBuilder { new UrlReaderProcessor({ reader, logger }), new CodeOwnersProcessor({ reader }), new LocationRefProcessor(), - new OwnerRelationProcessor(), new AnnotateLocationEntityProcessor(), ); } From 3aa7efb3ff18078f8e236763d45225e48721ef8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 25 Nov 2020 10:51:35 +0100 Subject: [PATCH 053/195] backend-common: add support for passing false as a CSP field value, to drop it from the defaults (#3437) --- .changeset/few-kings-agree.md | 5 ++ packages/backend-common/config.d.ts | 11 +++- .../service/lib/ServiceBuilderImpl.test.ts | 36 +++++++++++++ .../src/service/lib/ServiceBuilderImpl.ts | 45 ++++++++++------ .../src/service/lib/config.test.ts | 51 +++++++++++++++++++ .../backend-common/src/service/lib/config.ts | 17 +++++-- 6 files changed, 143 insertions(+), 22 deletions(-) create mode 100644 .changeset/few-kings-agree.md create mode 100644 packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts create mode 100644 packages/backend-common/src/service/lib/config.test.ts diff --git a/.changeset/few-kings-agree.md b/.changeset/few-kings-agree.md new file mode 100644 index 0000000000..0019611503 --- /dev/null +++ b/.changeset/few-kings-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Added support for passing false as a CSP field value, to drop it from the defaults in the backend diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 7a134ac506..c4ec84ab62 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -79,8 +79,15 @@ export interface Config { optionsSuccessStatus?: number; }; - /** */ - csp?: object; + /** + * Content Security Policy options. + * + * The keys are the plain policy ID, e.g. "upgrade-insecure-requests". The + * values are on the format that the helmet library expects them, as an + * array of strings. There is also the special value false, which means to + * remove the default value that Backstage puts in place for that policy. + */ + csp?: { [policyId: string]: string[] | false }; }; /** Configuration for integrations towards various external repository provider systems */ diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts new file mode 100644 index 0000000000..cffb0a88de --- /dev/null +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { applyCspDirectives } from './ServiceBuilderImpl'; + +describe('ServiceBuilderImpl', () => { + describe('applyCspDirectives', () => { + it('copies actual values', () => { + const result = applyCspDirectives({ key: ['value'] }); + expect(result).toEqual( + expect.objectContaining({ + 'default-src': ["'self'"], + key: ['value'], + }), + ); + }); + + it('removes false value keys', () => { + const result = applyCspDirectives({ 'upgrade-insecure-requests': false }); + expect(result!['upgrade-insecure-requests']).toBeUndefined(); + }); + }); +}); diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 710de6f1d7..d8b9ffa2d9 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -18,7 +18,7 @@ import { Config } from '@backstage/config'; import compression from 'compression'; import cors from 'cors'; import express, { Router } from 'express'; -import helmet from 'helmet'; +import helmet, { HelmetOptions } from 'helmet'; import * as http from 'http'; import stoppable from 'stoppable'; import { Logger } from 'winston'; @@ -56,7 +56,7 @@ const DEFAULT_CSP = { 'script-src': ["'self'"], 'script-src-attr': ["'none'"], 'style-src': ["'self'", 'https:', "'unsafe-inline'"], - 'upgrade-insecure-requests': [], + 'upgrade-insecure-requests': [] as string[], }; export class ServiceBuilderImpl implements ServiceBuilder { @@ -64,7 +64,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { private host: string | undefined; private logger: Logger | undefined; private corsOptions: cors.CorsOptions | undefined; - private cspOptions: CspOptions | undefined; + private cspOptions: Record | undefined; private httpsSettings: HttpsSettings | undefined; private enableMetrics: boolean = true; private routers: [string, Router][]; @@ -154,20 +154,11 @@ export class ServiceBuilderImpl implements ServiceBuilder { host, logger, corsOptions, - cspOptions, httpsSettings, + helmetOptions, } = this.getOptions(); - app.use( - helmet({ - contentSecurityPolicy: { - directives: { - ...DEFAULT_CSP, - ...cspOptions, - }, - }, - }), - ); + app.use(helmet(helmetOptions)); if (corsOptions) { app.use(cors(corsOptions)); } @@ -214,16 +205,38 @@ export class ServiceBuilderImpl implements ServiceBuilder { host: string; logger: Logger; corsOptions?: cors.CorsOptions; - cspOptions?: CspOptions; httpsSettings?: HttpsSettings; + helmetOptions: HelmetOptions; } { return { port: this.port ?? DEFAULT_PORT, host: this.host ?? DEFAULT_HOST, logger: this.logger ?? getRootLogger(), corsOptions: this.corsOptions, - cspOptions: this.cspOptions, httpsSettings: this.httpsSettings, + helmetOptions: { + contentSecurityPolicy: { + directives: applyCspDirectives(this.cspOptions), + }, + }, }; } } + +export function applyCspDirectives( + directives: Record | undefined, +): CspOptions | undefined { + const result: CspOptions = { ...DEFAULT_CSP }; + + if (directives) { + for (const [key, value] of Object.entries(directives)) { + if (value === false) { + delete result[key]; + } else { + result[key] = value; + } + } + } + + return result; +} diff --git a/packages/backend-common/src/service/lib/config.test.ts b/packages/backend-common/src/service/lib/config.test.ts new file mode 100644 index 0000000000..8a35f147ca --- /dev/null +++ b/packages/backend-common/src/service/lib/config.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { readCspOptions } from './config'; + +describe('config', () => { + describe('readCspOptions', () => { + it('reads valid values', () => { + const config = ConfigReader.fromConfigs([ + { context: '', data: { csp: { key: ['value'] } } }, + ]); + expect(readCspOptions(config)).toEqual( + expect.objectContaining({ + key: ['value'], + }), + ); + }); + + it('accepts false', () => { + const config = ConfigReader.fromConfigs([ + { context: '', data: { csp: { key: false } } }, + ]); + expect(readCspOptions(config)).toEqual( + expect.objectContaining({ + key: false, + }), + ); + }); + + it('rejects invalid value types', () => { + const config = ConfigReader.fromConfigs([ + { context: '', data: { csp: { key: [4] } } }, + ]); + expect(() => readCspOptions(config)).toThrow(/wanted string-array/); + }); + }); +}); diff --git a/packages/backend-common/src/service/lib/config.ts b/packages/backend-common/src/service/lib/config.ts index bfd966b499..541170b704 100644 --- a/packages/backend-common/src/service/lib/config.ts +++ b/packages/backend-common/src/service/lib/config.ts @@ -134,24 +134,33 @@ export function readCorsOptions(config: Config): CorsOptions | undefined { * Attempts to read a CSP options object from the root of a config object. * * @param config The root of a backend config object - * @returns A CSP options object, or undefined if not specified + * @returns A CSP options object, or undefined if not specified. Values can be + * false as well, which means to remove the default behavior for that + * key. * * @example * ```yaml * backend: * csp: * connect-src: ["'self'", 'http:', 'https:'] + * upgrade-insecure-requests: false * ``` */ -export function readCspOptions(config: Config): CspOptions | undefined { +export function readCspOptions( + config: Config, +): Record | undefined { const cc = config.getOptionalConfig('csp'); if (!cc) { return undefined; } - const result: CspOptions = {}; + const result: Record = {}; for (const key of cc.keys()) { - result[key] = cc.getStringArray(key); + if (cc.get(key) === false) { + result[key] = false; + } else { + result[key] = cc.getStringArray(key); + } } return result; From 76ff21c2d5c02e34357143a350820b8eb77075a0 Mon Sep 17 00:00:00 2001 From: Askar Date: Wed, 25 Nov 2020 11:06:39 +0100 Subject: [PATCH 054/195] fix(search): compile possible filter options from available entities (#3370) * load filters from entities * fix(search): compile possible filter options from available entities * rename filter state and add text in case no filter can be applied --- .../search/src/components/Filters/Filters.tsx | 116 ++++++++++-------- .../components/SearchResult/SearchResult.tsx | 45 +++++-- 2 files changed, 97 insertions(+), 64 deletions(-) diff --git a/plugins/search/src/components/Filters/Filters.tsx b/plugins/search/src/components/Filters/Filters.tsx index 19d4e53d72..7bd344e12e 100644 --- a/plugins/search/src/components/Filters/Filters.tsx +++ b/plugins/search/src/components/Filters/Filters.tsx @@ -49,8 +49,14 @@ export type FiltersState = { checked: Array; }; +export type FilterOptions = { + kind: Array; + lifecycle: Array; +}; + type FiltersProps = { filters: FiltersState; + filterOptions: FilterOptions; resetFilters: () => void; updateSelected: (filter: string) => void; updateChecked: (filter: string) => void; @@ -58,16 +64,13 @@ type FiltersProps = { export const Filters = ({ filters, + filterOptions, resetFilters, updateSelected, updateChecked, }: FiltersProps) => { const classes = useStyles(); - // TODO: move mocked filters out of filters component to make it more generic - const filter1 = ['All', 'API', 'Component', 'Location', 'Template']; - const filter2 = ['deprecated', 'recommended', 'experimental', 'production']; - return ( - - Kind - - - - Lifecycle - - {filter2.map(filter => ( - updateChecked(filter)} - > - + + Filters cannot be applied to available results + + + )} + {filterOptions.kind.length > 0 && ( + + Kind + + + )} + {filterOptions.lifecycle.length > 0 && ( + + Lifecycle + + {filterOptions.lifecycle.map(filter => ( + updateChecked(filter)} + > + + + + ))} + + + )} ); }; diff --git a/plugins/search/src/components/SearchResult/SearchResult.tsx b/plugins/search/src/components/SearchResult/SearchResult.tsx index f1e160e792..9fd54ac06c 100644 --- a/plugins/search/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search/src/components/SearchResult/SearchResult.tsx @@ -126,7 +126,7 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { const catalogApi = useApi(catalogApiRef); const [showFilters, toggleFilters] = useState(false); - const [filters, setFilters] = useState({ + const [selectedFilters, setSelectedFilters] = useState({ selected: 'All', checked: [], }); @@ -146,17 +146,18 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { // apply filters // filter on selected - if (filters.selected !== 'All') { + if (selectedFilters.selected !== 'All') { withFilters = results.filter((result: Result) => - filters.selected.includes(result.kind), + selectedFilters.selected.includes(result.kind), ); } // filter on checked - if (filters.checked.length > 0) { + if (selectedFilters.checked.length > 0) { withFilters = withFilters.filter( (result: Result) => - result.lifecycle && filters.checked.includes(result.lifecycle), + result.lifecycle && + selectedFilters.checked.includes(result.lifecycle), ); } @@ -174,7 +175,7 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { setFilteredResults(withFilters); } - }, [filters, searchQuery, results]); + }, [selectedFilters, searchQuery, results]); if (loading) { return ; } @@ -190,41 +191,58 @@ export const SearchResult = ({ searchQuery }: SearchResultProps) => { } const resetFilters = () => { - setFilters({ + setSelectedFilters({ selected: 'All', checked: [], }); }; const updateSelected = (filter: string) => { - setFilters(prevState => ({ + setSelectedFilters(prevState => ({ ...prevState, selected: filter, })); }; const updateChecked = (filter: string) => { - if (filters.checked.includes(filter)) { - setFilters(prevState => ({ + if (selectedFilters.checked.includes(filter)) { + setSelectedFilters(prevState => ({ ...prevState, checked: prevState.checked.filter(item => item !== filter), })); return; } - setFilters(prevState => ({ + setSelectedFilters(prevState => ({ ...prevState, checked: [...prevState.checked, filter], })); }; + const filterOptions = results.reduce( + (acc, curr) => { + if (curr.kind && acc.kind.indexOf(curr.kind) < 0) { + acc.kind.push(curr.kind); + } + if (curr.lifecycle && acc.lifecycle.indexOf(curr.lifecycle) < 0) { + acc.lifecycle.push(curr.lifecycle); + } + return acc; + }, + { + kind: [] as Array, + lifecycle: [] as Array, + }, + ); + return ( <> {showFilters && ( { searchQuery={searchQuery} numberOfResults={filteredResults.length} numberOfSelectedFilters={ - (filters.selected !== 'All' ? 1 : 0) + filters.checked.length + (selectedFilters.selected !== 'All' ? 1 : 0) + + selectedFilters.checked.length } handleToggleFilters={() => toggleFilters(!showFilters)} /> From 9b9e86f8afd3ec593aca08f0599a832c7b6748f7 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 25 Nov 2020 14:30:52 +0100 Subject: [PATCH 055/195] export oidcAuthRef --- .changeset/honest-gifts-push.md | 5 +++++ .github/styles/vocab.txt | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/honest-gifts-push.md diff --git a/.changeset/honest-gifts-push.md b/.changeset/honest-gifts-push.md new file mode 100644 index 0000000000..ad2df98a6e --- /dev/null +++ b/.changeset/honest-gifts-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-api': patch +--- + +export oidc provider diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index c31e19e35c..a0841fe7d0 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -131,6 +131,7 @@ npm nvm oauth Oauth +oidc Okta Oldsberg onboarding From a1f6e07d325e840eefc6f2c2b0ac252282f9467a Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 25 Nov 2020 14:32:49 +0100 Subject: [PATCH 056/195] created release log --- .changeset/honest-gifts-push.md | 5 ----- packages/core-api/CHANGELOG.md | 6 ++++++ packages/core-api/package.json | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 .changeset/honest-gifts-push.md diff --git a/.changeset/honest-gifts-push.md b/.changeset/honest-gifts-push.md deleted file mode 100644 index ad2df98a6e..0000000000 --- a/.changeset/honest-gifts-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-api': patch ---- - -export oidc provider diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index b936715c70..63926daf24 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core-api +## 0.2.2 + +### Patch Changes + +- 9b9e86f8a: export oidc provider + ## 0.2.1 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 212d4c9046..c39dca3b7a 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.1", + "version": "0.2.2", "private": false, "publishConfig": { "access": "public", From fb19e0241dc831ba238457df10d8f89234cc9c49 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:01:34 +0100 Subject: [PATCH 057/195] Add providesApis and consumesApis to component spec --- .../software-catalog/descriptor-format.md | 21 +++++++++- docs/features/software-catalog/references.md | 4 +- .../src/kinds/ComponentEntityV1alpha1.test.ts | 42 +++++++++++++++++++ .../src/kinds/ComponentEntityV1alpha1.ts | 4 ++ plugins/api-docs/README.md | 2 +- .../BuiltinKindsEntityProcessor.test.ts | 36 +++++++++++++++- .../processors/BuiltinKindsEntityProcessor.ts | 14 +++++++ 7 files changed, 118 insertions(+), 5 deletions(-) diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index f1d426f407..c86be1be83 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -381,7 +381,7 @@ spec: type: website lifecycle: production owner: artist-relations@example.com - implementsApis: + providesApis: - artist-api ``` @@ -451,6 +451,25 @@ is optional. The software catalog expects a list of one or more strings that references the names of other entities of the `kind` `API`. +This field has the same behavior as `spec.providesApis` and will be deprecated +in the future. + +### `spec.providesApis` [optional] + +Links APIs that are provided by the component, e.g. `artist-api`. This field is +optional. + +The software catalog expects a list of one or more strings that references the +names of other entities of the `kind` `API`. + +### `spec.consumesApis` [optional] + +Links APIs that are consumed by the component, e.g. `artist-api`. This field is +optional. + +The software catalog expects a list of one or more strings that references the +names of other entities of the `kind` `API`. + ## Kind: Template Describes the following entity kind: diff --git a/docs/features/software-catalog/references.md b/docs/features/software-catalog/references.md index e438ddc977..d347035950 100644 --- a/docs/features/software-catalog/references.md +++ b/docs/features/software-catalog/references.md @@ -51,7 +51,7 @@ spec: type: service lifecycle: experimental owner: group:pet-managers - implementsApis: + providesApis: - petstore - internal/streetlights - hello-world @@ -66,7 +66,7 @@ catalog that is of kind `Group`, namespace `default` (which, actually, also can be left out in its own yaml file because that's the default value there too), and name `pet-managers`. -The entries in `implementsApis` are also references. In this case, none of them +The entries in `providesApis` are also references. In this case, none of them needs to specify a kind since we know from the context that that's the only kind that's supported here. The second entry specifies a namespace but the other ones don't, and in this context, the default is to refer to the same namespace as the diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts index 64f9d05978..8eec63daac 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.test.ts @@ -34,6 +34,8 @@ describe('ComponentV1alpha1Validator', () => { lifecycle: 'production', owner: 'me', implementsApis: ['api-0'], + providesApis: ['api-0'], + consumesApis: ['api-0'], }, }; }); @@ -121,4 +123,44 @@ describe('ComponentV1alpha1Validator', () => { (entity as any).spec.implementsApis = []; await expect(validator.check(entity)).resolves.toBe(true); }); + + it('accepts missing providesApis', async () => { + delete (entity as any).spec.providesApis; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty providesApis', async () => { + (entity as any).spec.providesApis = ['']; + await expect(validator.check(entity)).rejects.toThrow(/providesApis/); + }); + + it('rejects undefined providesApis', async () => { + (entity as any).spec.providesApis = [undefined]; + await expect(validator.check(entity)).rejects.toThrow(/providesApis/); + }); + + it('accepts no providesApis', async () => { + (entity as any).spec.providesApis = []; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('accepts missing consumesApis', async () => { + delete (entity as any).spec.consumesApis; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects empty consumesApis', async () => { + (entity as any).spec.consumesApis = ['']; + await expect(validator.check(entity)).rejects.toThrow(/consumesApis/); + }); + + it('rejects undefined consumesApis', async () => { + (entity as any).spec.consumesApis = [undefined]; + await expect(validator.check(entity)).rejects.toThrow(/consumesApis/); + }); + + it('accepts no consumesApis', async () => { + (entity as any).spec.consumesApis = []; + await expect(validator.check(entity)).resolves.toBe(true); + }); }); diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 0e1e369e44..97c9140e61 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -30,6 +30,8 @@ const schema = yup.object>({ lifecycle: yup.string().required().min(1), owner: yup.string().required().min(1), implementsApis: yup.array(yup.string().required()).notRequired(), + providesApis: yup.array(yup.string().required()).notRequired(), + consumesApis: yup.array(yup.string().required()).notRequired(), kubernetes: yup .object({ selector: yup @@ -51,6 +53,8 @@ export interface ComponentEntityV1alpha1 extends Entity { lifecycle: string; owner: string; implementsApis?: string[]; + providesApis?: string[]; + consumesApis?: string[]; kubernetes?: { selector: { matchLabels: { diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index ecc0e5a560..f5b9a79a85 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -21,7 +21,7 @@ Right now, the following API formats are supported: Other formats are displayed as plain text, but this can easily be extended. To fill the catalog with APIs, [provide entities of kind API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api). -To link that an component implements an API, see [`implementsApis` property on components](https://backstage.io/docs/features/software-catalog/descriptor-format#specimplementsapis-optional). +To link that an component provides or consumes an API, see [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) property on components. ## Links diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index 94d35cc109..e456dfd7a2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -68,12 +68,14 @@ describe('BuiltinKindsEntityProcessor', () => { owner: 'o', lifecycle: 'l', implementsApis: ['a'], + providesApis: ['b'], + consumesApis: ['c'], }, }; await processor.postProcessEntity(entity, location, emit); - expect(emit).toBeCalledTimes(4); + expect(emit).toBeCalledTimes(8); expect(emit).toBeCalledWith({ type: 'relation', relation: { @@ -106,6 +108,38 @@ describe('BuiltinKindsEntityProcessor', () => { target: { kind: 'API', namespace: 'default', name: 'a' }, }, }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'b' }, + type: 'apiProvidedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'providesApi', + target: { kind: 'API', namespace: 'default', name: 'b' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'API', namespace: 'default', name: 'c' }, + type: 'apiConsumedBy', + target: { kind: 'Component', namespace: 'default', name: 'n' }, + }, + }); + expect(emit).toBeCalledWith({ + type: 'relation', + relation: { + source: { kind: 'Component', namespace: 'default', name: 'n' }, + type: 'consumesApi', + target: { kind: 'API', namespace: 'default', name: 'c' }, + }, + }); }); it('generates relations for api entities', async () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index a4e62e5b8c..62b496dd65 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -26,8 +26,10 @@ import { locationEntityV1alpha1Validator, LocationSpec, parseEntityRef, + RELATION_API_CONSUMED_BY, RELATION_API_PROVIDED_BY, RELATION_CHILD_OF, + RELATION_CONSUMES_API, RELATION_HAS_MEMBER, RELATION_MEMBER_OF, RELATION_OWNED_BY, @@ -138,6 +140,18 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { RELATION_PROVIDES_API, RELATION_API_PROVIDED_BY, ); + doEmit( + component.spec.providesApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_PROVIDES_API, + RELATION_API_PROVIDED_BY, + ); + doEmit( + component.spec.consumesApis, + { defaultKind: 'API', defaultNamespace: selfRef.namespace }, + RELATION_CONSUMES_API, + RELATION_API_CONSUMED_BY, + ); } /* From ab94c9542fdc32fe9407daad9f477dbface59099 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:03:02 +0100 Subject: [PATCH 058/195] Add changeset --- .changeset/good-cycles-shave.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/good-cycles-shave.md diff --git a/.changeset/good-cycles-shave.md b/.changeset/good-cycles-shave.md new file mode 100644 index 0000000000..90a9410b9c --- /dev/null +++ b/.changeset/good-cycles-shave.md @@ -0,0 +1,6 @@ +--- +'@backstage/catalog-model': patch +'@backstage/plugin-catalog-backend': patch +--- + +Add `providesApis` and `consumesApis` to the component entity spec. From 069cda35f43c1a775e98d07bf548eccb1d075821 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:21:08 +0100 Subject: [PATCH 059/195] Deprecate implementsApis --- .changeset/short-singers-serve.md | 15 +++++++++++++++ .../software-catalog/descriptor-format.md | 8 ++++++-- .../src/kinds/ComponentEntityV1alpha1.ts | 5 +++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .changeset/short-singers-serve.md diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md new file mode 100644 index 0000000000..ae8e8d171c --- /dev/null +++ b/.changeset/short-singers-serve.md @@ -0,0 +1,15 @@ +--- +'@backstage/catalog-model': patch +--- + +Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. + +Code that consumes these fields should remove those usages as soon as possible as migrate to using +relations instead. Producers should fill the field `spec.providesApis` instead, which has the same +semantic. + +After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At +the first release after that, they will not be present in released packages either. + +If your catalog-info.yaml files still contain this fields after the deletion, they will still be +valid and your ingestion will not break, but they won't be visible in the types for consuming code. diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index c86be1be83..438f874990 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -445,14 +445,18 @@ group of people in an organizational structure. ### `spec.implementsApis` [optional] +**NOTE**: This field was marked for deprecation on Nov 25nd, 2020. It will be +removed entirely from the model on Dec 14th, 2020 in the repository and will not +be present in released packages following the next release after that. Please +update your code to not consume this field before the removal date. + Links APIs that are implemented by the component, e.g. `artist-api`. This field is optional. The software catalog expects a list of one or more strings that references the names of other entities of the `kind` `API`. -This field has the same behavior as `spec.providesApis` and will be deprecated -in the future. +This field has the same behavior as `spec.providesApis`. ### `spec.providesApis` [optional] diff --git a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts index 97c9140e61..5fd4f88537 100644 --- a/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/ComponentEntityV1alpha1.ts @@ -52,6 +52,11 @@ export interface ComponentEntityV1alpha1 extends Entity { type: string; lifecycle: string; owner: string; + /** + * @deprecated This field will disappear on Dec 14th, 2020. Please remove + * any consuming code. The new field providesApis provides the + * same functionality like before. + */ implementsApis?: string[]; providesApis?: string[]; consumesApis?: string[]; From bd9578870d3787c1f0b03e6647b6bff6a564fc1f Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 25 Nov 2020 11:13:59 -0500 Subject: [PATCH 060/195] use ES2020 --- packages/cli/config/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index 62609aad5b..a55b215f74 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -11,7 +11,7 @@ "incremental": true, "isolatedModules": true, "jsx": "react", - "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2019", "ESNext.Promise"], + "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020", "ESNext.Promise"], "module": "ESNext", "moduleResolution": "node", "noEmit": false, From ff1301d28f1d5567ecf65ee21b67f45cd0ccba78 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 16:52:29 +0100 Subject: [PATCH 061/195] Warn if app-backend can't start-up because missing dir --- .changeset/brave-rats-obey.md | 5 +++++ plugins/app-backend/src/service/router.ts | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 .changeset/brave-rats-obey.md diff --git a/.changeset/brave-rats-obey.md b/.changeset/brave-rats-obey.md new file mode 100644 index 0000000000..a09d922763 --- /dev/null +++ b/.changeset/brave-rats-obey.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Warn if the app-backend can't start-up because the static directory that should be served is unavailable. diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index b435ec15f1..65cbddaae6 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -21,6 +21,7 @@ import { Logger } from 'winston'; import { notFoundHandler, resolvePackagePath } from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { injectConfig, readConfigs } from '../lib/config'; +import fs from 'fs-extra'; export interface RouterOptions { config: Config; @@ -35,9 +36,18 @@ export async function createRouter( const { config, logger, appPackageName, staticFallbackHandler } = options; const appDistDir = resolvePackagePath(appPackageName, 'dist'); - logger.info(`Serving static app content from ${appDistDir}`); const staticDir = resolvePath(appDistDir, 'static'); + if (!(await fs.pathExists(staticDir))) { + logger.warn( + `Can't serve static app content from ${staticDir}, directory doesn't exist`, + ); + + return Router(); + } + + logger.info(`Serving static app content from ${appDistDir}`); + const appConfigs = await readConfigs({ config, appDistDir, From 6f70ed7a95e94d40e2eb5634b851c0ea6487e664 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 25 Nov 2020 17:23:54 +0100 Subject: [PATCH 062/195] Replace usage of implementsApis with relations --- .changeset/fifty-nails-pay.md | 6 ++++++ .changeset/short-singers-serve.md | 2 +- .../MissingImplementsApisEmptyState.tsx | 7 +++---- plugins/api-docs/src/catalog/Router.tsx | 5 +++-- .../api-docs/src/components/useComponentApiNames.ts | 12 ++++++++++-- .../catalog/src/components/AboutCard/AboutCard.tsx | 7 +++++-- 6 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 .changeset/fifty-nails-pay.md diff --git a/.changeset/fifty-nails-pay.md b/.changeset/fifty-nails-pay.md new file mode 100644 index 0000000000..5880d9a8cc --- /dev/null +++ b/.changeset/fifty-nails-pay.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-api-docs': patch +'@backstage/plugin-catalog': patch +--- + +Replace usage of implementsApis with relations diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index ae8e8d171c..c2f1c1914f 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -4,7 +4,7 @@ Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. -Code that consumes these fields should remove those usages as soon as possible as migrate to using +Code that consumes these fields should remove those usages as soon as possible and migrate to using relations instead. Producers should fill the field `spec.providesApis` instead, which has the same semantic. diff --git a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx index 9160aca514..f13848bd19 100644 --- a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx +++ b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx @@ -28,7 +28,7 @@ spec: type: service lifecycle: production owner: guest - implementsApis: + providesApis: - example-api `; @@ -49,8 +49,7 @@ export const MissingImplementsApisEmptyState = () => { description={ Components can implement APIs that are displayed on this page. You - need to fill the implementsApis field to enable this - tool. + need to fill the providesApis field to enable this tool. } action={ @@ -71,7 +70,7 @@ export const MissingImplementsApisEmptyState = () => { diff --git a/plugins/api-docs/src/catalog/Router.tsx b/plugins/api-docs/src/catalog/Router.tsx index d3fe7f7553..6991ad4b57 100644 --- a/plugins/api-docs/src/catalog/Router.tsx +++ b/plugins/api-docs/src/catalog/Router.tsx @@ -15,14 +15,15 @@ */ import React from 'react'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, RELATION_PROVIDES_API } from '@backstage/catalog-model'; import { Route, Routes } from 'react-router'; import { catalogRoute } from '../routes'; import { EntityPageApi } from './EntityPageApi'; import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; const isPluginApplicableToEntity = (entity: Entity) => { - return ((entity.spec?.implementsApis as string[]) || []).length > 0; + // TODO: Als support RELATION_CONSUMES_API + return entity.relations?.some(r => r.type === RELATION_PROVIDES_API); }; export const Router = ({ entity }: { entity: Entity }) => diff --git a/plugins/api-docs/src/components/useComponentApiNames.ts b/plugins/api-docs/src/components/useComponentApiNames.ts index 0eabe2b6c7..1303967895 100644 --- a/plugins/api-docs/src/components/useComponentApiNames.ts +++ b/plugins/api-docs/src/components/useComponentApiNames.ts @@ -14,8 +14,16 @@ * limitations under the License. */ -import { ComponentEntity } from '@backstage/catalog-model'; +import { + ComponentEntity, + RELATION_PROVIDES_API, +} from '@backstage/catalog-model'; export const useComponentApiNames = (entity: ComponentEntity) => { - return (entity.spec?.implementsApis as string[]) || []; + // TODO: This code doesn't handle namespaces and kinds correctly, but will be removed soon + return ( + entity.relations + ?.filter(r => r.type === RELATION_PROVIDES_API) + ?.map(r => r.target.name) || [] + ); }; diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 89630d6f10..49653fe981 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -18,6 +18,7 @@ import { Entity, ENTITY_DEFAULT_NAMESPACE, RELATION_OWNED_BY, + RELATION_PROVIDES_API, serializeEntityRef, } from '@backstage/catalog-model'; import { @@ -111,6 +112,8 @@ type AboutCardProps = { export function AboutCard({ entity, variant }: AboutCardProps) { const classes = useStyles(); const codeLink = getCodeLinkInfo(entity); + // TODO: Also support RELATION_CONSUMES_API here + const hasApis = entity.relations?.some(r => r.type === RELATION_PROVIDES_API); return ( @@ -146,9 +149,9 @@ export function AboutCard({ entity, variant }: AboutCardProps) { }/${entity.kind}/${entity.metadata.name}`} /> } href="api" /> From 266e93b47831b2c058612d4ee643b1a0df4e8030 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 24 Nov 2020 23:29:18 +0100 Subject: [PATCH 063/195] TechDocs: Refactor metadata retrieval 1. Don't use mkdocs in name of APIs or variables. It is an implementation detail and liable to change in future. 2. techDocsApi.getMetadata('mkdocs') and techDocsAPI.getMetadata('entity') should be two separate functions just because their responses differ in structure. They should also be type checked. 3. Use either 'techdocs' or 'TechDocs' consistently. 'techDocs' seems like an unnecessary third way to write TechDocs, which can be avoided. 4. Remove unused /plugins/techdocs-backend/src/service/metadata.ts file --- .../techdocs-backend/src/service/metadata.ts | 41 ------------------- .../techdocs-backend/src/service/router.ts | 6 +-- plugins/techdocs/src/api.ts | 36 ++++++++++++++-- .../reader/components/TechDocsPage.test.tsx | 11 ++--- .../src/reader/components/TechDocsPage.tsx | 14 +++---- .../components/TechDocsPageHeader.test.tsx | 4 +- .../reader/components/TechDocsPageHeader.tsx | 11 +++-- 7 files changed, 57 insertions(+), 66 deletions(-) delete mode 100644 plugins/techdocs-backend/src/service/metadata.ts diff --git a/plugins/techdocs-backend/src/service/metadata.ts b/plugins/techdocs-backend/src/service/metadata.ts deleted file mode 100644 index 760180f8d2..0000000000 --- a/plugins/techdocs-backend/src/service/metadata.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 fetch from 'cross-fetch'; - -export class TechDocsMetadata { - private async getMetadataFile(docsUrl: String) { - const metadataURL = `${docsUrl}/techdocs_metadata.json`; - - try { - const req = await fetch(metadataURL); - - return await req.json(); - } catch (error) { - throw new Error(error); - } - } - - public async getMkDocsMetaData(docsUrl: any) { - const mkDocsMetadata = await this.getMetadataFile(docsUrl); - - if (!mkDocsMetadata) return null; - - return { - ...mkDocsMetadata, - }; - } -} diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 1d932a1fb9..625252dd7e 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -61,7 +61,7 @@ export async function createRouter({ }: RouterOptions): Promise { const router = Router(); - router.get('/metadata/mkdocs/*', async (req, res) => { + router.get('/metadata/techdocs/*', async (req, res) => { let storageUrl = config.getString('techdocs.storageUrl'); if (publisher instanceof LocalPublish) { storageUrl = new URL( @@ -74,8 +74,8 @@ export async function createRouter({ const metadataURL = `${storageUrl}/${path}/techdocs_metadata.json`; try { - const mkDocsMetadata = await (await fetch(metadataURL)).json(); - res.send(mkDocsMetadata); + const techdocsMetadata = await (await fetch(metadataURL)).json(); + res.send(techdocsMetadata); } catch (err) { logger.info(`Unable to get metadata for ${path} with error ${err}`); throw new Error(`Unable to get metadata for ${path} with error ${err}`); diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 7281ebdf13..98e1e6c310 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -15,7 +15,6 @@ */ import { createApiRef } from '@backstage/core'; - import { ParsedEntityId } from './types'; export const techdocsStorageApiRef = createApiRef({ @@ -38,7 +37,8 @@ export interface TechDocsStorage { } export interface TechDocs { - getMetadata(metadataType: string, entityId: ParsedEntityId): Promise; + getTechDocsMetadata(entityId: ParsedEntityId): Promise; + getEntityMetadata(entityId: ParsedEntityId): Promise; } /** @@ -53,10 +53,38 @@ export class TechDocsApi implements TechDocs { this.apiOrigin = apiOrigin; } - async getMetadata(metadataType: string, entityId: ParsedEntityId) { + /** + * Retrieve TechDocs metadata. + * + * When docs are built, we generate a techdocs_metadata.json and store it along with the generated + * static files. It includes necessary data about the docs site. This method requests techdocs-backend + * which retries the TechDocs metadata. + * + * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + */ + async getTechDocsMetadata(entityId: ParsedEntityId) { const { kind, namespace, name } = entityId; - const requestUrl = `${this.apiOrigin}/metadata/${metadataType}/${namespace}/${kind}/${name}`; + const requestUrl = `${this.apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`; + + const request = await fetch(`${requestUrl}`); + const res = await request.json(); + + return res; + } + + /** + * Retrieve metadata about an entity. + * + * This method requests techdocs-backend which uses the catalog APIs to respond with filtered + * information required here. + * + * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + */ + async getEntityMetadata(entityId: ParsedEntityId) { + const { kind, namespace, name } = entityId; + + const requestUrl = `${this.apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`; const request = await fetch(`${requestUrl}`); const res = await request.json(); diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx index 464115bab7..c05c0488ff 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx @@ -50,17 +50,18 @@ describe('', () => { entityId: 'Component::backstage', }); - const techDocsApi: Partial = { - getMetadata: () => Promise.resolve([]), + const techdocsApi: Partial = { + getEntityMetadata: () => Promise.resolve([]), + getTechDocsMetadata: () => Promise.resolve([]), }; - const techDocsStorageApi: Partial = { + const techdocsStorageApi: Partial = { getEntityDocs: (): Promise => Promise.resolve('String'), getBaseUrl: (): string => '', }; const apiRegistry = ApiRegistry.from([ - [techdocsApiRef, techDocsApi], - [techdocsStorageApiRef, techDocsStorageApi], + [techdocsApiRef, techdocsApi], + [techdocsStorageApiRef, techdocsStorageApi], ]); await act(async () => { diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.tsx index 00f0b3c9d3..f2635c0dfb 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPage.tsx @@ -26,19 +26,19 @@ export const TechDocsPage = () => { const [documentReady, setDocumentReady] = useState(false); const { namespace, kind, name } = useParams(); - const techDocsApi = useApi(techdocsApiRef); + const techdocsApi = useApi(techdocsApiRef); - const mkdocsMetadataRequest = useAsync(() => { + const techdocsMetadataRequest = useAsync(() => { if (documentReady) { - return techDocsApi.getMetadata('mkdocs', { kind, namespace, name }); + return techdocsApi.getTechDocsMetadata({ kind, namespace, name }); } return Promise.resolve({ loading: true }); - }, [kind, namespace, name, techDocsApi, documentReady]); + }, [kind, namespace, name, techdocsApi, documentReady]); const entityMetadataRequest = useAsync(() => { - return techDocsApi.getMetadata('entity', { kind, namespace, name }); - }, [kind, namespace, name, techDocsApi]); + return techdocsApi.getEntityMetadata({ kind, namespace, name }); + }, [kind, namespace, name, techdocsApi]); const onReady = () => { setDocumentReady(true); @@ -48,7 +48,7 @@ export const TechDocsPage = () => { ', () => { }, }, }, - mkdocs: { + techdocs: { loading: false, value: { site_name: 'test-site-name', @@ -73,7 +73,7 @@ describe('', () => { entity: { loading: false, }, - mkdocs: { + techdocs: { loading: false, }, }} diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx index f4c447bae2..475b87da6f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx @@ -25,7 +25,7 @@ type TechDocsPageHeaderProps = { entityId: ParsedEntityId; metadataRequest: { entity: AsyncState; - mkdocs: AsyncState; + techdocs: AsyncState; }; }; @@ -33,15 +33,18 @@ export const TechDocsPageHeader = ({ entityId, metadataRequest, }: TechDocsPageHeaderProps) => { - const { mkdocs: mkdocsMetadata, entity: entityMetadata } = metadataRequest; + const { + techdocs: techdocsMetadata, + entity: entityMetadata, + } = metadataRequest; - const { value: mkDocsMetadataValues } = mkdocsMetadata; + const { value: techdocsMetadataValues } = techdocsMetadata; const { value: entityMetadataValues } = entityMetadata; const { kind, name } = entityId; const { site_name: siteName, site_description: siteDescription } = - mkDocsMetadataValues || {}; + techdocsMetadataValues || {}; const { locationMetadata, From 039970d7106c537f8d51cb0b9eda1e575b9bb7b4 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Wed, 25 Nov 2020 16:28:16 -0500 Subject: [PATCH 064/195] introduce data.type=config_info for the new postMessage in auth --- packages/core-api/src/lib/loginPopup.test.ts | 2 ++ packages/core-api/src/lib/loginPopup.ts | 2 +- plugins/auth-backend/src/lib/flow/authFlowHelpers.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index 6527618ef7..6b3b49c781 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -161,6 +161,7 @@ describe('showLoginPopup', () => { source: popupMock, origin: 'origin', data: { + type: 'config_info', targetOrigin: 'http://localhost', }, } as MessageEvent); @@ -198,6 +199,7 @@ describe('showLoginPopup', () => { source: popupMock, origin: 'origin', data: { + type: 'config_info', targetOrigin: 'http://differenthost', }, } as MessageEvent); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 5c6d424363..00a5efce66 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -95,7 +95,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { } const { data } = event; - if (data.targetOrigin) { + if (data.type === 'config_info') { targetOrigin = data.targetOrigin; return; } diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts index 5aba427905..22ffc7af88 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts @@ -53,7 +53,7 @@ export const postMessageResponse = ( const script = ` var authResponse = decodeURIComponent('${base64Data}'); var origin = decodeURIComponent('${base64Origin}'); - var originInfo = {'targetOrigin': origin}; + var originInfo = {'type': 'config_info', 'targetOrigin': origin}; (window.opener || window.parent).postMessage(originInfo, '*'); (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin); window.close(); From a85a1bedd632bfedc11e6fc7cca0022f5ce35351 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Nov 2020 23:45:21 +0100 Subject: [PATCH 065/195] github/workflows: use service account auth for changeset By switching to using a service account instead of the regular token we allowed builds to be triggered in created PR --- .github/workflows/changeset.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml index a557b4921b..4bd03b4d96 100644 --- a/.github/workflows/changeset.yml +++ b/.github/workflows/changeset.yml @@ -19,4 +19,4 @@ jobs: # Calls out to `changeset version`, but also runs prettier version: yarn release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} From feaec380e66292413fb0d21d00e8a0cdbbf24039 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Wed, 25 Nov 2020 18:43:34 -0500 Subject: [PATCH 066/195] improve error message --- packages/core-api/src/lib/loginPopup.test.ts | 2 +- packages/core-api/src/lib/loginPopup.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index 6b3b49c781..98541c268e 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -208,7 +208,7 @@ describe('showLoginPopup', () => { popupMock.closed = true; }, 150); await expect(payloadPromise).rejects.toThrow( - 'Login failed, incorrect app origin', + 'Login failed, Incorrect app origin, expected http://differenthost', ); expect(openSpy).toBeCalledTimes(1); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 00a5efce66..2e14447882 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -121,7 +121,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { if (popup.closed) { const errMessage = `Login failed, ${ targetOrigin !== window.location.origin - ? 'incorrect app origin' + ? `Incorrect app origin, expected ${targetOrigin}` : 'popup was closed' }`; const error = new Error(errMessage); From 700a212b4692857f00a8dd93c04b83d30584da36 Mon Sep 17 00:00:00 2001 From: jothisubaramaniam Date: Wed, 25 Nov 2020 18:44:52 -0500 Subject: [PATCH 067/195] leave the changeset filename as is --- .../{auth-backend-ten-doors-exist.md => ten-doors-exist.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changeset/{auth-backend-ten-doors-exist.md => ten-doors-exist.md} (100%) diff --git a/.changeset/auth-backend-ten-doors-exist.md b/.changeset/ten-doors-exist.md similarity index 100% rename from .changeset/auth-backend-ten-doors-exist.md rename to .changeset/ten-doors-exist.md From f03cbc879cf6da601d0b51619dacad093710a77d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Nov 2020 05:09:16 +0000 Subject: [PATCH 068/195] build(deps): bump eslint-plugin-react from 7.19.0 to 7.21.5 Bumps [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) from 7.19.0 to 7.21.5. - [Release notes](https://github.com/yannickcr/eslint-plugin-react/releases) - [Changelog](https://github.com/yannickcr/eslint-plugin-react/blob/master/CHANGELOG.md) - [Commits](https://github.com/yannickcr/eslint-plugin-react/compare/v7.19.0...v7.21.5) Signed-off-by: dependabot[bot] --- yarn.lock | 132 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 24 deletions(-) diff --git a/yarn.lock b/yarn.lock index 967fbb016d..576db3b66c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1196,7 +1196,7 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.8.3": +"@babel/runtime-corejs3@^7.10.2": version "7.10.3" resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.3.tgz#931ed6941d3954924a7aa967ee440e60c507b91a" integrity sha512-HA7RPj5xvJxQl429r5Cxr2trJwOfPjKiqhCXcdQPSqO2G0RHPZpXu4fkYmBaTKCp2c/jRaMK9GB/lN+7zvvFPw== @@ -8340,6 +8340,14 @@ cachedir@^2.3.0: resolved "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" integrity sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw== +call-bind@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz#24127054bb3f9bdcb4b1fb82418186072f77b8ce" + integrity sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.0" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -10992,6 +11000,23 @@ es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es- string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-abstract@^1.17.5: + version "1.17.7" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" + integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" @@ -11249,22 +11274,21 @@ eslint-plugin-react-hooks@^4.0.0: integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA== eslint-plugin-react@^7.12.4: - version "7.19.0" - resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" - integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== + version "7.21.5" + resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.5.tgz#50b21a412b9574bfe05b21db176e8b7b3b15bff3" + integrity sha512-8MaEggC2et0wSF6bUeywF7qQ46ER81irOdWS4QWxnnlAEsnzeBevk1sWh7fhpCghPpXb+8Ks7hvaft6L/xsR6g== dependencies: array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" doctrine "^2.1.0" has "^1.0.3" - jsx-ast-utils "^2.2.3" - object.entries "^1.1.1" + jsx-ast-utils "^2.4.1 || ^3.0.0" + object.entries "^1.1.2" object.fromentries "^2.0.2" object.values "^1.1.1" prop-types "^15.7.2" - resolve "^1.15.1" - semver "^6.3.0" + resolve "^1.18.1" string.prototype.matchall "^4.0.2" - xregexp "^4.3.0" eslint-scope@^4.0.3: version "4.0.3" @@ -12436,6 +12460,15 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be" + integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-monorepo-packages@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/get-monorepo-packages/-/get-monorepo-packages-1.2.0.tgz#3eee88d30b11a5f65955dec6ae331958b2a168e4" @@ -14025,6 +14058,11 @@ is-callable@^1.1.4, is-callable@^1.1.5: resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" + integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -14044,6 +14082,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz#a4cc031d9b1aca63eecbd18a650e13cb4eeab946" + integrity sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -15406,7 +15451,7 @@ jss@10.1.1, jss@^10.0.3: is-in-browser "^1.1.3" tiny-warning "^1.0.2" -jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: +jsx-ast-utils@^2.2.1: version "2.2.3" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA== @@ -15414,6 +15459,14 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.1.0" + resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz#642f1d7b88aa6d7eb9d8f2210e166478444fa891" + integrity sha512-d4/UOjg+mxAWxCiF0c5UTSwyqbchkbqCvK87aBovhnh8GtysTjWmgC63tY0cJx/HzGgm9qnA147jVBdpOiQ2RA== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.1" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -17628,6 +17681,11 @@ object-inspect@^1.7.0: resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== +object-inspect@^1.8.0: + version "1.8.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== + object-is@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" @@ -17660,6 +17718,16 @@ object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.assign@^4.1.1: + version "4.1.2" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" @@ -17670,14 +17738,13 @@ object.defaults@^1.1.0: for-own "^1.0.0" isobject "^3.0.0" -object.entries@^1.1.0, object.entries@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" - integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== +object.entries@^1.1.0, object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== dependencies: define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.17.5" has "^1.0.3" "object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2: @@ -20935,13 +21002,21 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.17.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.3.2: +resolve@1.17.0: version "1.17.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.16.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.3.2: + version "1.19.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -22230,6 +22305,14 @@ string.prototype.padstart@^3.0.0: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" +string.prototype.trimend@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" + integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + string.prototype.trimleft@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" @@ -22246,6 +22329,14 @@ string.prototype.trimright@^2.1.1: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimstart@^1.0.1: + version "1.0.3" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" + integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -24620,13 +24711,6 @@ xpath@0.0.27: resolved "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== -xregexp@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50" - integrity sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g== - dependencies: - "@babel/runtime-corejs3" "^7.8.3" - xss@^1.0.6: version "1.0.7" resolved "https://registry.npmjs.org/xss/-/xss-1.0.7.tgz#a554cbd5e909324bd6893fb47fff441ad54e2a95" From faa73389f2c87f7afcd99674c6655a2bf4c628f5 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 06:29:29 +0100 Subject: [PATCH 069/195] Change package versions --- .changeset/smart-turkeys-bathe.md | 2 -- packages/app/package.json | 2 +- packages/backend/package.json | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.changeset/smart-turkeys-bathe.md b/.changeset/smart-turkeys-bathe.md index f7776a81aa..5f429b5b37 100644 --- a/.changeset/smart-turkeys-bathe.md +++ b/.changeset/smart-turkeys-bathe.md @@ -1,8 +1,6 @@ --- '@backstage/plugin-catalog-backend': minor '@backstage/plugin-catalog-import': minor -'example-app': patch -'example-backend': patch '@backstage/catalog-model': patch '@backstage/plugin-scaffolder': patch --- diff --git a/packages/app/package.json b/packages/app/package.json index 0ee1b47e57..52906bb65a 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.2", + "version": "0.2.1", "private": true, "bundled": true, "dependencies": { diff --git a/packages/backend/package.json b/packages/backend/package.json index 5011c21c14..410aca1087 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.2", + "version": "0.2.1", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -35,7 +35,7 @@ "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.2", + "example-app": "^0.2.1", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", From 8e7696d533c31610f66b977c561f7118994c2ea1 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 09:24:30 +0100 Subject: [PATCH 070/195] Fix after merge --- packages/app/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/package.json b/packages/app/package.json index 9e54005cd3..37919b2d31 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -9,6 +9,7 @@ "@backstage/core": "^0.3.2", "@backstage/plugin-api-docs": "^0.2.2", "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog-import": "^0.2.0", "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-cloudbuild": "^0.2.2", "@backstage/plugin-cost-insights": "^0.4.1", From 8bef6b9a5f65e5f4fa33002fe89975f051a62217 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 10:18:45 +0100 Subject: [PATCH 071/195] Fix after merge --- plugins/catalog-import/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ac41d70c30..2a44d7555a 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.2.0", - "@backstage/core": "^0.3.1", + "@backstage/core": "^0.3.2", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.3.0", "@backstage/theme": "^0.2.1", @@ -40,7 +40,7 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.1", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 4b53294a6cb75b145f90cd53e97d13498f05c125 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 26 Nov 2020 00:29:46 +0100 Subject: [PATCH 072/195] TechDocs: Update mkdocs.yml with repo_url when possible before building docs --- .changeset/eleven-lobsters-train.md | 7 + plugins/techdocs-backend/package.json | 2 + .../techdocs-backend/src/service/helpers.ts | 3 + .../stages/generate/__fixtures__/mkdocs.yml | 2 + .../__fixtures__/mkdocs_with_repo_url.yml | 4 + .../techdocs/stages/generate/helpers.test.ts | 197 +++++++++++++++++- .../src/techdocs/stages/generate/helpers.ts | 137 +++++++++++- .../src/techdocs/stages/generate/techdocs.ts | 16 +- .../src/techdocs/stages/generate/types.ts | 15 +- 9 files changed, 373 insertions(+), 10 deletions(-) create mode 100644 .changeset/eleven-lobsters-train.md create mode 100644 plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml create mode 100644 plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml diff --git a/.changeset/eleven-lobsters-train.md b/.changeset/eleven-lobsters-train.md new file mode 100644 index 0000000000..95b9145ee6 --- /dev/null +++ b/.changeset/eleven-lobsters-train.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs': minor +'@backstage/plugin-techdocs-backend': minor +--- + +- Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. +- techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 1d9f3ea7c1..a09adb7380 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -32,7 +32,9 @@ "express-promise-router": "^3.0.3", "fs-extra": "^9.0.1", "git-url-parse": "^11.4.0", + "js-yaml": "^3.14.0", "knex": "^0.21.6", + "mock-fs": "^4.13.0", "nodegit": "^0.27.0", "winston": "^3.2.1" }, diff --git a/plugins/techdocs-backend/src/service/helpers.ts b/plugins/techdocs-backend/src/service/helpers.ts index 3017a68461..4a736f7388 100644 --- a/plugins/techdocs-backend/src/service/helpers.ts +++ b/plugins/techdocs-backend/src/service/helpers.ts @@ -69,10 +69,13 @@ export class DocsBuilder { this.logger.info(`Running preparer on entity ${getEntityId(this.entity)}`); const preparedDir = await this.preparer.prepare(this.entity); + const parsedLocationAnnotation = getLocationForEntity(this.entity); + this.logger.info(`Running generator on entity ${getEntityId(this.entity)}`); const { resultDir } = await this.generator.run({ directory: preparedDir, dockerClient: this.dockerClient, + parsedLocationAnnotation, }); this.logger.info(`Running publisher on entity ${getEntityId(this.entity)}`); diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml new file mode 100644 index 0000000000..1d1186840f --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs.yml @@ -0,0 +1,2 @@ +site_name: Test site name +site_description: Test site description diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml new file mode 100644 index 0000000000..37b004bfe4 --- /dev/null +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/__fixtures__/mkdocs_with_repo_url.yml @@ -0,0 +1,4 @@ +site_name: Test site name +site_description: Test site description + +repo_url: https://github.com/backstage/backstage diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts index 283560f0ce..4938739fdf 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts @@ -13,10 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import Stream, { PassThrough } from 'stream'; +import fs from 'fs'; import os from 'os'; +import { resolve as resolvePath } from 'path'; +import Stream, { PassThrough } from 'stream'; import Docker from 'dockerode'; -import { runDockerContainer, getGeneratorKey } from './helpers'; +import mockFs from 'mock-fs'; +import * as winston from 'winston'; +import { + runDockerContainer, + getGeneratorKey, + isValidRepoUrlForMkdocs, + getRepoUrlFromLocationAnnotation, + patchMkdocsYmlPreBuild, +} from './helpers'; +import { RemoteProtocol } from '../prepare/types'; +import { ParsedLocationAnnotation } from '../../../helpers'; const mockEntity = { apiVersion: 'version', @@ -28,6 +40,14 @@ const mockEntity = { const mockDocker = new Docker() as jest.Mocked; +const mkdocsYml = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs.yml'), +); +const mkdocsYmlWithRepoUrl = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs_with_repo_url.yml'), +); +const mockLogger = winston.createLogger(); + describe('helpers', () => { describe('getGeneratorKey', () => { it('should return techdocs as the only generator key', () => { @@ -138,4 +158,177 @@ describe('helpers', () => { }); }); }); + + describe('isValidRepoUrlForMkdocs', () => { + it('should return true for valid repo_url values for mkdocs', () => { + const validRepoUrls = [ + 'https://github.com/org/repo', + 'https://github.com/backstage/backstage/', + 'https://github.com/org123/repo1-2-3/', + 'http://github.com/insecureOrg/insecureRepo', + 'https://gitlab.com/org/repo', + 'https://gitlab.com/backstage/backstage/', + 'https://gitlab.com/org123/repo1-2-3/', + 'http://gitlab.com/insecureOrg/insecureRepo', + ]; + + const validRemoteProtocols = ['github', 'gitlab']; + + validRepoUrls.forEach(url => { + validRemoteProtocols.forEach(targetType => { + expect( + isValidRepoUrlForMkdocs(url, targetType as RemoteProtocol), + ).toBe(true); + }); + }); + }); + + it('should return false for invalid repo_urls values for mkdocs', () => { + const invalidRepoUrls = [ + 'git@github.com:org/repo', + 'https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend', + ]; + + invalidRepoUrls.forEach(url => { + expect(isValidRepoUrlForMkdocs(url, 'github')).toBe(false); + }); + }); + + it('should return false for unsupported remote protocols', () => { + const validRepoUrl = 'https://github.com/backstage/backstage'; + + const unsupportedRemoteProtocols = ['dir', 'file', 'url']; + + unsupportedRemoteProtocols.forEach(targetType => { + expect( + isValidRepoUrlForMkdocs(validRepoUrl, targetType as RemoteProtocol), + ).toBe(false); + }); + }); + }); + + describe('getRepoUrlFromLocationAnnotation', () => { + it('should return undefined for unsupported location type', () => { + const parsedLocationAnnotation1: ParsedLocationAnnotation = { + type: 'dir', + target: '/home/user/workspace/docs-repository', + }; + + const parsedLocationAnnotation2: ParsedLocationAnnotation = { + type: 'file', + target: '/home/user/workspace/docs-repository/catalog-info.yaml', + }; + + const parsedLocationAnnotation3: ParsedLocationAnnotation = { + type: 'url', + target: 'https://my-website.com/storage/this/docs/repository', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe( + undefined, + ); + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe( + undefined, + ); + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe( + undefined, + ); + }); + + it('should return correct target url for supported hosts', () => { + const parsedLocationAnnotation1: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/backstage/backstage.git', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation1)).toBe( + 'https://github.com/backstage/backstage', + ); + + const parsedLocationAnnotation2: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/org/repo', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation2)).toBe( + 'https://github.com/org/repo', + ); + + const parsedLocationAnnotation3: ParsedLocationAnnotation = { + type: 'gitlab', + target: 'https://gitlab.com/org/repo', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation3)).toBe( + 'https://gitlab.com/org/repo', + ); + + const parsedLocationAnnotation4: ParsedLocationAnnotation = { + type: 'github', + target: + 'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component', + }; + + expect(getRepoUrlFromLocationAnnotation(parsedLocationAnnotation4)).toBe( + 'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component', + ); + }); + }); + + describe('pathMkdocsPreBuild', () => { + beforeEach(() => { + mockFs({ + '/mkdocs.yml': mkdocsYml, + '/mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('should add repo_url to mkdocs.yml', () => { + const parsedLocationAnnotation: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/backstage/backstage', + }; + + patchMkdocsYmlPreBuild( + '/mkdocs.yml', + mockLogger, + parsedLocationAnnotation, + ); + + const updatedMkdocsYml = fs.readFileSync('/mkdocs.yml').toString(); + + expect(updatedMkdocsYml).toContain( + "repo_url: 'https://github.com/backstage/backstage'", + ); + }); + + it('should not override existing repo_url in mkdocs.yml', () => { + const parsedLocationAnnotation: ParsedLocationAnnotation = { + type: 'github', + target: 'https://github.com/neworg/newrepo', + }; + + patchMkdocsYmlPreBuild( + '/mkdocs_with_repo_url.yml', + mockLogger, + parsedLocationAnnotation, + ); + + const updatedMkdocsYml = fs + .readFileSync('/mkdocs_with_repo_url.yml') + .toString(); + + expect(updatedMkdocsYml).toContain( + "repo_url: 'https://github.com/backstage/backstage'", + ); + expect(updatedMkdocsYml).not.toContain( + "repo_url: 'https://github.com/neworg/newrepo'", + ); + }); + }); }); diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts index d0ffa72f1c..254186b484 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts @@ -14,11 +14,16 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import fs from 'fs'; +import { spawn } from 'child_process'; import { Writable, PassThrough } from 'stream'; import Docker from 'dockerode'; +import yaml from 'js-yaml'; +import { Logger } from 'winston'; +import { Entity } from '@backstage/catalog-model'; import { SupportedGeneratorKey } from './types'; -import { spawn } from 'child_process'; +import { ParsedLocationAnnotation } from '../../../helpers'; +import { RemoteProtocol } from '../prepare/types'; // TODO: Implement proper support for more generators. export function getGeneratorKey(entity: Entity): SupportedGeneratorKey { @@ -142,3 +147,131 @@ export const runCommand = async ({ }); }); }; + +/** + * Return true if mkdocs can compile docs with provided repo_url + * + * Valid repo_url examples in mkdocs.yml + * - https://github.com/backstage/backstage + * - https://gitlab.com/org/repo/ + * - http://github.com/backstage/backstage + * - A http(s) protocol URL to the root of the repository + * + * Invalid repo_url examples in mkdocs.yml + * - https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component + * - (anything that is not valid as described above) + * + * @param {string} repoUrl URL supposed to be used as repo_url in mkdocs.yml + * @param {RemoteProtocol} locationType Type of source code host - github, gitlab, dir, url, etc. + * @returns {boolean} + */ +export const isValidRepoUrlForMkdocs = ( + repoUrl: string, + locationType: RemoteProtocol, +): boolean => { + // Trim trailing slash + const cleanRepoUrl = repoUrl.replace(/\/$/, ''); + + if (locationType === 'github' || locationType === 'gitlab') { + // A valid repoUrl to the root of the repository will be split into 5 strings if split using the / delimiter. + // We do not want URLs which have more than that number of forward slashes since they will signify a non-root location + // Note: This is not the best possible implementation but will work most of the times.. Feel free to improve or + // highlight edge cases. + return cleanRepoUrl.split('/').length === 5; + } + + return false; +}; + +/** + * Return a valid URL of the repository used in backstage.io/techdocs-ref annotation. + * Return undefined if the `target` is not valid in context of repo_url in mkdocs.yml + * Alter URL so that it is a valid repo_url config in mkdocs.yml + * + * @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type + * @returns {string | undefined} + */ +export const getRepoUrlFromLocationAnnotation = ( + parsedLocationAnnotation: ParsedLocationAnnotation, +): string | undefined => { + const { type: locationType, target } = parsedLocationAnnotation; + + // Add more options from the RemoteProtocol type of parsedLocationAnnotation.type here + // when TechDocs supports more hosts and if mkdocs can generated an Edit URL for them. + const supportedHosts = ['github', 'gitlab']; + + if (supportedHosts.includes(locationType)) { + // Trim .git or .git/ from the end of repository url + return target.replace(/.git\/*$/, ''); + } + + return undefined; +}; + +/** + * Update the mkdocs.yml file before TechDocs generator uses it to build docs site. + * + * List of tasks: + * - Add repo_url if it does not exists + * If mkdocs.yml has a repo_url, the generated docs site gets an Edit button on the pages by default. + * If repo_url is missing in mkdocs.yml, we will use techdocs annotation of the entity to possibly get + * the repository URL. + * + * This function will not throw an error since this is not critical to the whole TechDocs pipeline. + * Instead it will log warnings if there are any errors in reading, parsing or writing YAML. + * + * @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site + * @param {Logger} logger + * @param {ParsedLocationAnnotation} parsedLocationAnnotation Object with location url and type + */ +export const patchMkdocsYmlPreBuild = async ( + mkdocsYmlPath: string, + logger: Logger, + parsedLocationAnnotation: ParsedLocationAnnotation, +) => { + let mkdocsYmlFileString; + try { + mkdocsYmlFileString = fs.readFileSync(mkdocsYmlPath, 'utf8'); + } catch (error) { + logger.warn( + `Could not read file ${mkdocsYmlPath} before running the generator. ${error.message}`, + ); + return; + } + + let mkdocsYml: any; + try { + mkdocsYml = yaml.safeLoad(mkdocsYmlFileString); + + // mkdocsYml should be an object type after successful parsing. + // But based on its type definition, it can also be a string or undefined, which we don't want. + if (typeof mkdocsYml === 'string' || typeof mkdocsYml === 'undefined') { + throw new Error('Bad YAML format.'); + } + } catch (error) { + logger.warn( + `Error in parsing YAML at ${mkdocsYmlPath} before running the generator. ${error.message}`, + ); + return; + } + + // Add repo_url to mkdocs.yml if it is missing. This will enable the Page edit button generated by MkDocs. + if (!('repo_url' in mkdocsYml)) { + const repoUrl = getRepoUrlFromLocationAnnotation(parsedLocationAnnotation); + if (repoUrl !== undefined) { + // mkdocs.yml will not build with invalid repo_url. So, make sure it is valid. + if (isValidRepoUrlForMkdocs(repoUrl, parsedLocationAnnotation.type)) { + mkdocsYml.repo_url = repoUrl; + } + } + } + + try { + fs.writeFileSync(mkdocsYmlPath, yaml.safeDump(mkdocsYml), 'utf8'); + } catch (error) { + logger.warn( + `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`, + ); + return; + } +}; diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts index 1160ff1ade..faefd8ac16 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/techdocs.ts @@ -26,7 +26,11 @@ import { GeneratorRunOptions, GeneratorRunResult, } from './types'; -import { runDockerContainer, runCommand } from './helpers'; +import { + runDockerContainer, + runCommand, + patchMkdocsYmlPreBuild, +} from './helpers'; type TechdocsGeneratorOptions = { // This option enables users to configure if they want to use TechDocs container @@ -62,6 +66,7 @@ export class TechdocsGenerator implements GeneratorBase { public async run({ directory, dockerClient, + parsedLocationAnnotation, }: GeneratorRunOptions): Promise { const tmpdirPath = os.tmpdir(); // Fixes a problem with macOS returning a path that is a symlink @@ -71,6 +76,15 @@ export class TechdocsGenerator implements GeneratorBase { ); const [log, logStream] = createStream(); + // TODO: In future mkdocs.yml can be mkdocs.yaml. So, use a config variable here to find out + // the correct file name. + // Do some updates to mkdocs.yml before generating docs e.g. adding repo_url + await patchMkdocsYmlPreBuild( + path.join(directory, 'mkdocs.yml'), + this.logger, + parsedLocationAnnotation, + ); + try { switch (this.options.runGeneratorIn) { case 'local': diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts index 398e4dcdd0..6d9ce5afca 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/types.ts @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import type { Writable } from 'stream'; +import { Writable } from 'stream'; import Docker from 'dockerode'; import { Entity } from '@backstage/catalog-model'; +import { ParsedLocationAnnotation } from '../../../helpers'; /** * The returned directory from the generator which is ready @@ -26,14 +27,18 @@ export type GeneratorRunResult = { }; /** - * The values that the generator will receive. The directory of the - * uncompiled documentation, with the values from the frontend. A dedicated log stream and a docker - * client to run any generator on top of your directory. + * The values that the generator will receive. + * + * @param {string} directory The directory of the uncompiled documentation, with the values from the frontend + * @param {Docker} dockerClient A docker client to run any generator on top of your directory + * @param {ParsedLocationAnnotation} parsedLocationAnnotation backstage.io/techdocs-ref annotation of an entity + * @param {Writable} [logStream] A dedicated log stream */ export type GeneratorRunOptions = { directory: string; - logStream?: Writable; dockerClient: Docker; + parsedLocationAnnotation: ParsedLocationAnnotation; + logStream?: Writable; }; export type GeneratorBase = { From 202da8ab36ed6942c10db994031329ec0a4f5e77 Mon Sep 17 00:00:00 2001 From: Govind Date: Thu, 26 Nov 2020 11:27:00 +0100 Subject: [PATCH 073/195] Allow application to register custom `AuthProviderFactory`. Refs #1984 (#3329) * Allow application to register custom `AuthProviderFactory`. Refs #1984 * PR comments: Explicit exports * Export `OAuth` specific types from the package * Update documentation on how to use the interfaces * Re-export from base `index.ts` as per ADR-004 * pass `providerId` as a parameter to `AuthFactory` instead of hardcoding Co-authored-by: Govindarajan Nagarajan --- docs/auth/auth-backend-classes.md | 118 +++++++++++++----- plugins/auth-backend/src/index.ts | 8 ++ plugins/auth-backend/src/lib/flow/index.ts | 2 + .../src/providers/auth0/provider.ts | 2 +- .../auth-backend/src/providers/factories.ts | 16 +-- .../src/providers/github/provider.ts | 2 +- .../src/providers/gitlab/provider.ts | 2 +- .../src/providers/google/provider.ts | 2 +- plugins/auth-backend/src/providers/index.ts | 14 ++- .../src/providers/microsoft/provider.ts | 3 +- .../src/providers/oauth2/provider.ts | 2 +- .../src/providers/oidc/provider.ts | 2 +- .../src/providers/okta/provider.ts | 2 +- .../src/providers/onelogin/provider.ts | 2 +- .../src/providers/saml/provider.ts | 2 +- plugins/auth-backend/src/providers/types.ts | 1 + plugins/auth-backend/src/service/router.ts | 29 ++++- 17 files changed, 148 insertions(+), 61 deletions(-) diff --git a/docs/auth/auth-backend-classes.md b/docs/auth/auth-backend-classes.md index 442553d4b8..ae6602e62a 100644 --- a/docs/auth/auth-backend-classes.md +++ b/docs/auth/auth-backend-classes.md @@ -6,45 +6,59 @@ description: Documentation on Auth backend classes ## How Does Authentication Work? -The Backstage application can use various authentication providers for -authentication. A provider has to implement an `AuthProviderRouteHandlers` -interface for handling authentication. This interface consists of four methods. -Each of these methods is hosted at an endpoint `/auth/[provider]/method`, where -`method` performs a certain operation as follows: +The Backstage application can use various external authentication providers for +authentication. An external provider is wrapped using an +`AuthProviderRouteHandlers` interface for handling authentication. This +interface consists of four methods. Each of these methods is hosted at an +endpoint (by default)`/api/auth/[provider]/method`, where `method` performs a +certain operation as follows: ``` - /auth/[provider]/start -> start - /auth/[provider]/handler/frame -> frameHandler - /auth/[provider]/refresh -> refresh - /auth/[provider]/logout -> logout + /auth/[provider]/start -> Initiate a login from the web page + /auth/[provider]/handler/frame -> Handle a `finished` authentication operation + /auth/[provider]/refresh -> refresh -> Refresh the validity of a login + /auth/[provider]/logout -> logout a logged-in user ``` -For more information on how these methods are used and for which purpose, refer -to the [OAuth documentation](oauth.md). +The flow is as follows: -For details on the parameters, input and output conditions for each method, -refer to the type documentation under -`plugins/auth-backend/src/providers/types.ts`. +1. `User` attempts to sign-in. +2. The `auth` wrapper re-directs the user to an external authenticator. +3. The authenticator validates the user and returns the result of the validation + (success OR failure), to the wrapper's endpoint (`handler/frame`) +4. `handler/frame` will issue the appropriate response to the webpage. +5. User signs out by clicking on an UI interface and the webpage makes a request + to logout the user. There are currently two different classes for two authentication mechanisms that implement this interface: an `OAuthAdapter` for [OAuth](https://oauth.net/2/) based mechanisms and a `SAMLAuthProvider` for [SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) + +If you do not have a `OAuth2` or a `SAML` based authentication provider, look in +the section [below](#Implementing-Your-Own-Auth-Wrapper) + based mechanisms. ### OAuth mechanisms +For more information on how these methods are used and for which purpose, refer +to the [OAuth documentation](oauth.md). + Currently OAuth is assumed to be the de facto authentication mechanism for Backstage based applications. Backstage comes with a "batteries-included" set of supported commonly used OAuth -providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. +providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. For a +list of available providers, look at the available wrappers in +`backstage/plugins/auth-backend/src/providers/` -All of these use the authorization flow of OAuth2 to implement authentication. +All of these use the **authorization flow** of OAuth2 to implement +authentication. -If your authentication provider is any of the above mentioned (except generic -OAuth2) providers, you can configure them by setting the right variables in -`app-config.yaml` under the `auth` section. +If your authentication provider is any of the above mentioned providers, you can +configure them by setting the right variables in `app-config.yaml` under the +`auth` section. ### Configuration @@ -85,18 +99,59 @@ auth: ... ``` -## Technical Notes +## Implementing Your Own Auth Wrapper -### OAuthEnvironmentHandler +The core interface of any Auth wrapper is the `AuthProviderRouteHandlers` +interface. This interface has 4 methods corresponding to API described in the +initial section. Any Auth Wrapper will have to implement this interface. -The concept of an "env" is core to the way the auth backend works. It uses an +When initiating a login, a pop-up window is created by the frontend, to allow +the user to initiate a login. This login request is done to the `/start` +endpoint which is handled by the `start` method. + +The `start` method re-directs to the external auth provider who authenticates +the request and re-directs the request to the `/frame/handler` endpoint, which +is handled by the `frameHandler` method. + +The `frameHandler` returns a `html` response, containing a script that does a +`postMessage` to the frontend's window containing the result of the request. The +`WebMessageResponse` type is the message sent by the `postMessage` to the +frontend. + +A `postMessageResponse` utility function wraps the logic of generating a +`postMessage` response that ensures that `CORS` is successfully handled. This +function takes an `express.Response`, a `WebMessageResponse` and the URL of the +frontend (`appOrigin`) as parameters and return a `HTML` page with the script +and the message + +### OAuth wrapping Interfaces. + +Each OAuth external provider is supported by a corresponding `Passport` +strategy. For a generic OAuth2 provider, passport has a `passport-oauth2` +strategy. The strategy class handles the implementation details of working with +each provider. + +Each strategy is wrapped by a `OAuthHandlers` interface. + +This interface cannot be directly used as an `express` HTTP Request handler. + +To do so, `OAuthHandlers` are wrapped in a `OAuthAdapter`, which implements the +`AuthProviderRouterHandlers` interface. + +#### Env + +The concept of an `env` is core to the way the auth backend works. It uses an `env` query parameter to identify the environment in which the application is -running (`development`, `staging`, `production`, etc). Each runtime can support -multiple environments at the same time and the right handler for each request is -identified and dispatched to based on the `env` parameter. All -`AuthProviderRouteHandlers` are wrapped within an `OAuthEnvironmentHandler`. +running (`development`, `staging`, `production`, etc). Each runtime can +simultaneously support multiple environments at the same time and the right +handler for each request is identified and dispatched to, based on the `env` +parameter. -To instantiate multiple OAuth providers for different environments, use +`OAuthEnvironmentHandler` is a utility wrapper for an `OAuthHandlers` that +implements the `AuthProviderRouteHandlers` interface while supporting multiple +`env`s. + +To instantiate OAuth providers (the same but for different environments), use `OAuthEnvironmentHandler.mapConfig`. It's a helper to iterate over a configuration object that is a map of environment to configurations. See one of the existing OAuth providers for an example of how it is used. @@ -116,8 +171,13 @@ The `OAuthEnvironmentHandler.mapConfig(config, envConfig => ...)` call will split the `config` by the top level `development` and `production` keys, and pass on each block as `envConfig`. -For a list of currently available providers, look in the `factories` module -located in `plugins/auth-backend/src/providers/factories.ts` +For convenience, the `AuthProviderFactory` is a factory function that has to be +implemented which can then generate a `AuthProviderRouteHandlers` for a given +`provider`. + +All of the supported providers provide a `AuthProviderFactory` that returns a +`OAuthEnvironmentHandler` ,capable of handling authentication for multiple +`env`s. ### OAuth2 provider diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index 7612c392a2..3d3f059dcc 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -15,3 +15,11 @@ */ export * from './service/router'; +export * from './providers'; + +// flow package provides 2 functions +// ensuresXRequestedWith and postMessageResponse to safely handle CORS requests for login. The WebMessageResponse type in flow is used to type the response from the login-popup +export * from './lib/flow'; + +// OAuth wrapper over a passport or a custom `startegy`. +export * from './lib/oauth'; diff --git a/plugins/auth-backend/src/lib/flow/index.ts b/plugins/auth-backend/src/lib/flow/index.ts index a5f2f7a3ac..7f0627994f 100644 --- a/plugins/auth-backend/src/lib/flow/index.ts +++ b/plugins/auth-backend/src/lib/flow/index.ts @@ -15,3 +15,5 @@ */ export { ensuresXRequestedWith, postMessageResponse } from './authFlowHelpers'; + +export type { WebMessageResponse } from './types'; diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index f851fb1eb5..8772842e2a 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -149,12 +149,12 @@ export class Auth0AuthProvider implements OAuthHandlers { } export const createAuth0Provider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'auth0'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const domain = envConfig.getString('domain'); diff --git a/plugins/auth-backend/src/providers/factories.ts b/plugins/auth-backend/src/providers/factories.ts index d4a29c0959..7bad4f4d81 100644 --- a/plugins/auth-backend/src/providers/factories.ts +++ b/plugins/auth-backend/src/providers/factories.ts @@ -24,9 +24,9 @@ import { createSamlProvider } from './saml'; import { createAuth0Provider } from './auth0'; import { createMicrosoftProvider } from './microsoft'; import { createOneLoginProvider } from './onelogin'; -import { AuthProviderFactory, AuthProviderFactoryOptions } from './types'; +import { AuthProviderFactory } from './types'; -const factories: { [providerId: string]: AuthProviderFactory } = { +export const factories: { [providerId: string]: AuthProviderFactory } = { google: createGoogleProvider, github: createGithubProvider, gitlab: createGitlabProvider, @@ -38,15 +38,3 @@ const factories: { [providerId: string]: AuthProviderFactory } = { oidc: createOidcProvider, onelogin: createOneLoginProvider, }; - -export function createAuthProvider( - providerId: string, - options: AuthProviderFactoryOptions, -) { - const factory = factories[providerId]; - if (!factory) { - throw Error(`No auth provider available for '${providerId}'`); - } - - return factory(options); -} diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index bab7b3fc28..6e3f9a6000 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -137,12 +137,12 @@ export class GithubAuthProvider implements OAuthHandlers { } export const createGithubProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'github'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const enterpriseInstanceUrl = envConfig.getOptionalString( diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 4d4ecc3b56..df6d2daeb7 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -140,12 +140,12 @@ export class GitlabAuthProvider implements OAuthHandlers { } export const createGitlabProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'gitlab'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const audience = envConfig.getString('audience'); diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index dd5b51eb01..ae4f5fabd0 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -175,6 +175,7 @@ export class GoogleAuthProvider implements OAuthHandlers { } export const createGoogleProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, logger, @@ -182,7 +183,6 @@ export const createGoogleProvider: AuthProviderFactory = ({ catalogApi, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'google'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 99348438be..3f3d16f28f 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -14,4 +14,16 @@ * limitations under the License. */ -export { createAuthProvider } from './factories'; +export { factories as defaultAuthProviderFactories } from './factories'; + +// Export the minimal interface required for implementing a +// custom Authorization Handler +export type { + AuthProviderRouteHandlers, + AuthProviderFactoryOptions, + AuthProviderFactory, +} from './types'; + +// These types are needed for a postMessage from the login pop-up +// to the frontend +export type { AuthResponse, BackstageIdentity, ProfileInfo } from './types'; diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index a09d5871d2..b0c3635be8 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -206,13 +206,12 @@ export class MicrosoftAuthProvider implements OAuthHandlers { } export const createMicrosoftProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'microsoft'; - const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const tenantID = envConfig.getString('tenantId'); diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 6342d556e1..04cb7670f3 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -157,12 +157,12 @@ export class OAuth2AuthProvider implements OAuthHandlers { } export const createOAuth2Provider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'oauth2'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index fbf38c69c4..9e44a82c37 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -171,12 +171,12 @@ export class OidcAuthProvider implements OAuthHandlers { } export const createOidcProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'oidc'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 09597696ba..84b258e489 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -168,12 +168,12 @@ export class OktaAuthProvider implements OAuthHandlers { } export const createOktaProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'okta'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const audience = envConfig.getString('audience'); diff --git a/plugins/auth-backend/src/providers/onelogin/provider.ts b/plugins/auth-backend/src/providers/onelogin/provider.ts index 97cf182e5a..838b655d59 100644 --- a/plugins/auth-backend/src/providers/onelogin/provider.ts +++ b/plugins/auth-backend/src/providers/onelogin/provider.ts @@ -147,12 +147,12 @@ export class OneLoginProvider implements OAuthHandlers { } export const createOneLoginProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const providerId = 'onelogin'; const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const issuer = envConfig.getString('issuer'); diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index a8edc9a714..7b663a5528 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -121,12 +121,12 @@ type SAMLProviderOptions = { }; export const createSamlProvider: AuthProviderFactory = ({ + providerId, globalConfig, config, tokenIssuer, }) => { const url = new URL(globalConfig.baseUrl); - const providerId = 'saml'; const entryPoint = config.getString('entryPoint'); const issuer = config.getString('issuer'); const opts = { diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index 0e500e027e..a40f2a96ee 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -113,6 +113,7 @@ export interface AuthProviderRouteHandlers { } export type AuthProviderFactoryOptions = { + providerId: string; globalConfig: AuthProviderConfig; config: Config; logger: Logger; diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 47e0d14caa..85d60661e9 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -14,6 +14,14 @@ * limitations under the License. */ +import express from 'express'; +import Router from 'express-promise-router'; +import cookieParser from 'cookie-parser'; +import { Logger } from 'winston'; +import { + defaultAuthProviderFactories, + AuthProviderFactory, +} from '../providers'; import { NotFoundError, PluginDatabaseManager, @@ -21,20 +29,18 @@ import { } from '@backstage/backend-common'; import { CatalogClient } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; -import cookieParser from 'cookie-parser'; -import express from 'express'; -import Router from 'express-promise-router'; -import { Logger } from 'winston'; import { createOidcRouter, DatabaseKeyStore, TokenFactory } from '../identity'; -import { createAuthProvider } from '../providers'; import session from 'express-session'; import passport from 'passport'; +type ProviderFactories = { [s: string]: AuthProviderFactory }; + export interface RouterOptions { logger: Logger; database: PluginDatabaseManager; config: Config; discovery: PluginEndpointDiscovery; + providerFactories?: ProviderFactories; } export async function createRouter({ @@ -42,6 +48,7 @@ export async function createRouter({ config, discovery, database, + providerFactories, }: RouterOptions): Promise { const router = Router(); @@ -74,13 +81,23 @@ export async function createRouter({ router.use(express.urlencoded({ extended: false })); router.use(express.json()); + const allProviderFactories = { + ...defaultAuthProviderFactories, + ...providerFactories, + }; const providersConfig = config.getConfig('auth.providers'); const providers = providersConfig.keys(); for (const providerId of providers) { logger.info(`Configuring provider, ${providerId}`); try { - const provider = createAuthProvider(providerId, { + const providerFactory = allProviderFactories[providerId]; + if (!providerFactory) { + throw Error(`No auth provider available for '${providerId}'`); + } + + const provider = providerFactory({ + providerId, globalConfig: { baseUrl: authUrl, appUrl }, config: providersConfig.getConfig(providerId), logger, From 01ea1cc6ccb65c57bff206a2c4e616f63897014a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 11:27:51 +0100 Subject: [PATCH 074/195] Clarify options in Governance (#3455) --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 65841d1c6a..0ab8d8390d 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,6 +1,6 @@ # Process for becoming a maintainer -## Your organization is not yet a maintainer +## a) Your organization is not yet a maintainer - Express interest to the sponsors that your organization is interested in becoming a maintainer. Becoming a maintainer generally means that you are going to be spending substantial time on Backstage for the foreseeable future. You should have domain expertise and be extremely proficient in TypeScript. - We will expect you to start contributing increasingly complicated PRs, under the guidance of the existing maintainers. @@ -8,7 +8,7 @@ - As you gain experience with the code base and our standards, we will ask you to do code reviews for incoming PRs. - After a period of approximately 2-3 months of working together and making sure we see eye to eye, the existing sponsors and maintainers will confer and decide whether to grant maintainer status or not. We make no guarantees on the length of time this will take, but 2-3 months is the approximate goal. -## Your organization is currently a maintainer +## b) Your organization is currently a maintainer To become a maintainer you need to demonstrate the following: From 0dedb26e10e4972951c1c7d9c6cdf72c07bb086e Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 11:54:20 +0100 Subject: [PATCH 075/195] Revert catalog-backend version to 0.2.2 --- packages/backend/package.json | 2 +- plugins/catalog-backend/package.json | 2 +- plugins/catalog-import/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index b9daf224f0..5d3678a284 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -23,7 +23,7 @@ "@backstage/config": "^0.1.1", "@backstage/plugin-app-backend": "^0.3.0", "@backstage/plugin-auth-backend": "^0.2.3", - "@backstage/plugin-catalog-backend": "^0.3.0", + "@backstage/plugin-catalog-backend": "^0.2.2", "@backstage/plugin-graphql-backend": "^0.1.3", "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.1", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d3c77db6dc..26f7180d43 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.3.0", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 2a44d7555a..8efa765224 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -24,7 +24,7 @@ "@backstage/catalog-model": "^0.2.0", "@backstage/core": "^0.3.2", "@backstage/plugin-catalog": "^0.2.0", - "@backstage/plugin-catalog-backend": "^0.3.0", + "@backstage/plugin-catalog-backend": "^0.2.2", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", From 3660463e2a5120d4a225b44a7ef64788d89305d8 Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 26 Nov 2020 13:00:42 +0100 Subject: [PATCH 076/195] chore: use path.join to fix master build on windows --- packages/config-loader/src/lib/schema/collect.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/config-loader/src/lib/schema/collect.test.ts b/packages/config-loader/src/lib/schema/collect.test.ts index 7aca4f7c0c..2427ab994d 100644 --- a/packages/config-loader/src/lib/schema/collect.test.ts +++ b/packages/config-loader/src/lib/schema/collect.test.ts @@ -16,6 +16,7 @@ import mockFs from 'mock-fs'; import { collectConfigSchemas } from './collect'; +import path from 'path'; const mockSchema = { type: 'object', @@ -56,7 +57,7 @@ describe('collectConfigSchemas', () => { await expect(collectConfigSchemas(['a'])).resolves.toEqual([ { - path: 'node_modules/a/package.json', + path: path.join('node_modules', 'a', 'package.json'), value: mockSchema, }, ]); @@ -163,15 +164,15 @@ describe('collectConfigSchemas', () => { await expect(collectConfigSchemas(['a', 'b', 'c'])).resolves.toEqual([ { - path: 'node_modules/a/package.json', + path: path.join('node_modules', 'a', 'package.json'), value: { ...mockSchema, title: 'inline' }, }, { - path: 'node_modules/b/schema.json', + path: path.join('node_modules', 'b', 'schema.json'), value: { ...mockSchema, title: 'external' }, }, { - path: 'node_modules/c/schema.d.ts', + path: path.join('node_modules', 'c', 'schema.d.ts'), value: { $schema: 'http://json-schema.org/draft-07/schema#', type: 'object', From 50eff1d003b50c2982cb429595327545dc128062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 26 Nov 2020 13:14:12 +0100 Subject: [PATCH 077/195] auth-backend: add changeset that was missing (plus minor docs tweaks) (#3458) --- .changeset/beige-mugs-visit.md | 5 ++ docs/auth/auth-backend-classes.md | 101 +++++++++++++++--------------- 2 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 .changeset/beige-mugs-visit.md diff --git a/.changeset/beige-mugs-visit.md b/.changeset/beige-mugs-visit.md new file mode 100644 index 0000000000..4b5f0f1bdd --- /dev/null +++ b/.changeset/beige-mugs-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Allow the backend to register custom AuthProviderFactories diff --git a/docs/auth/auth-backend-classes.md b/docs/auth/auth-backend-classes.md index ae6602e62a..8dbae0daa7 100644 --- a/docs/auth/auth-backend-classes.md +++ b/docs/auth/auth-backend-classes.md @@ -10,37 +10,38 @@ The Backstage application can use various external authentication providers for authentication. An external provider is wrapped using an `AuthProviderRouteHandlers` interface for handling authentication. This interface consists of four methods. Each of these methods is hosted at an -endpoint (by default)`/api/auth/[provider]/method`, where `method` performs a +endpoint (by default) `/api/auth/[provider]/method`, where `method` performs a certain operation as follows: ``` /auth/[provider]/start -> Initiate a login from the web page - /auth/[provider]/handler/frame -> Handle a `finished` authentication operation - /auth/[provider]/refresh -> refresh -> Refresh the validity of a login - /auth/[provider]/logout -> logout a logged-in user + /auth/[provider]/handler/frame -> Handle a finished authentication operation + /auth/[provider]/refresh -> Refresh the validity of a login + /auth/[provider]/logout -> Log out a logged-in user ``` The flow is as follows: -1. `User` attempts to sign-in. -2. The `auth` wrapper re-directs the user to an external authenticator. +1. A user attempts to sign in. +2. A popup window is opened, pointing to the `auth` endpoint. That endpoint does + initial preparations and then re-directs the user to an external + authenticator, still inside the popup. 3. The authenticator validates the user and returns the result of the validation - (success OR failure), to the wrapper's endpoint (`handler/frame`) -4. `handler/frame` will issue the appropriate response to the webpage. -5. User signs out by clicking on an UI interface and the webpage makes a request - to logout the user. + (success OR failure), to the wrapper's endpoint (`handler/frame`). +4. The `handler/frame` rendered b´webpage will issue the appropriate response to + the webpage that opened the popup window, and the popup is closed. +5. The user signs out by clicking on a UI interface and the webpage makes a + request to logout the user. There are currently two different classes for two authentication mechanisms that implement this interface: an `OAuthAdapter` for [OAuth](https://oauth.net/2/) based mechanisms and a `SAMLAuthProvider` for -[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html) +[SAML](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-tech-overview-2.0.html). -If you do not have a `OAuth2` or a `SAML` based authentication provider, look in -the section [below](#Implementing-Your-Own-Auth-Wrapper) +If you do not have an `OAuth2` or `SAML` based authentication provider, look in +the section [below](#implementing-your-own-auth-wrapper). -based mechanisms. - -### OAuth mechanisms +### OAuth Mechanisms For more information on how these methods are used and for which purpose, refer to the [OAuth documentation](oauth.md). @@ -51,7 +52,7 @@ Backstage based applications. Backstage comes with a "batteries-included" set of supported commonly used OAuth providers: Okta, GitHub, Google, GitLab, and a generic OAuth2 provider. For a list of available providers, look at the available wrappers in -`backstage/plugins/auth-backend/src/providers/` +`backstage/plugins/auth-backend/src/providers/`. All of these use the **authorization flow** of OAuth2 to implement authentication. @@ -63,14 +64,13 @@ configure them by setting the right variables in `app-config.yaml` under the ### Configuration Each authentication provider (except SAML) needs five parameters: an OAuth -client ID, a client secret, an authorization endpoint and a token endpoint, and -an app origin. The app origin is the URL at which the frontend of the -application is hosted, and it is read from the `app.baseUrl` config. This is -required because the application opens a popup window to perform the -authentication, and once the flow is completed, the popup window sends a -`postMessage` to the frontend application to indicate the result of the -operation. Also this URL is used to verify that authentication requests are -coming from only this endpoint. +client ID, a client secret, an authorization endpoint, a token endpoint, and an +app origin. The app origin is the URL at which the frontend of the application +is hosted, and it is read from the `app.baseUrl` config. This is required +because the application opens a popup window to perform the authentication, and +once the flow is completed, the popup window sends a `postMessage` to the +frontend application to indicate the result of the operation. Also this URL is +used to verify that authentication requests are coming from only this endpoint. These values are configured via the `app-config.yaml` present in the root of your app folder. @@ -101,9 +101,9 @@ auth: ## Implementing Your Own Auth Wrapper -The core interface of any Auth wrapper is the `AuthProviderRouteHandlers` -interface. This interface has 4 methods corresponding to API described in the -initial section. Any Auth Wrapper will have to implement this interface. +The core interface of any auth wrapper is the `AuthProviderRouteHandlers` +interface. This interface has four methods corresponding to the API described in +the initial section. Any auth wrapper will have to implement this interface. When initiating a login, a pop-up window is created by the frontend, to allow the user to initiate a login. This login request is done to the `/start` @@ -113,29 +113,28 @@ The `start` method re-directs to the external auth provider who authenticates the request and re-directs the request to the `/frame/handler` endpoint, which is handled by the `frameHandler` method. -The `frameHandler` returns a `html` response, containing a script that does a -`postMessage` to the frontend's window containing the result of the request. The -`WebMessageResponse` type is the message sent by the `postMessage` to the +The `frameHandler` returns an HTML response, containing a script that does a +`postMessage` to the frontend's window, containing the result of the request. +The `WebMessageResponse` type is the message sent by the `postMessage` to the frontend. A `postMessageResponse` utility function wraps the logic of generating a -`postMessage` response that ensures that `CORS` is successfully handled. This +`postMessage` response that ensures that CORS is successfully handled. This function takes an `express.Response`, a `WebMessageResponse` and the URL of the -frontend (`appOrigin`) as parameters and return a `HTML` page with the script -and the message +frontend (`appOrigin`) as parameters and return an HTML page with the script and +the message. -### OAuth wrapping Interfaces. +### OAuth Wrapping Interfaces. -Each OAuth external provider is supported by a corresponding `Passport` -strategy. For a generic OAuth2 provider, passport has a `passport-oauth2` -strategy. The strategy class handles the implementation details of working with -each provider. +Each OAuth external provider is supported by a corresponding +[Passport](https://github.com/jaredhanson/passport) strategy. For a generic +OAuth2 provider, passport has a `passport-oauth2` strategy. The strategy class +handles the implementation details of working with each provider. -Each strategy is wrapped by a `OAuthHandlers` interface. +Each strategy is wrapped by an `OAuthHandlers` interface. -This interface cannot be directly used as an `express` HTTP Request handler. - -To do so, `OAuthHandlers` are wrapped in a `OAuthAdapter`, which implements the +This interface cannot be directly used as an Express HTTP request handler. To do +so, `OAuthHandlers` are wrapped in an `OAuthAdapter`, which implements the `AuthProviderRouterHandlers` interface. #### Env @@ -153,7 +152,7 @@ implements the `AuthProviderRouteHandlers` interface while supporting multiple To instantiate OAuth providers (the same but for different environments), use `OAuthEnvironmentHandler.mapConfig`. It's a helper to iterate over a -configuration object that is a map of environment to configurations. See one of +configuration object that is a map of environments to configurations. See one of the existing OAuth providers for an example of how it is used. Given the following configuration: @@ -168,18 +167,18 @@ production: ``` The `OAuthEnvironmentHandler.mapConfig(config, envConfig => ...)` call will -split the `config` by the top level `development` and `production` keys, and -pass on each block as `envConfig`. +split the config by the top level `development` and `production` keys, and pass +on each block as `envConfig`. For convenience, the `AuthProviderFactory` is a factory function that has to be implemented which can then generate a `AuthProviderRouteHandlers` for a given -`provider`. +provider. -All of the supported providers provide a `AuthProviderFactory` that returns a -`OAuthEnvironmentHandler` ,capable of handling authentication for multiple -`env`s. +All of the supported providers provide an `AuthProviderFactory` that returns an +`OAuthEnvironmentHandler`, capable of handling authentication for multiple +environments. -### OAuth2 provider +### OAuth2 Provider The `oauth2` provider abstracts a generic **OAuth2 + OIDC** based authentication provider. What this means is that after the application has been given From a00c393b38bcb794c241a98072fc850374c2ca5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 14:20:47 +0100 Subject: [PATCH 078/195] Add API docs plugin to new apps --- .../configure-app-with-plugins.md | 61 ++++++++++++++++++- .../default-app/packages/app/package.json.hbs | 1 - .../default-app/packages/app/src/plugins.ts | 1 - .../default-app/packages/app/src/sidebar.tsx | 2 + 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 1d02f147c1..8b071fb646 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -6,7 +6,66 @@ description: Documentation on How Configuring App with plugins ## Adding existing plugins to your app -Coming soon! +The following steps assumes you have created a new Backstage app and want to add +an existing plugin to it. We are using the +[CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) +in this example. + +0. Add to the repo: + +```bash +yarn add @backstage/plugin-circleci +``` + +1. Add plugin API to your Backstage instance: + +```js +// packages/app/src/api.ts +import { ApiHolder } from '@backstage/core'; +import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci'; + +const builder = ApiRegistry.builder(); +builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own CircleCI instance */)); + +export default builder.build() as ApiHolder; +``` + +2. Add plugin itself: + +```js +// packages/app/src/plugins.ts +export { plugin as Circleci } from '@backstage/plugin-circleci'; +``` + +3. Register the plugin router: + +```jsx +// packages/app/src/components/catalog/EntityPage.tsx + +import { Router as CircleCIRouter } from '@backstage/plugin-circleci'; + +// Then somewhere inside +} +/>; +``` + +Note that stand-alone plugins that are not "attached" to the Software Catalog +would be added outside the `EntityPage`. + +4. [Optional] Add proxy config: + +```yaml +// app-config.yaml +proxy: + '/circleci/api': + target: https://circleci.com/api/v1.1 + headers: + Circle-Token: + $env: CIRCLECI_AUTH_TOKEN +``` ### Adding a plugin page to the Sidebar diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index fd0d5cbf36..3f04773e31 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -15,7 +15,6 @@ "@backstage/plugin-techdocs": "^{{version '@backstage/plugin-techdocs'}}", "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", "@backstage/plugin-circleci": "^{{version '@backstage/plugin-circleci'}}", - "@backstage/plugin-explore": "^{{version '@backstage/plugin-explore'}}", "@backstage/plugin-lighthouse": "^{{version '@backstage/plugin-lighthouse'}}", "@backstage/plugin-tech-radar": "^{{version '@backstage/plugin-tech-radar'}}", "@backstage/plugin-github-actions": "^{{version '@backstage/plugin-github-actions'}}", diff --git a/packages/create-app/templates/default-app/packages/app/src/plugins.ts b/packages/create-app/templates/default-app/packages/app/src/plugins.ts index b719594a0b..9eabff0044 100644 --- a/packages/create-app/templates/default-app/packages/app/src/plugins.ts +++ b/packages/create-app/templates/default-app/packages/app/src/plugins.ts @@ -3,7 +3,6 @@ export { plugin as CatalogPlugin } from '@backstage/plugin-catalog'; export { plugin as RegisterComponent } from '@backstage/plugin-register-component'; export { plugin as ScaffolderPlugin } from '@backstage/plugin-scaffolder'; export { plugin as TechDocsPlugin } from '@backstage/plugin-techdocs'; -export { plugin as Explore } from '@backstage/plugin-explore'; export { plugin as Circleci } from '@backstage/plugin-circleci'; export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; export { plugin as TechRadar } from '@backstage/plugin-tech-radar'; diff --git a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx index 901347645e..b5425b66b6 100644 --- a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx @@ -1,6 +1,7 @@ import React, { FC, useContext } from 'react'; import HomeIcon from '@material-ui/icons/Home'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; +import ExtensionIcon from '@material-ui/icons/Extension'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import BuildIcon from '@material-ui/icons/BuildRounded'; import RuleIcon from '@material-ui/icons/AssignmentTurnedIn'; @@ -26,6 +27,7 @@ export const AppSidebar = () => ( {/* Global nav, not org-specific */} + From 3a201c5d54a536755a51b89c401ecd1bcbd61ec2 Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Thu, 26 Nov 2020 08:30:12 -0500 Subject: [PATCH 079/195] feat: add config schema for rollbar plugins (#3404) * feat: add config schema for rollbar plugins * remove rollbar from create-app template --- .changeset/gorgeous-games-build.md | 6 +++++ app-config.yaml | 5 ---- .../packages/backend/package.json.hbs | 1 - plugins/rollbar-backend/README.md | 1 - plugins/rollbar-backend/config.d.ts | 26 ++++++++++++++++++ plugins/rollbar-backend/package.json | 6 +++-- plugins/rollbar/README.md | 1 + plugins/rollbar/config.d.ts | 27 +++++++++++++++++++ plugins/rollbar/package.json | 20 +++----------- 9 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 .changeset/gorgeous-games-build.md create mode 100644 plugins/rollbar-backend/config.d.ts create mode 100644 plugins/rollbar/config.d.ts diff --git a/.changeset/gorgeous-games-build.md b/.changeset/gorgeous-games-build.md new file mode 100644 index 0000000000..ff050c43f6 --- /dev/null +++ b/.changeset/gorgeous-games-build.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-rollbar': patch +'@backstage/plugin-rollbar-backend': patch +--- + +Add config schema for the rollbar & rollbar-backend plugins diff --git a/app-config.yaml b/app-config.yaml index fcc54a1a35..546d00466a 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -64,11 +64,6 @@ techdocs: sentry: organization: my-company -rollbar: - organization: my-company - accountToken: - $env: ROLLBAR_ACCOUNT_TOKEN - lighthouse: baseUrl: http://localhost:3003 diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 179d604670..07d1b0be58 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -25,7 +25,6 @@ "@backstage/plugin-auth-backend": "^{{version '@backstage/plugin-auth-backend'}}", "@backstage/plugin-catalog-backend": "^{{version '@backstage/plugin-catalog-backend'}}", "@backstage/plugin-proxy-backend": "^{{version '@backstage/plugin-proxy-backend'}}", - "@backstage/plugin-rollbar-backend": "^{{version '@backstage/plugin-rollbar-backend'}}", "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "@octokit/rest": "^18.0.0", diff --git a/plugins/rollbar-backend/README.md b/plugins/rollbar-backend/README.md index 8f13eb5f26..6cf3b55948 100644 --- a/plugins/rollbar-backend/README.md +++ b/plugins/rollbar-backend/README.md @@ -8,7 +8,6 @@ The following values are read from the configuration file. ```yaml rollbar: - organization: organization-name accountToken: $env: ROLLBAR_ACCOUNT_TOKEN ``` diff --git a/plugins/rollbar-backend/config.d.ts b/plugins/rollbar-backend/config.d.ts new file mode 100644 index 0000000000..473b27a506 --- /dev/null +++ b/plugins/rollbar-backend/config.d.ts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** Configuration options for the rollbar-backend plugin */ + rollbar?: { + /** + * The autentication token for accessing the Rollbar API + * @see https://explorer.docs.rollbar.com/#section/Authentication + */ + accountToken: string; + }; +} diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 307c37e0da..0f0b8bc366 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -42,6 +42,8 @@ "supertest": "^4.0.2" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } diff --git a/plugins/rollbar/README.md b/plugins/rollbar/README.md index a232462dde..cc862051f7 100644 --- a/plugins/rollbar/README.md +++ b/plugins/rollbar/README.md @@ -54,6 +54,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( # app.config.yaml rollbar: organization: organization-name + # used by rollbar-backend accountToken: $env: ROLLBAR_ACCOUNT_TOKEN ``` diff --git a/plugins/rollbar/config.d.ts b/plugins/rollbar/config.d.ts new file mode 100644 index 0000000000..63526d19b0 --- /dev/null +++ b/plugins/rollbar/config.d.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** Configuration options for the rollbar plugin */ + rollbar?: { + /** + * The Rollbar organization name. This can be omitted by using the `rollbar.com/project-slug` annotation. + * @see https://backstage.io/docs/features/software-catalog/well-known-annotations#rollbarcomproject-slug + * @visibility frontend + */ + organization?: string; + }; +} diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 1177a83eb6..64850c0dee 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -51,22 +51,8 @@ "msw": "^0.21.2" }, "files": [ - "dist" + "dist", + "config.d.ts" ], - "configSchema": { - "$schema": "https://backstage.io/schema/config-v1", - "title": "@backstage/rollbar", - "type": "object", - "properties": { - "rollbar": { - "type": "object", - "properties": { - "organization": { - "type": "string", - "visibility": "frontend" - } - } - } - } - } + "configSchema": "config.d.ts" } From 0783fd28f043bc68ba5984af2d3f7d9628ebe081 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Thu, 26 Nov 2020 14:19:31 +0000 Subject: [PATCH 080/195] Improve phrasing of CLI description (#3390) The replaced wording referred to both "is" and "are" in the same sentence. --- docs/overview/stability-index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/overview/stability-index.md b/docs/overview/stability-index.md index 416cdcdd4a..567e9dcbff 100644 --- a/docs/overview/stability-index.md +++ b/docs/overview/stability-index.md @@ -79,11 +79,11 @@ want to ensure some stability. ### [`cli`](https://github.com/backstage/backstage/tree/master/packages/cli/) -The main toolchain used for Backstage development. The interface that is -considered for stability are the various commands and options passed to those -commands, as well as the environment variables read by the CLI. The build output -may change over time and is not considered a breaking change unless it is likely -to affect external tooling. +The main toolchain used for Backstage development. The various CLI commands and +options passed to those commands, as well as the environment variables read by +the CLI, are considered to be the interface that the stability index refers to. +The build output may change over time and is not considered a breaking change +unless it is likely to affect external tooling. Stability: `2` From 852229a3b88b77f1f3221a84967e61e6178deb1a Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 15:47:21 +0100 Subject: [PATCH 081/195] Update yarn.lock --- yarn.lock | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3a64cb0f25..b8093d125d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1320,37 +1320,6 @@ uuid "^8.0.0" yup "^0.29.3" -"@backstage/plugin-catalog-backend@^0.2.2": - version "0.2.2" - resolved "https://registry.npmjs.org/@backstage/plugin-catalog-backend/-/plugin-catalog-backend-0.2.2.tgz#4e17e0376a920f1425de5b0997d34134ff101a11" - integrity sha512-2pxcqQ3C+FGqYDwL7dkMm76zNM75IF1P8rb3iKrbv8qRRwPqtT0wUx+Gctv3RSL8WgGyCxP1Ho9dQ+FC7o2YSQ== - dependencies: - "@azure/msal-node" "^1.0.0-alpha.8" - "@backstage/backend-common" "^0.3.1" - "@backstage/catalog-model" "^0.3.0" - "@backstage/config" "^0.1.1" - "@octokit/graphql" "^4.5.6" - "@types/express" "^4.17.6" - codeowners-utils "^1.0.2" - core-js "^3.6.5" - cross-fetch "^3.0.6" - express "^4.17.1" - express-promise-router "^3.0.3" - fs-extra "^9.0.0" - git-url-parse "^11.4.0" - knex "^0.21.6" - ldapjs "^2.2.0" - lodash "^4.17.15" - morgan "^1.10.0" - p-limit "^3.0.2" - qs "^6.9.4" - sqlite3 "^5.0.0" - uuid "^8.0.0" - winston "^3.2.1" - yaml "^1.9.2" - yn "^4.0.0" - yup "^0.29.3" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" From 3619ea4c47e87a5ab20eb26290ed3d542d7f7c71 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 26 Nov 2020 16:13:51 +0100 Subject: [PATCH 082/195] Add a configuration schema for the proxy plugin --- .changeset/weak-roses-search.md | 5 +++ app-config.yaml | 2 +- plugins/proxy-backend/config.d.ts | 53 ++++++++++++++++++++++++++++++ plugins/proxy-backend/package.json | 6 ++-- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .changeset/weak-roses-search.md create mode 100644 plugins/proxy-backend/config.d.ts diff --git a/.changeset/weak-roses-search.md b/.changeset/weak-roses-search.md new file mode 100644 index 0000000000..9f5074ce48 --- /dev/null +++ b/.changeset/weak-roses-search.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-proxy-backend': patch +--- + +Add configuration schema for the commonly used properties diff --git a/app-config.yaml b/app-config.yaml index 546d00466a..95c6b9006a 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -38,7 +38,7 @@ proxy: headers: Authorization: $env: TRAVISCI_AUTH_TOKEN - travis-api-version: 3 + travis-api-version: '3' '/newrelic/apm/api': target: https://api.newrelic.com/v2 diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts new file mode 100644 index 0000000000..3db4f47d68 --- /dev/null +++ b/plugins/proxy-backend/config.d.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + /** + * A list of forwarding-proxies. Each key is a route to match, + * below the prefix that the proxy plugin is mounted on. I must + * start with a '/'. + */ + proxy: { [key: string]: string | ProxyConfig }; +} + +interface ProxyConfig { + /** + * Target of the proxy. Url string to be parsed with the url module. + */ + target: string; + /** + * Object with extra headers to be added to target requests. + */ + headers?: { [key: string]: string }; + /** + * Changes the origin of the host header to the target URL. Default: true. + */ + changeOrigin?: boolean; + /** + * Rewrite target's url path. Object-keys will be used as RegExp to match paths. + * If pathRewrite is not specified, it is set to a single rewrite that removes the entire prefix and route. + */ + pathRewrite?: { [regexp: string]: string }; + /** + * Limit the forwarded HTTP methods, for example allowedMethods: ['GET'] to enforce read-only access. + */ + allowedMethods?: string[]; + /** + * Limit the forwarded HTTP methods. By default, only the headers that are considered safe for CORS + * and headers that are set by the proxy will be forwarded. + */ + allowedHeaders?: string[]; +} diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 22acdd3fe8..1283f9d261 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -41,6 +41,8 @@ "supertest": "^4.0.2" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } From 032aba5920514011ca74f965b29fec9782f149e0 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 16:40:18 +0100 Subject: [PATCH 083/195] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- .changeset/short-singers-serve.md | 4 ++-- plugins/api-docs/README.md | 2 +- plugins/api-docs/src/catalog/Router.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index c2f1c1914f..f1e90f3b55 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -11,5 +11,5 @@ semantic. After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At the first release after that, they will not be present in released packages either. -If your catalog-info.yaml files still contain this fields after the deletion, they will still be -valid and your ingestion will not break, but they won't be visible in the types for consuming code. +If your catalog-info.yaml files still contain this field after the deletion, they will still be +valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either.``` diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index f5b9a79a85..182ee5a882 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -21,7 +21,7 @@ Right now, the following API formats are supported: Other formats are displayed as plain text, but this can easily be extended. To fill the catalog with APIs, [provide entities of kind API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api). -To link that an component provides or consumes an API, see [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) property on components. +To link that a component provides or consumes an API, see the [`providesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specprovidesapis-optional) and [`consumesApis`](https://backstage.io/docs/features/software-catalog/descriptor-format#specconsumesapis-optional) properties on the Component kind. ## Links diff --git a/plugins/api-docs/src/catalog/Router.tsx b/plugins/api-docs/src/catalog/Router.tsx index 6991ad4b57..64c074fc46 100644 --- a/plugins/api-docs/src/catalog/Router.tsx +++ b/plugins/api-docs/src/catalog/Router.tsx @@ -22,7 +22,7 @@ import { EntityPageApi } from './EntityPageApi'; import { MissingImplementsApisEmptyState } from './MissingImplementsApisEmptyState'; const isPluginApplicableToEntity = (entity: Entity) => { - // TODO: Als support RELATION_CONSUMES_API + // TODO: Also support RELATION_CONSUMES_API return entity.relations?.some(r => r.type === RELATION_PROVIDES_API); }; From 1b7a9d7317aee61398460f46572ba9f4621794ec Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 26 Nov 2020 16:14:42 +0100 Subject: [PATCH 084/195] Refactor the proxying docs so the paragraph structure makes more sense --- docs/plugins/proxying.md | 29 ++++++++++++++++------------- plugins/proxy-backend/config.d.ts | 2 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index f55b4d9229..ac0d110010 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -52,19 +52,7 @@ configuration will lead to the proxy acting on backend requests to The value inside each route is either a simple URL string, or an object on the format accepted by -[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). It -is also possible to limit the forwarded HTTP methods with the configuration -`allowedMethods`, for example `allowedMethods: ['GET']` to enforce read-only -access. - -By default, the proxy will only forward safe HTTP request headers to the target. -Those are based on the headers that are considered safe for CORS and includes -headers like `content-type` or `last-modified`, as well as all headers that are -set by the proxy. If the proxy should forward other headers like -`authorization`, this must be enabled by the `allowedHeaders` config, for -example `allowedHeaders: ['Authorization']`. This should help to not -accidentally forward confidential headers (`cookie`, `X-Auth-Request-User`) to -third-parties. +[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). If the value is a string, it is assumed to correspond to: @@ -85,3 +73,18 @@ except with the following caveats for convenience: `'^/api/proxy/larger-example/v1/': '/'` is added. That means that a request to `/api/proxy/larger-example/v1/some/path` will be translated to a request to `http://larger.example.com:8080/svc.v1/some/path`. + +There are also additional settings: + +- `allowedMethods`: Limit the forwarded HTTP methods. For example + `allowedMethods: ['GET']` enforces read-only access. +- `allowedHeaders`: A list of headers that should be forwarded to the target. + +By default, the proxy will only forward safe HTTP request headers to the target. +Those are based on the headers that are considered safe for CORS and includes +headers like `content-type` or `last-modified`, as well as all headers that are +set by the proxy. If the proxy should forward other headers like +`authorization`, this must be enabled by the `allowedHeaders` config, for +example `allowedHeaders: ['Authorization']`. This should help to not +accidentally forward confidential headers (`cookie`, `X-Auth-Request-User`) to +third-parties. diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 3db4f47d68..122d052c1c 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -17,7 +17,7 @@ export interface Config { /** * A list of forwarding-proxies. Each key is a route to match, - * below the prefix that the proxy plugin is mounted on. I must + * below the prefix that the proxy plugin is mounted on. It must * start with a '/'. */ proxy: { [key: string]: string | ProxyConfig }; From 1ec19a3f4a5666638649f26e2b8cb187335d09e2 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 17:30:10 +0100 Subject: [PATCH 085/195] Handle empty YAML documents One of our users just run into this. Kubernetes has the same behavior and doesn't complain about an empty document. --- .changeset/five-dryers-shout.md | 18 ++++++++++ .../ingestion/processors/util/parse.test.ts | 35 +++++++++++++++++++ .../src/ingestion/processors/util/parse.ts | 3 ++ 3 files changed, 56 insertions(+) create mode 100644 .changeset/five-dryers-shout.md diff --git a/.changeset/five-dryers-shout.md b/.changeset/five-dryers-shout.md new file mode 100644 index 0000000000..fbcc85182d --- /dev/null +++ b/.changeset/five-dryers-shout.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Ignore empty YAML documents. Having a YAML file like this is now ingested without an error: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: web +spec: + type: website +--- + +``` + +This behaves now the same way as Kubernetes handles multiple documents in a single YAML file. diff --git a/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts b/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts index 8cb91fb2ce..53201486bc 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/parse.test.ts @@ -115,6 +115,41 @@ describe('parseEntityYaml', () => { ]); }); + it('should handle empty yaml documents', () => { + // This happens if the user accidentially adds a "---" + // at the end of a file + const results = Array.from( + parseEntityYaml( + Buffer.from( + ` + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: web + spec: + type: website +--- + `, + 'utf8', + ), + testLoc, + ), + ); + + expect(results).toEqual([ + result.entity(testLoc, { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'web', + }, + spec: { + type: 'website', + }, + }), + ]); + }); + it('should emit parsing errors', () => { const results = Array.from( parseEntityYaml(Buffer.from('`', 'utf8'), testLoc), diff --git a/plugins/catalog-backend/src/ingestion/processors/util/parse.ts b/plugins/catalog-backend/src/ingestion/processors/util/parse.ts index 3f07a815b0..c3dcd42d62 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/parse.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/parse.ts @@ -40,6 +40,9 @@ export function* parseEntityYaml( const json = document.toJSON(); if (lodash.isPlainObject(json)) { yield result.entity(location, json as Entity); + } else if (json === null) { + // Ignore null values, these happen if there is an empty document in the + // YAML file, for example if --- is added to the end of the file. } else { const message = `Expected object at root, got ${typeof json}`; yield result.generalError(location, message); From 68650afeb2c2b2e0407dc29ea8b424147d79592b Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 17:57:51 +0100 Subject: [PATCH 086/195] Clarify that consumesApi and providesApi are two different relationships This follows the implementation in #3408 --- .../software-catalog/well-known-relations.md | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/features/software-catalog/well-known-relations.md b/docs/features/software-catalog/well-known-relations.md index 07a4fc0a5c..54f7833d1b 100644 --- a/docs/features/software-catalog/well-known-relations.md +++ b/docs/features/software-catalog/well-known-relations.md @@ -45,17 +45,28 @@ entity, but there will always be one ultimate owner. This relation is commonly generated based on `spec.owner` of the owned entity, where present. -### `consumesApi` and `providesApi` +### `providesApi` and `apiProvidedBy` A relation with an [API](descriptor-format.md#kind-api) entity, typically from a [Component](descriptor-format.md#kind-component) or [System](descriptor-format.md#kind-system). -These relations express that a component or system either exposes an API - -meaning that it hosts callable endpoints from which you can consume that API - -or that they are dependent on being able to consume that API. +These relations express that a component or system exposes an API - meaning that +it hosts callable endpoints from which you can consume that API. -This relation is commonly generated based on `spec.implementsApis` of the +This relation is commonly generated based on `spec.providesApis` of the +component or system in question. + +### `consumesApi` and `apiConsumedBy` + +A relation with an [API](descriptor-format.md#kind-api) entity, typically from a +[Component](descriptor-format.md#kind-component) or +[System](descriptor-format.md#kind-system). + +These relations express that a component or system consumes an API - meaning +that it depends on endpoints of the API. + +This relation is commonly generated based on `spec.consumesApis` of the component or system in question. ### `dependsOn` and `dependencyOf` From 8697dea5b46d721c0e218a1f8925da9ff843198c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Nov 2020 19:30:05 +0100 Subject: [PATCH 087/195] build(deps): bump rollup from 2.23.0 to 2.33.3 (#3434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * build(deps): bump rollup from 2.23.0 to 2.33.3 Bumps [rollup](https://github.com/rollup/rollup) from 2.23.0 to 2.33.3. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.23.0...v2.33.3) Signed-off-by: dependabot[bot] * fix types Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Fredrik Adelöw --- .changeset/shy-humans-protect.md | 5 +++++ packages/cli/package.json | 2 +- packages/cli/src/lib/builder/plugins.test.ts | 12 ++++++------ yarn.lock | 8 ++++---- 4 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 .changeset/shy-humans-protect.md diff --git a/.changeset/shy-humans-protect.md b/.changeset/shy-humans-protect.md new file mode 100644 index 0000000000..612cb9faf8 --- /dev/null +++ b/.changeset/shy-humans-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Bump Rollup diff --git a/packages/cli/package.json b/packages/cli/package.json index 034c8a3de5..b79a8ec37a 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -86,7 +86,7 @@ "react-hot-loader": "^4.12.21", "recursive-readdir": "^2.2.2", "replace-in-file": "^6.0.0", - "rollup": "2.23.x", + "rollup": "2.33.x", "rollup-plugin-dts": "1.4.13", "rollup-plugin-esbuild": "^2.0.0", "rollup-plugin-peer-deps-external": "^2.2.2", diff --git a/packages/cli/src/lib/builder/plugins.test.ts b/packages/cli/src/lib/builder/plugins.test.ts index 41504a700e..d1e1b3e2bd 100644 --- a/packages/cli/src/lib/builder/plugins.test.ts +++ b/packages/cli/src/lib/builder/plugins.test.ts @@ -38,11 +38,11 @@ describe('forwardFileImports', () => { expect(plugin.name).toBe('forward-file-imports'); }); - it('should call through to original external option', () => { + it('should call through to original external option', async () => { const plugin = forwardFileImports({ include: /\.png$/ }); const external = jest.fn((id: string) => id.endsWith('external')); - const options = plugin.options?.call(context, { external })!; + const options = (await plugin.options?.call(context, { external }))!; if (typeof options.external !== 'function') { throw new Error('options.external is not a function'); } @@ -70,12 +70,12 @@ describe('forwardFileImports', () => { ).toThrow('Unknown importer of file module ./my-image.png'); }); - it('should handle original external array', () => { + it('should handle original external array', async () => { const plugin = forwardFileImports({ include: /\.png$/ }); - const options = plugin.options?.call(context, { + const options = (await plugin.options?.call(context, { external: ['my-external'], - })!; + }))!; if (typeof options.external !== 'function') { throw new Error('options.external is not a function'); } @@ -106,7 +106,7 @@ describe('forwardFileImports', () => { it('should extract files', async () => { const plugin = forwardFileImports({ include: /\.png$/ }); - const options = plugin.options?.call(context, {})!; + const options = (await plugin.options?.call(context, {}))!; if (typeof options.external !== 'function') { throw new Error('options.external is not a function'); } diff --git a/yarn.lock b/yarn.lock index 576db3b66c..4856559521 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21188,10 +21188,10 @@ rollup-pluginutils@^2.8.2: dependencies: estree-walker "^0.6.1" -rollup@2.23.x: - version "2.23.0" - resolved "https://registry.npmjs.org/rollup/-/rollup-2.23.0.tgz#b7ab1fee0c0e60132fd0553c4df1e9cdacfada9d" - integrity sha512-vLNmZFUGVwrnqNAJ/BvuLk1MtWzu4IuoqsH9UWK5AIdO3rt8/CSiJNvPvCIvfzrbNsqKbNzPAG1V2O4eTe2XZg== +rollup@2.33.x: + version "2.33.3" + resolved "https://registry.npmjs.org/rollup/-/rollup-2.33.3.tgz#ae72ce31f992b09a580072951bfea76e9df17342" + integrity sha512-RpayhPTe4Gu/uFGCmk7Gp5Z9Qic2VsqZ040G+KZZvsZYdcuWaJg678JeDJJvJeEQXminu24a2au+y92CUWVd+w== optionalDependencies: fsevents "~2.1.2" From 6321746e20695095706625443bcf78c3a7f0e0f6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Nov 2020 13:33:06 +0100 Subject: [PATCH 088/195] cli: make versions:bump properly bump the versions of all @backstage packages --- .changeset/long-birds-rush.md | 2 +- .../cli/src/commands/versions/bump.test.ts | 15 ++- packages/cli/src/commands/versions/bump.ts | 96 ++++++++++++------- packages/cli/src/lib/versioning/Lockfile.ts | 6 ++ 4 files changed, 78 insertions(+), 41 deletions(-) diff --git a/.changeset/long-birds-rush.md b/.changeset/long-birds-rush.md index db4da34789..b1ce6aa125 100644 --- a/.changeset/long-birds-rush.md +++ b/.changeset/long-birds-rush.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Make versions:bump install new versions of dependencies that were within the specified range +Make versions:bump install new versions of dependencies that were within the specified range as well as install new versions of transitive @backstage dependencies. diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index f4d69c52be..1325f0e2fd 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -25,6 +25,7 @@ import { withLogCollector } from '@backstage/test-utils'; const REGISTRY_VERSIONS: { [name: string]: string } = { '@backstage/core': '1.0.6', + '@backstage/core-api': '1.0.7', '@backstage/theme': '2.0.0', }; @@ -54,11 +55,8 @@ const lockfileMock = `${HEADER} version "1.0.3" `; -// This resulting lockfile isn't a real world example, since it doesn't include the package bumps +// This is the lockfile that we produce to unlock versions before we run yarn install const lockfileMockResult = `${HEADER} -"@backstage/core-api@^1.0.3", "@backstage/core-api@^1.0.6": - version "1.0.6" - "@backstage/core@^1.0.5": version "1.0.6" dependencies: @@ -121,15 +119,16 @@ describe('bump', () => { expect(logs.filter(Boolean)).toEqual([ 'Checking for updates of @backstage/theme', 'Checking for updates of @backstage/core', + 'Checking for updates of @backstage/core-api', 'Some packages are outdated, updating', 'Removing lockfile entry for @backstage/core@^1.0.3 to bump to 1.0.6', + 'Removing lockfile entry for @backstage/core-api@^1.0.6 to bump to 1.0.7', + 'Removing lockfile entry for @backstage/core-api@^1.0.3 to bump to 1.0.7', 'Bumping @backstage/theme in b to ^2.0.0', "Running 'yarn install' to install new versions", - 'Removing duplicate dependencies from yarn.lock', - "Running 'yarn install' to remove duplicates from node_modules", ]); - expect(runObj.runPlain).toHaveBeenCalledTimes(2); + expect(runObj.runPlain).toHaveBeenCalledTimes(3); expect(runObj.runPlain).toHaveBeenCalledWith( 'yarn', 'info', @@ -143,7 +142,7 @@ describe('bump', () => { '@backstage/theme', ); - expect(runObj.run).toHaveBeenCalledTimes(2); + expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith('yarn', ['install']); const lockfileContents = await fs.readFile('/yarn.lock', 'utf8'); diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index 1883d87bac..441f65f353 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -41,6 +41,9 @@ type PkgVersionInfo = { export default async () => { const lockfilePath = paths.resolveTargetRoot('yarn.lock'); + const lockfile = await Lockfile.load(lockfilePath); + + const findTargetVersion = createVersionFinder(); // First we discover all Backstage dependencies within our own repo const dependencyMap = await mapDependencies(paths.targetDir); @@ -48,20 +51,16 @@ export default async () => { // Next check with the package registry to see which dependency ranges we need to bump const versionBumps = new Map(); // Track package versions that we want to remove from yarn.lock in order to trigger a bump - const unlocked = Array<{ name: string; range: string; latest: string }>(); + const unlocked = Array<{ name: string; range: string; target: string }>(); await workerThreads(16, dependencyMap.entries(), async ([name, pkgs]) => { - console.log(`Checking for updates of ${name}`); - const info = await fetchPackageInfo(name); - const latest = info['dist-tags'].latest; - if (!latest) { - throw new Error(`No latest version found for ${name}`); - } + const target = await findTargetVersion(name); for (const pkg of pkgs) { - if (semver.satisfies(latest, pkg.range)) { - if (semver.minVersion(pkg.range)?.version !== latest) { - unlocked.push({ name, range: pkg.range, latest }); + if (semver.satisfies(target, pkg.range)) { + if (semver.minVersion(pkg.range)?.version !== target) { + unlocked.push({ name, range: pkg.range, target }); } + continue; } versionBumps.set( @@ -69,12 +68,32 @@ export default async () => { (versionBumps.get(pkg.name) ?? []).concat({ name, location: pkg.location, - range: `^${latest}`, // TODO(Rugvip): Option to use something else than ^? + range: `^${target}`, // TODO(Rugvip): Option to use something else than ^? }), ); } }); + // Check for updates of transitive backstage dependencies + await workerThreads(16, lockfile.keys(), async name => { + // Only check @backstage packages and friends, we don't want this to do a full update of all deps + if (!includedFilter(name)) { + return; + } + + const target = await findTargetVersion(name); + + for (const entry of lockfile.get(name) ?? []) { + // Ignore lockfile entries that don't satisfy the version range, since + // these can't cause the package to be locked to an older version + if (!semver.satisfies(target, entry.range)) { + continue; + } + // Unlock all entries that are within range but on the old version + unlocked.push({ name, range: entry.range, target }); + } + }); + console.log(); // Write all discovered version bumps to package.json in this repo @@ -85,17 +104,21 @@ export default async () => { console.log(); if (unlocked.length > 0) { - const lockfile = await Lockfile.load(lockfilePath); - for (const { name, range, latest } of unlocked) { + const removed = new Set(); + for (const { name, range, target } of unlocked) { // Don't bother removing lockfile entries if they're already on the correct version const existingEntry = lockfile.get(name)?.find(e => e.range === range); - if (existingEntry?.version === latest) { + if (existingEntry?.version === target) { continue; } - console.log( - `Removing lockfile entry for ${name}@${range} to bump to ${latest}`, - ); - lockfile.remove(name, range); + const key = JSON.stringify({ name, range }); + if (!removed.has(key)) { + removed.add(key); + console.log( + `Removing lockfile entry for ${name}@${range} to bump to ${target}`, + ); + lockfile.remove(name, range); + } } await lockfile.save(); } @@ -126,24 +149,13 @@ export default async () => { console.log(); // Finally we make sure the new lockfile doesn't have any duplicates - const lockfile = await Lockfile.load(lockfilePath); - const result = lockfile.analyze({ + const dedupLockfile = await Lockfile.load(lockfilePath); + const result = dedupLockfile.analyze({ filter: includedFilter, }); if (result.newVersions.length > 0) { - console.log(); - console.log('Removing duplicate dependencies from yarn.lock'); - lockfile.replaceVersions(result.newVersions); - await lockfile.save(); - - console.log( - "Running 'yarn install' to remove duplicates from node_modules", - ); - console.log(); - await run('yarn', ['install']); - - console.log(); + throw new Error('Duplicate versions present after package bump'); } const forbiddenNewRanges = result.newRanges.filter(({ name }) => @@ -158,6 +170,26 @@ export default async () => { } }; +function createVersionFinder() { + const found = new Map(); + + return async function findTargetVersion(name: string) { + const existing = found.get(name); + if (existing) { + return existing; + } + + console.log(`Checking for updates of ${name}`); + const info = await fetchPackageInfo(name); + const latest = info['dist-tags'].latest; + if (!latest) { + throw new Error(`No latest version found for ${name}`); + } + found.set(name, latest); + return latest; + }; +} + async function workerThreads( count: number, items: IterableIterator, diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index f9bc789d95..fd7c189b40 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -100,10 +100,16 @@ export class Lockfile { private readonly data: LockfileData, ) {} + /** Get the entries for a single package in the lockfile */ get(name: string): LockfileQueryEntry[] | undefined { return this.packages.get(name); } + /** Returns the name of all packages available in the lockfile */ + keys(): IterableIterator { + return this.packages.keys(); + } + /** Analyzes the lockfile to identify possible actions and warnings for the entries */ analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult { const { filter } = options ?? {}; From c70d68005d500529bfc6a00825a96a1a57aac52a Mon Sep 17 00:00:00 2001 From: Andrew Thauer <6507159+andrewthauer@users.noreply.github.com> Date: Thu, 26 Nov 2020 14:14:30 -0500 Subject: [PATCH 089/195] fix: add rollbar config to fix example-backend error --- app-config.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app-config.yaml b/app-config.yaml index 546d00466a..66e16506b9 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -64,6 +64,11 @@ techdocs: sentry: organization: my-company +rollbar: + organization: my-company + # NOTE: The rollbar-backend & accountToken key may be deprecated in the future (replaced by a proxy config) + accountToken: my-rollbar-account-token + lighthouse: baseUrl: http://localhost:3003 From 05b54d8b17c9618abe244a3487ba43638dfc1307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:26:53 +0100 Subject: [PATCH 090/195] Update docs/getting-started/configure-app-with-plugins.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/getting-started/configure-app-with-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 8b071fb646..44f0abe6a0 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -6,7 +6,7 @@ description: Documentation on How Configuring App with plugins ## Adding existing plugins to your app -The following steps assumes you have created a new Backstage app and want to add +The following steps assume that you have created a new Backstage app and want to add an existing plugin to it. We are using the [CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) in this example. From 276a9e5919ddb74f9c0a34af4cdb823222969636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:27:02 +0100 Subject: [PATCH 091/195] Update docs/getting-started/configure-app-with-plugins.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/getting-started/configure-app-with-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index 44f0abe6a0..b32a08b273 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -17,7 +17,7 @@ in this example. yarn add @backstage/plugin-circleci ``` -1. Add plugin API to your Backstage instance: +1. Add the plugin API to your Backstage instance: ```js // packages/app/src/api.ts From 63998e65b1c7d5b7e6086c813e408eb5b0374739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:27:10 +0100 Subject: [PATCH 092/195] Update docs/getting-started/configure-app-with-plugins.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- docs/getting-started/configure-app-with-plugins.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index b32a08b273..aa2bc7ef4d 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -30,7 +30,7 @@ builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own export default builder.build() as ApiHolder; ``` -2. Add plugin itself: +2. Add the plugin itself: ```js // packages/app/src/plugins.ts From dbca620ff9ea28456df27439c9ee77b0f402a63b Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 26 Nov 2020 14:56:23 +0100 Subject: [PATCH 093/195] Use fs-extra and async file read method --- .../techdocs/stages/generate/helpers.test.ts | 22 +++++++++---------- .../src/techdocs/stages/generate/helpers.ts | 6 ++--- plugins/techdocs/src/api.ts | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts index 4938739fdf..54ef0b1e83 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import fs from 'fs'; +import fs from 'fs-extra'; import os from 'os'; import { resolve as resolvePath } from 'path'; import Stream, { PassThrough } from 'stream'; @@ -288,45 +288,43 @@ describe('helpers', () => { mockFs.restore(); }); - it('should add repo_url to mkdocs.yml', () => { + it('should add repo_url to mkdocs.yml', async () => { const parsedLocationAnnotation: ParsedLocationAnnotation = { type: 'github', target: 'https://github.com/backstage/backstage', }; - patchMkdocsYmlPreBuild( + await patchMkdocsYmlPreBuild( '/mkdocs.yml', mockLogger, parsedLocationAnnotation, ); - const updatedMkdocsYml = fs.readFileSync('/mkdocs.yml').toString(); + const updatedMkdocsYml = await fs.readFile('/mkdocs.yml'); - expect(updatedMkdocsYml).toContain( + expect(updatedMkdocsYml.toString()).toContain( "repo_url: 'https://github.com/backstage/backstage'", ); }); - it('should not override existing repo_url in mkdocs.yml', () => { + it('should not override existing repo_url in mkdocs.yml', async () => { const parsedLocationAnnotation: ParsedLocationAnnotation = { type: 'github', target: 'https://github.com/neworg/newrepo', }; - patchMkdocsYmlPreBuild( + await patchMkdocsYmlPreBuild( '/mkdocs_with_repo_url.yml', mockLogger, parsedLocationAnnotation, ); - const updatedMkdocsYml = fs - .readFileSync('/mkdocs_with_repo_url.yml') - .toString(); + const updatedMkdocsYml = await fs.readFile('/mkdocs_with_repo_url.yml'); - expect(updatedMkdocsYml).toContain( + expect(updatedMkdocsYml.toString()).toContain( "repo_url: 'https://github.com/backstage/backstage'", ); - expect(updatedMkdocsYml).not.toContain( + expect(updatedMkdocsYml.toString()).not.toContain( "repo_url: 'https://github.com/neworg/newrepo'", ); }); diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts index 254186b484..0f60712f5e 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/generate/helpers.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import fs from 'fs'; +import fs from 'fs-extra'; import { spawn } from 'child_process'; import { Writable, PassThrough } from 'stream'; import Docker from 'dockerode'; @@ -231,7 +231,7 @@ export const patchMkdocsYmlPreBuild = async ( ) => { let mkdocsYmlFileString; try { - mkdocsYmlFileString = fs.readFileSync(mkdocsYmlPath, 'utf8'); + mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8'); } catch (error) { logger.warn( `Could not read file ${mkdocsYmlPath} before running the generator. ${error.message}`, @@ -267,7 +267,7 @@ export const patchMkdocsYmlPreBuild = async ( } try { - fs.writeFileSync(mkdocsYmlPath, yaml.safeDump(mkdocsYml), 'utf8'); + await fs.writeFile(mkdocsYmlPath, yaml.safeDump(mkdocsYml), 'utf8'); } catch (error) { logger.warn( `Could not write to ${mkdocsYmlPath} after updating it before running the generator. ${error.message}`, diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 98e1e6c310..368705fa22 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -58,7 +58,7 @@ export class TechDocsApi implements TechDocs { * * When docs are built, we generate a techdocs_metadata.json and store it along with the generated * static files. It includes necessary data about the docs site. This method requests techdocs-backend - * which retries the TechDocs metadata. + * which retrieves the TechDocs metadata. * * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. */ From 0ebfc75b4157129dd96401d55e7db9056a7e3132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:50:32 +0100 Subject: [PATCH 094/195] Fix docs --- .../configure-app-with-plugins.md | 21 ++++--------------- plugins/circleci/README.md | 15 +------------ 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index aa2bc7ef4d..042d8964ca 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -6,30 +6,17 @@ description: Documentation on How Configuring App with plugins ## Adding existing plugins to your app -The following steps assume that you have created a new Backstage app and want to add -an existing plugin to it. We are using the +The following steps assume that you have created a new Backstage app and want to +add an existing plugin to it. We are using the [CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) -in this example. +plugin in this example. -0. Add to the repo: +1. Add the plugin's NPM package to the repo: ```bash yarn add @backstage/plugin-circleci ``` -1. Add the plugin API to your Backstage instance: - -```js -// packages/app/src/api.ts -import { ApiHolder } from '@backstage/core'; -import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci'; - -const builder = ApiRegistry.builder(); -builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own CircleCI instance */)); - -export default builder.build() as ApiHolder; -``` - 2. Add the plugin itself: ```js diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index 4802e8bee2..35fed49827 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -7,25 +7,12 @@ Website: [https://circleci.com/](https://circleci.com/) ## Setup -0. If you have standalone app (you didn't clone this repo), then do +1. If you have standalone app (you didn't clone this repo), then do ```bash yarn add @backstage/plugin-circleci ``` -1. Add plugin API to your Backstage instance: - -```js -// packages/app/src/api.ts -import { ApiHolder } from '@backstage/core'; -import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci'; - -const builder = ApiRegistry.builder(); -builder.add(circleCIApiRef, new CircleCIApi(/* optional custom url for your own CircleCI instance */)); - -export default builder.build() as ApiHolder; -``` - 2. Add plugin itself: ```js From ea475893d72ca0ac30e7d87f371aa7817cf98592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:53:22 +0100 Subject: [PATCH 095/195] Create slow-insects-fail.md --- .changeset/slow-insects-fail.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/slow-insects-fail.md diff --git a/.changeset/slow-insects-fail.md b/.changeset/slow-insects-fail.md new file mode 100644 index 0000000000..214092d5ad --- /dev/null +++ b/.changeset/slow-insects-fail.md @@ -0,0 +1,6 @@ +--- +"example-app": patch +"@backstage/create-app": patch +--- + +Add API docs plugin to new apps being created through the CLI. From 5490e16b59db9d6bb7641d07ac5c87cf48a530c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Thu, 26 Nov 2020 20:58:28 +0100 Subject: [PATCH 096/195] Fix changeset --- .changeset/slow-insects-fail.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/slow-insects-fail.md b/.changeset/slow-insects-fail.md index 214092d5ad..1e7b3f4087 100644 --- a/.changeset/slow-insects-fail.md +++ b/.changeset/slow-insects-fail.md @@ -1,6 +1,6 @@ --- -"example-app": patch -"@backstage/create-app": patch +'example-app': patch +'@backstage/create-app': patch --- -Add API docs plugin to new apps being created through the CLI. +Add [API docs plugin](https://github.com/backstage/backstage/tree/master/plugins/api-docs) to new apps being created through the CLI. From cd72d6acc418e7b3ae731b7fbc8ffa15e91f5e33 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 26 Nov 2020 17:33:35 +0100 Subject: [PATCH 097/195] Remove wrong markdown --- .changeset/short-singers-serve.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md index f1e90f3b55..1bd0f590da 100644 --- a/.changeset/short-singers-serve.md +++ b/.changeset/short-singers-serve.md @@ -12,4 +12,4 @@ After Dec 14th, the fields will be removed from types and classes of the Backsta the first release after that, they will not be present in released packages either. If your catalog-info.yaml files still contain this field after the deletion, they will still be -valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either.``` +valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either. From 29429545359f748b9caa0947b37105abf4be50bd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Nov 2020 22:18:47 +0100 Subject: [PATCH 098/195] cli: only load config schema that applies to the target package --- .changeset/angry-bees-brush.md | 5 +++ packages/cli/src/commands/app/build.ts | 8 +++- packages/cli/src/commands/app/serve.ts | 8 +++- packages/cli/src/commands/config/print.ts | 5 ++- packages/cli/src/commands/config/validate.ts | 5 ++- packages/cli/src/commands/index.ts | 8 ++++ packages/cli/src/commands/plugin/serve.ts | 8 +++- packages/cli/src/lib/config.ts | 46 ++++++++++++++++++-- 8 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 .changeset/angry-bees-brush.md diff --git a/.changeset/angry-bees-brush.md b/.changeset/angry-bees-brush.md new file mode 100644 index 0000000000..aa42d6ef6b --- /dev/null +++ b/.changeset/angry-bees-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Only load config that applies to the target package for frontend build and serve tasks. Also added `--package ` flag to scope the config schema used by the `config:print` and `config:check` commands. diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index c5f5f5aa2f..21189e9427 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -14,16 +14,22 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { buildBundle } from '../../lib/bundler'; import { parseParallel, PARALLEL_ENV_VAR } from '../../lib/parallel'; import { loadCliConfig } from '../../lib/config'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const { name } = await fs.readJson(paths.resolveTarget('package.json')); await buildBundle({ entry: 'src/index', parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), statsJsonEnabled: cmd.stats, - ...(await loadCliConfig(cmd.config)), + ...(await loadCliConfig({ + args: cmd.config, + fromPackage: name, + })), }); }; diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index c3f58774b5..dc165ecb90 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { serveBundle } from '../../lib/bundler'; import { loadCliConfig } from '../../lib/config'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const { name } = await fs.readJson(paths.resolveTarget('package.json')); const waitForExit = await serveBundle({ entry: 'src/index', checksEnabled: cmd.check, - ...(await loadCliConfig(cmd.config)), + ...(await loadCliConfig({ + args: cmd.config, + fromPackage: name, + })), }); await waitForExit(); diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 9649c70ef3..8148bfc9f8 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -21,7 +21,10 @@ import { loadCliConfig } from '../../lib/config'; import { ConfigSchema, ConfigVisibility } from '@backstage/config-loader'; export default async (cmd: Command) => { - const { schema, appConfigs } = await loadCliConfig(cmd.config); + const { schema, appConfigs } = await loadCliConfig({ + args: cmd.config, + fromPackage: cmd.package, + }); const visibility = getVisiblityOption(cmd); const data = serializeConfigData(appConfigs, schema, visibility); diff --git a/packages/cli/src/commands/config/validate.ts b/packages/cli/src/commands/config/validate.ts index 229f9e07e9..581e4bae43 100644 --- a/packages/cli/src/commands/config/validate.ts +++ b/packages/cli/src/commands/config/validate.ts @@ -18,5 +18,8 @@ import { Command } from 'commander'; import { loadCliConfig } from '../../lib/config'; export default async (cmd: Command) => { - await loadCliConfig(cmd.config); + await loadCliConfig({ + args: cmd.config, + fromPackage: cmd.package, + }); }; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 7302559498..2b0e3e25b9 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -141,6 +141,10 @@ export function registerCommands(program: CommanderStatic) { program .command('config:print') + .option( + '--package ', + 'Only load config schema that applies to the given package', + ) .option('--frontend', 'Print only the frontend configuration') .option('--with-secrets', 'Include secrets in the printed configuration') .option( @@ -153,6 +157,10 @@ export function registerCommands(program: CommanderStatic) { program .command('config:check') + .option( + '--package ', + 'Only load config schema that applies to the given package', + ) .option(...configOption) .description( 'Validate that the given configuration loads and matches schema', diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index cb9def3158..27db824d2f 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import fs from 'fs-extra'; import { Command } from 'commander'; import { serveBundle } from '../../lib/bundler'; import { loadCliConfig } from '../../lib/config'; +import { paths } from '../../lib/paths'; export default async (cmd: Command) => { + const { name } = await fs.readJson(paths.resolveTarget('package.json')); const waitForExit = await serveBundle({ entry: 'dev/index', checksEnabled: cmd.check, - ...(await loadCliConfig(cmd.config)), + ...(await loadCliConfig({ + args: cmd.config, + fromPackage: name, + })), }); await waitForExit(); diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 08a8193818..f6cc9c4269 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -18,14 +18,23 @@ import { loadConfig, loadConfigSchema } from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; import { paths } from './paths'; -export async function loadCliConfig(configArgs: string[]) { - const configPaths = configArgs.map(arg => paths.resolveTarget(arg)); +type Options = { + args: string[]; + fromPackage?: string; +}; + +export async function loadCliConfig(options: Options) { + const configPaths = options.args.map(arg => paths.resolveTarget(arg)); // Consider all packages in the monorepo when loading in config const LernaProject = require('@lerna/project'); const project = new LernaProject(paths.targetDir); const packages = await project.getPackages(); - const localPackageNames = packages.map((p: any) => p.name); + + const localPackageNames = options.fromPackage + ? findPackages(packages, options.fromPackage) + : packages.map((p: any) => p.name); + const schema = await loadConfigSchema({ dependencies: localPackageNames, }); @@ -61,3 +70,34 @@ export async function loadCliConfig(configArgs: string[]) { throw error; } } + +function findPackages(packages: any[], fromPackage: string): string[] { + const PackageGraph = require('@lerna/package-graph'); + + const graph = new PackageGraph(packages); + + const targets = new Set(); + const searchNames = [fromPackage]; + + while (searchNames.length) { + const name = searchNames.pop()!; + + if (targets.has(name)) { + continue; + } + + const node = graph.get(name); + if (!node) { + throw new Error(`Package '${name}' not found`); + } + + targets.add(name); + + // Workaround for Backstage main repo only, since the CLI has some artificial devDependencies + if (name !== '@backstage/cli') { + searchNames.push(...node.localDependencies.keys()); + } + } + + return Array.from(targets); +} From b3d4e4e57a6c44da31bacf7df3e2dd1d3fbcb04f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 26 Nov 2020 22:27:22 +0100 Subject: [PATCH 099/195] backend-common: move the frontend visibility of config to @backstage/integration --- .changeset/sixty-eyes-shout.md | 6 ++++ packages/backend-common/config.d.ts | 7 ----- packages/integration/config.d.ts | 45 +++++++++++++++++++++++++++++ packages/integration/package.json | 6 ++-- 4 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 .changeset/sixty-eyes-shout.md create mode 100644 packages/integration/config.d.ts diff --git a/.changeset/sixty-eyes-shout.md b/.changeset/sixty-eyes-shout.md new file mode 100644 index 0000000000..36e1c93a26 --- /dev/null +++ b/.changeset/sixty-eyes-shout.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': patch +'@backstage/integration': patch +--- + +Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index c4ec84ab62..8a6c6d1209 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -96,7 +96,6 @@ export interface Config { azure?: Array<{ /** * The hostname of the given Azure instance - * @visibility frontend */ host: string; /** @@ -110,7 +109,6 @@ export interface Config { bitbucket?: Array<{ /** * The hostname of the given Bitbucket instance - * @visibility frontend */ host: string; /** @@ -120,7 +118,6 @@ export interface Config { token?: string; /** * The base url for the BitBucket API, for example https://api.bitbucket.org/2.0 - * @visibility frontend */ apiBaseUrl?: string; /** @@ -139,7 +136,6 @@ export interface Config { github?: Array<{ /** * The hostname of the given GitHub instance - * @visibility frontend */ host: string; /** @@ -149,12 +145,10 @@ export interface Config { token?: string; /** * The base url for the GitHub API, for example https://api.github.com - * @visibility frontend */ apiBaseUrl?: string; /** * The base url for GitHub raw resources, for example https://raw.githubusercontent.com - * @visibility frontend */ rawBaseUrl?: string; }>; @@ -163,7 +157,6 @@ export interface Config { gitlab?: Array<{ /** * The hostname of the given GitLab instance - * @visibility frontend */ host: string; /** diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts new file mode 100644 index 0000000000..a03a07409b --- /dev/null +++ b/packages/integration/config.d.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + integrations?: { + azure?: Array<{ + /** @visibility frontend */ + host: string; + }>; + + bitbucket?: Array<{ + /** @visibility frontend */ + host: string; + /** @visibility frontend */ + apiBaseUrl?: string; + }>; + + github?: Array<{ + /** @visibility frontend */ + host: string; + /** @visibility frontend */ + apiBaseUrl?: string; + /** @visibility frontend */ + rawBaseUrl?: string; + }>; + + gitlab?: Array<{ + /** @visibility frontend */ + host: string; + }>; + }; +} diff --git a/packages/integration/package.json b/packages/integration/package.json index 2bc65c46a3..119ce51370 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -28,6 +28,8 @@ "@types/jest": "^26.0.7" }, "files": [ - "dist" - ] + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" } From 773084bf87987a3ff1474e9f0833e23fc1adac97 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Nov 2020 04:52:26 +0000 Subject: [PATCH 100/195] build(deps-dev): bump @graphql-codegen/typescript-resolvers Bumps [@graphql-codegen/typescript-resolvers](https://github.com/dotansimha/graphql-code-generator/tree/HEAD/packages/plugins/typescript/resolvers) from 1.17.8 to 1.17.12. - [Release notes](https://github.com/dotansimha/graphql-code-generator/releases) - [Changelog](https://github.com/dotansimha/graphql-code-generator/blob/master/packages/plugins/typescript/resolvers/CHANGELOG.md) - [Commits](https://github.com/dotansimha/graphql-code-generator/commits/@graphql-codegen/typescript-resolvers@1.17.12/packages/plugins/typescript/resolvers) Signed-off-by: dependabot[bot] --- yarn.lock | 177 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4856559521..64c636790a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1880,10 +1880,10 @@ "@graphql-tools/utils" "^6.0.18" tslib "~2.0.0" -"@graphql-codegen/plugin-helpers@^1.17.8", "@graphql-codegen/plugin-helpers@^1.17.9": - version "1.17.9" - resolved "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.17.9.tgz#429f7f75b9f09f5229e42546027613f62ef0d4b1" - integrity sha512-kyj+qsnLGd1JLqXuLpvI6Q/VW7frhoHHNxYJWerpwsSV9m4cttmFj8d9JTfYPZbg6cLIRR7+10lVugzMKozjzA== +"@graphql-codegen/plugin-helpers@^1.17.8", "@graphql-codegen/plugin-helpers@^1.17.9", "@graphql-codegen/plugin-helpers@^1.18.2": + version "1.18.2" + resolved "https://registry.npmjs.org/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.2.tgz#57011076cb8b8f5d04d37d226a5eda300c01be94" + integrity sha512-SvX+Ryq2naLcoD6jJNxtzc/moWTgHJ+X0KRfvhGWTa+xtFTS02i+PWOR89YYPcD8+LF6GmyFBjx2FmLCx4JwMg== dependencies: "@graphql-tools/utils" "^6" camel-case "4.1.1" @@ -1898,41 +1898,42 @@ upper-case "2.0.1" "@graphql-codegen/typescript-resolvers@^1.17.7": - version "1.17.8" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.17.8.tgz#c10af67e54c00520aa33be0403c9e19981d820b5" - integrity sha512-0uMipQjqdRWCW/4I9ZmuZmPuR9jMqKqKph+G/9iAVkfRz3XEa8v1ho+pZ7NhJLWIeiW9NC7B/BKB1UUhKecysg== + version "1.17.12" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript-resolvers/-/typescript-resolvers-1.17.12.tgz#1f8f608d68b46780351c8224a61750b9b9307497" + integrity sha512-qICKxl06R1yCBh03cdyiOYlHTebQ1n/FOiQPCxo4bsiF6HfbrgpMS8fdGEZ8v+VBPxQFZZ7HQ2KcuPNsuw5R6g== dependencies: - "@graphql-codegen/plugin-helpers" "^1.17.8" - "@graphql-codegen/typescript" "^1.17.8" - "@graphql-codegen/visitor-plugin-common" "^1.17.13" - "@graphql-tools/utils" "^6.0.18" + "@graphql-codegen/plugin-helpers" "^1.18.2" + "@graphql-codegen/typescript" "^1.18.1" + "@graphql-codegen/visitor-plugin-common" "^1.17.20" + "@graphql-tools/utils" "^6" auto-bind "~4.0.0" - tslib "~2.0.0" + tslib "~2.0.1" -"@graphql-codegen/typescript@^1.17.7", "@graphql-codegen/typescript@^1.17.8": - version "1.17.8" - resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.17.8.tgz#08477e532d8342c8a74f75f46f97c85831418e23" - integrity sha512-OQCD4CbnKfHG63u1Sh+UsSKdzkoRHI8NmqnV0Z5ECO4Mm3mrm6fGoYZkKmHbA2tgWIFEsQLevDW+CfduyMd9BA== +"@graphql-codegen/typescript@^1.17.7", "@graphql-codegen/typescript@^1.18.1": + version "1.18.1" + resolved "https://registry.npmjs.org/@graphql-codegen/typescript/-/typescript-1.18.1.tgz#3d730472a01f18aea6331046f4ebfe3d91326801" + integrity sha512-Ee37NutKmaNrgAo2d5mv42RqPd8jJ6zyUKAH669Gbv0dFn2EK3sdC9PYQC9gXptv+H/eQn2gYgaa2nVpEPAIzg== dependencies: - "@graphql-codegen/plugin-helpers" "^1.17.8" - "@graphql-codegen/visitor-plugin-common" "^1.17.13" + "@graphql-codegen/plugin-helpers" "^1.18.2" + "@graphql-codegen/visitor-plugin-common" "^1.17.20" auto-bind "~4.0.0" - tslib "~2.0.0" + tslib "~2.0.1" -"@graphql-codegen/visitor-plugin-common@^1.17.13": - version "1.17.13" - resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.13.tgz#88dfd8b266aab140a36018807449a1162612e393" - integrity sha512-RWL5VcUQh1mcNb+nr7pDnfoMZ9cn2MurDcZMYGTblIXox+FT33y9Cg4erRjzkVuTvxSf7xXc2TSrSkt9MeZRCw== +"@graphql-codegen/visitor-plugin-common@^1.17.20": + version "1.17.20" + resolved "https://registry.npmjs.org/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.20.tgz#cff95cdd49cef270b3811fdb141a412ffe2bdfd7" + integrity sha512-buIpcNNyTqVubknancX8m9jARCZsUA5eKuskg+CylWKL/8CSaD2Tiq7CfbbNO10o7XIgRrPtJMl1c9hQ6N4ytw== dependencies: - "@graphql-codegen/plugin-helpers" "^1.17.8" - "@graphql-tools/relay-operation-optimizer" "^6.0.18" - array.prototype.flatmap "^1.2.3" + "@graphql-codegen/plugin-helpers" "^1.18.2" + "@graphql-tools/optimize" "^1.0.1" + "@graphql-tools/relay-operation-optimizer" "^6" + array.prototype.flatmap "^1.2.4" auto-bind "~4.0.0" dependency-graph "^0.9.0" graphql-tag "^2.11.0" parse-filepath "^1.0.2" pascal-case "^3.1.1" - tslib "~2.0.0" + tslib "~2.0.1" "@graphql-modules/core@^0.7.17": version "0.7.17" @@ -2108,6 +2109,13 @@ "@graphql-tools/utils" "^6.2.4" tslib "~2.0.1" +"@graphql-tools/optimize@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@graphql-tools/optimize/-/optimize-1.0.1.tgz#9933fffc5a3c63f95102b1cb6076fb16ac7bb22d" + integrity sha512-cRlUNsbErYoBtzzS6zXahXeTBZGPVlPHXCpnEZ0XiK/KY/sQL96cyzak0fM/Gk6qEI9/l32MYEICjasiBQrl5w== + dependencies: + tslib "~2.0.1" + "@graphql-tools/prisma-loader@^6": version "6.2.4" resolved "https://registry.npmjs.org/@graphql-tools/prisma-loader/-/prisma-loader-6.2.4.tgz#3f902b9f1d36ae0c4731e1fe963178bea300af49" @@ -2138,13 +2146,14 @@ tslib "~2.0.1" yaml-ast-parser "^0.0.43" -"@graphql-tools/relay-operation-optimizer@^6.0.18": - version "6.0.18" - resolved "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.0.18.tgz#0089d4f222d323aae6d5b266daa5f2bc3689a14e" - integrity sha512-X11M/njvdeGUvER+oJCCFMmnoPPq1Y+W6AXWX8WKeaM4LxdMeYY9mAWfhUyNOX4XZlxUA6zNNH+QwBAyUyZvRA== +"@graphql-tools/relay-operation-optimizer@^6": + version "6.3.0" + resolved "https://registry.npmjs.org/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.3.0.tgz#f8c7f6c8aa4a9cf50ab151fbc5db4f4282a79532" + integrity sha512-Or3UgRvkY9Fq1AAx7q38oPqFmTepLz7kp6wDHKyR0ceG7AvHv5En22R12mAeISInbhff4Rpwgf6cE8zHRu6bCw== dependencies: - "@graphql-tools/utils" "6.0.18" - relay-compiler "10.0.1" + "@graphql-tools/utils" "^7.1.0" + relay-compiler "10.1.0" + tslib "~2.0.1" "@graphql-tools/schema@6.0.15": version "6.0.15" @@ -2210,6 +2219,15 @@ camel-case "4.1.1" tslib "~2.0.1" +"@graphql-tools/utils@^7.1.0": + version "7.1.0" + resolved "https://registry.npmjs.org/@graphql-tools/utils/-/utils-7.1.0.tgz#0cbcfa7b6e7741650f78e8bde4823780ed9e8c57" + integrity sha512-lMTgy08BdqQ31zyyCRrxcKZ6gAKQB9amGKJGg0mb3b4I3iJQKeaw9a7T96yGe1frGak1UK9y7OoYqx8RG7KitA== + dependencies: + "@ardatan/aggregate-error" "0.0.6" + camel-case "4.1.1" + tslib "~2.0.1" + "@graphql-tools/wrap@^6.2.4": version "6.2.4" resolved "https://registry.npmjs.org/@graphql-tools/wrap/-/wrap-6.2.4.tgz#2709817da6e469753735a9fe038c9e99736b2c57" @@ -7115,6 +7133,16 @@ array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.2.3: es-abstract "^1.17.0-next.1" function-bind "^1.1.1" +array.prototype.flatmap@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz#94cfd47cc1556ec0747d97f7c7738c58122004c9" + integrity sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + function-bind "^1.1.1" + array.prototype.map@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz#9a4159f416458a23e9483078de1106b2ef68f8ec" @@ -9365,7 +9393,7 @@ core-js@3, core-js@^3.0.1, core-js@^3.0.4, core-js@^3.5.0, core-js@^3.6.0, core- resolved "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a" integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA== -core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.11, core-js@^2.6.5: +core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.11, core-js@^2.6.5: version "2.6.11" resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== @@ -9476,7 +9504,7 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== @@ -11017,6 +11045,24 @@ es-abstract@^1.17.5: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +es-abstract@^1.18.0-next.1: + version "1.18.0-next.1" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" + integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.0" + is-regex "^1.1.1" + object-inspect "^1.8.0" + object-keys "^1.1.1" + object.assign "^4.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" @@ -11881,14 +11927,13 @@ fbjs-css-vars@^1.0.0: resolved "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== -fbjs@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" - integrity sha512-MUgcMEJaFhCaF1QtWGnmq9ZDRAzECTCRAF7O6UZIlAlkTs1SasiX9aP0Iw7wfD2mJ7wDTNfg2w7u5fSCwJk1OA== +fbjs@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/fbjs/-/fbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165" + integrity sha512-dJd4PiDOFuhe7vk4F80Mba83Vr2QuK86FoxtgPmzBqEJahncp+13YCmfoa53KHCo6OnlXLG7eeMWPfB5CrpVKg== dependencies: - core-js "^2.4.1" + cross-fetch "^3.0.4" fbjs-css-vars "^1.0.0" - isomorphic-fetch "^2.1.1" loose-envify "^1.0.0" object-assign "^4.1.0" promise "^7.1.1" @@ -14257,6 +14302,11 @@ is-module@^1.0.0: resolved "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= +is-negative-zero@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" + integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= + is-npm@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -14407,7 +14457,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -14531,14 +14581,6 @@ isobject@^4.0.0: resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isomorphic-fetch@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" @@ -17256,14 +17298,6 @@ node-fetch@2.6.1, node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0, node- resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-forge@^0.10.0: version "0.10.0" resolved "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -20756,10 +20790,10 @@ relateurl@^0.2.7: resolved "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -relay-compiler@10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/relay-compiler/-/relay-compiler-10.0.1.tgz#d3029a5121cad52e1e55073210365b827cee5f3b" - integrity sha512-hrTqh81XXxPB4EgvxPmvojICr0wJnRoumxOsMZnS9dmhDHSqcBAT7+C3+rdGm5sSdNH8mbMcZM7YSPDh8ABxQw== +relay-compiler@10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/relay-compiler/-/relay-compiler-10.1.0.tgz#fb4672cdbe9b54869a3a79759edd8c2d91609cbe" + integrity sha512-HPqc3N3tNgEgUH5+lTr5lnLbgnsZMt+MRiyS0uAVNhuPY2It0X1ZJG+9qdA3L9IqKFUNwVn6zTO7RArjMZbARQ== dependencies: "@babel/core" "^7.0.0" "@babel/generator" "^7.5.0" @@ -20770,21 +20804,21 @@ relay-compiler@10.0.1: babel-preset-fbjs "^3.3.0" chalk "^4.0.0" fb-watchman "^2.0.0" - fbjs "^1.0.0" + fbjs "^3.0.0" glob "^7.1.1" immutable "~3.7.6" nullthrows "^1.1.1" - relay-runtime "10.0.1" + relay-runtime "10.1.0" signedsource "^1.0.0" yargs "^15.3.1" -relay-runtime@10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/relay-runtime/-/relay-runtime-10.0.1.tgz#c83bd7e6e37234ece2a62a254e37dd199a4f74f9" - integrity sha512-sPYiuosq+5gQ7zXs2EKg2O8qRSsF8vmMYo6SIHEi4juBLg1HrdTEvqcaNztc2ZFmUc4vYZpTbbS4j/TZCtHuyA== +relay-runtime@10.1.0: + version "10.1.0" + resolved "https://registry.npmjs.org/relay-runtime/-/relay-runtime-10.1.0.tgz#4753bf36e95e8d862cef33608e3d98b4ed730d16" + integrity sha512-bxznLnQ1ST6APN/cFi7l0FpjbZVchWQjjhj9mAuJBuUqNNCh9uV+UTRhpQF7Q8ycsPp19LHTpVyGhYb0ustuRQ== dependencies: "@babel/runtime" "^7.0.0" - fbjs "^1.0.0" + fbjs "^3.0.0" remark-gfm@^1.0.0: version "1.0.0" @@ -24344,11 +24378,6 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@>=0.10.0: - version "3.3.1" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.3.1.tgz#6c1acf37dec176b0fd6bc9a74b616bec2f612935" - integrity sha512-faXTmGDcLuEPBpJwb5LQfyxvubKiE+RlbmmweFGKjvIPFj4uHTTfdtTIkdTRhC6OSH9S9eyYbx8kZ0UEaQqYTA== - whatwg-fetch@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" From 7c3e07a9986e015484be128e201676f0d4e7e7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Fri, 27 Nov 2020 10:58:24 +0100 Subject: [PATCH 101/195] Fix changeset --- .changeset/slow-insects-fail.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.changeset/slow-insects-fail.md b/.changeset/slow-insects-fail.md index 1e7b3f4087..a07e025f34 100644 --- a/.changeset/slow-insects-fail.md +++ b/.changeset/slow-insects-fail.md @@ -1,5 +1,4 @@ --- -'example-app': patch '@backstage/create-app': patch --- From 8432bde46ab2c8bbdd5bee59de35e905b85c3572 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 27 Nov 2020 11:11:03 +0100 Subject: [PATCH 102/195] Use inline type instead of a dedicated interface --- plugins/proxy-backend/config.d.ts | 62 ++++++++++++++++--------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 122d052c1c..8bb7e958b8 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -20,34 +20,36 @@ export interface Config { * below the prefix that the proxy plugin is mounted on. It must * start with a '/'. */ - proxy: { [key: string]: string | ProxyConfig }; -} - -interface ProxyConfig { - /** - * Target of the proxy. Url string to be parsed with the url module. - */ - target: string; - /** - * Object with extra headers to be added to target requests. - */ - headers?: { [key: string]: string }; - /** - * Changes the origin of the host header to the target URL. Default: true. - */ - changeOrigin?: boolean; - /** - * Rewrite target's url path. Object-keys will be used as RegExp to match paths. - * If pathRewrite is not specified, it is set to a single rewrite that removes the entire prefix and route. - */ - pathRewrite?: { [regexp: string]: string }; - /** - * Limit the forwarded HTTP methods, for example allowedMethods: ['GET'] to enforce read-only access. - */ - allowedMethods?: string[]; - /** - * Limit the forwarded HTTP methods. By default, only the headers that are considered safe for CORS - * and headers that are set by the proxy will be forwarded. - */ - allowedHeaders?: string[]; + proxy: { + [key: string]: + | string + | { + /** + * Target of the proxy. Url string to be parsed with the url module. + */ + target: string; + /** + * Object with extra headers to be added to target requests. + */ + headers?: { [key: string]: string }; + /** + * Changes the origin of the host header to the target URL. Default: true. + */ + changeOrigin?: boolean; + /** + * Rewrite target's url path. Object-keys will be used as RegExp to match paths. + * If pathRewrite is not specified, it is set to a single rewrite that removes the entire prefix and route. + */ + pathRewrite?: { [regexp: string]: string }; + /** + * Limit the forwarded HTTP methods, for example allowedMethods: ['GET'] to enforce read-only access. + */ + allowedMethods?: string[]; + /** + * Limit the forwarded HTTP methods. By default, only the headers that are considered safe for CORS + * and headers that are set by the proxy will be forwarded. + */ + allowedHeaders?: string[]; + }; + }; } From 0e1f240a99dff96087b626365d5b23f73e487594 Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Fri, 27 Nov 2020 11:24:59 +0100 Subject: [PATCH 103/195] Change the proxy setting to be optional --- plugins/proxy-backend/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/proxy-backend/config.d.ts b/plugins/proxy-backend/config.d.ts index 8bb7e958b8..e9b0f45893 100644 --- a/plugins/proxy-backend/config.d.ts +++ b/plugins/proxy-backend/config.d.ts @@ -20,7 +20,7 @@ export interface Config { * below the prefix that the proxy plugin is mounted on. It must * start with a '/'. */ - proxy: { + proxy?: { [key: string]: | string | { From b623cc275ce5aef11bf8d8ffa71aba76a025b35b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 27 Nov 2020 13:15:23 +0100 Subject: [PATCH 104/195] cli: narrow version range of rollup-plugin-esbuild to avoid breaking change --- .changeset/yellow-colts-burn.md | 5 +++++ packages/cli/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/yellow-colts-burn.md diff --git a/.changeset/yellow-colts-burn.md b/.changeset/yellow-colts-burn.md new file mode 100644 index 0000000000..5d4a132b66 --- /dev/null +++ b/.changeset/yellow-colts-burn.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Narrow down the version range of rollup-plugin-esbuild to avoid breaking change in newer version diff --git a/packages/cli/package.json b/packages/cli/package.json index b79a8ec37a..db7c127128 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -88,7 +88,7 @@ "replace-in-file": "^6.0.0", "rollup": "2.33.x", "rollup-plugin-dts": "1.4.13", - "rollup-plugin-esbuild": "^2.0.0", + "rollup-plugin-esbuild": "2.3.x", "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^3.1.1", "rollup-plugin-typescript2": "^0.27.3", From 892645b8151e96f2a7ef437e1698b2ee45678294 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 27 Nov 2020 12:51:09 +0000 Subject: [PATCH 105/195] Version Packages --- .changeset/angry-bees-brush.md | 5 ----- .changeset/beige-mugs-visit.md | 5 ----- .changeset/brave-rats-obey.md | 5 ----- .changeset/chilled-zoos-rush.md | 7 ------- .changeset/eleven-lobsters-train.md | 7 ------- .changeset/few-kings-agree.md | 5 ----- .changeset/fifty-nails-pay.md | 6 ------ .changeset/five-dryers-shout.md | 18 ----------------- .changeset/good-cycles-shave.md | 6 ------ .changeset/gorgeous-games-build.md | 6 ------ .changeset/long-birds-rush.md | 5 ----- .changeset/pink-goats-sort.md | 6 ------ .changeset/short-singers-serve.md | 15 -------------- .changeset/shy-humans-protect.md | 5 ----- .changeset/sixty-eyes-shout.md | 6 ------ .changeset/ten-doors-exist.md | 6 ------ .changeset/yellow-colts-burn.md | 5 ----- packages/app/CHANGELOG.md | 22 ++++++++++++++++++++ packages/app/package.json | 14 ++++++------- packages/backend-common/CHANGELOG.md | 9 +++++++++ packages/backend-common/package.json | 6 +++--- packages/backend/CHANGELOG.md | 24 ++++++++++++++++++++++ packages/backend/package.json | 20 +++++++++--------- packages/catalog-model/CHANGELOG.md | 18 +++++++++++++++++ packages/catalog-model/package.json | 4 ++-- packages/cli/CHANGELOG.md | 9 +++++++++ packages/cli/package.json | 4 ++-- packages/core-api/CHANGELOG.md | 6 ++++++ packages/core-api/package.json | 4 ++-- packages/create-app/package.json | 22 ++++++++++---------- packages/integration/CHANGELOG.md | 6 ++++++ packages/integration/package.json | 4 ++-- plugins/api-docs/CHANGELOG.md | 18 +++++++++++++++++ plugins/api-docs/package.json | 8 ++++---- plugins/app-backend/CHANGELOG.md | 9 +++++++++ plugins/app-backend/package.json | 6 +++--- plugins/auth-backend/CHANGELOG.md | 14 +++++++++++++ plugins/auth-backend/package.json | 8 ++++---- plugins/catalog-backend/CHANGELOG.md | 29 +++++++++++++++++++++++++++ plugins/catalog-backend/package.json | 8 ++++---- plugins/catalog/CHANGELOG.md | 12 +++++++++++ plugins/catalog/package.json | 8 ++++---- plugins/rollbar-backend/CHANGELOG.md | 9 +++++++++ plugins/rollbar-backend/package.json | 6 +++--- plugins/rollbar/CHANGELOG.md | 12 +++++++++++ plugins/rollbar/package.json | 8 ++++---- plugins/techdocs-backend/CHANGELOG.md | 17 ++++++++++++++++ plugins/techdocs-backend/package.json | 8 ++++---- plugins/techdocs/CHANGELOG.md | 18 +++++++++++++++++ plugins/techdocs/package.json | 10 ++++----- 50 files changed, 306 insertions(+), 192 deletions(-) delete mode 100644 .changeset/angry-bees-brush.md delete mode 100644 .changeset/beige-mugs-visit.md delete mode 100644 .changeset/brave-rats-obey.md delete mode 100644 .changeset/chilled-zoos-rush.md delete mode 100644 .changeset/eleven-lobsters-train.md delete mode 100644 .changeset/few-kings-agree.md delete mode 100644 .changeset/fifty-nails-pay.md delete mode 100644 .changeset/five-dryers-shout.md delete mode 100644 .changeset/good-cycles-shave.md delete mode 100644 .changeset/gorgeous-games-build.md delete mode 100644 .changeset/long-birds-rush.md delete mode 100644 .changeset/pink-goats-sort.md delete mode 100644 .changeset/short-singers-serve.md delete mode 100644 .changeset/shy-humans-protect.md delete mode 100644 .changeset/sixty-eyes-shout.md delete mode 100644 .changeset/ten-doors-exist.md delete mode 100644 .changeset/yellow-colts-burn.md diff --git a/.changeset/angry-bees-brush.md b/.changeset/angry-bees-brush.md deleted file mode 100644 index aa42d6ef6b..0000000000 --- a/.changeset/angry-bees-brush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Only load config that applies to the target package for frontend build and serve tasks. Also added `--package ` flag to scope the config schema used by the `config:print` and `config:check` commands. diff --git a/.changeset/beige-mugs-visit.md b/.changeset/beige-mugs-visit.md deleted file mode 100644 index 4b5f0f1bdd..0000000000 --- a/.changeset/beige-mugs-visit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Allow the backend to register custom AuthProviderFactories diff --git a/.changeset/brave-rats-obey.md b/.changeset/brave-rats-obey.md deleted file mode 100644 index a09d922763..0000000000 --- a/.changeset/brave-rats-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch ---- - -Warn if the app-backend can't start-up because the static directory that should be served is unavailable. diff --git a/.changeset/chilled-zoos-rush.md b/.changeset/chilled-zoos-rush.md deleted file mode 100644 index 6830cb7fcf..0000000000 --- a/.changeset/chilled-zoos-rush.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-api-docs': minor ---- - -APIs now have real entity pages that are customizable in the app. -Therefore the old entity page from this plugin is removed. -See the `packages/app` on how to create and customize the API entity page. diff --git a/.changeset/eleven-lobsters-train.md b/.changeset/eleven-lobsters-train.md deleted file mode 100644 index 95b9145ee6..0000000000 --- a/.changeset/eleven-lobsters-train.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-techdocs': minor -'@backstage/plugin-techdocs-backend': minor ---- - -- Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. -- techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` diff --git a/.changeset/few-kings-agree.md b/.changeset/few-kings-agree.md deleted file mode 100644 index 0019611503..0000000000 --- a/.changeset/few-kings-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Added support for passing false as a CSP field value, to drop it from the defaults in the backend diff --git a/.changeset/fifty-nails-pay.md b/.changeset/fifty-nails-pay.md deleted file mode 100644 index 5880d9a8cc..0000000000 --- a/.changeset/fifty-nails-pay.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch ---- - -Replace usage of implementsApis with relations diff --git a/.changeset/five-dryers-shout.md b/.changeset/five-dryers-shout.md deleted file mode 100644 index fbcc85182d..0000000000 --- a/.changeset/five-dryers-shout.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Ignore empty YAML documents. Having a YAML file like this is now ingested without an error: - -```yaml -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: web -spec: - type: website ---- - -``` - -This behaves now the same way as Kubernetes handles multiple documents in a single YAML file. diff --git a/.changeset/good-cycles-shave.md b/.changeset/good-cycles-shave.md deleted file mode 100644 index 90a9410b9c..0000000000 --- a/.changeset/good-cycles-shave.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog-backend': patch ---- - -Add `providesApis` and `consumesApis` to the component entity spec. diff --git a/.changeset/gorgeous-games-build.md b/.changeset/gorgeous-games-build.md deleted file mode 100644 index ff050c43f6..0000000000 --- a/.changeset/gorgeous-games-build.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-rollbar': patch -'@backstage/plugin-rollbar-backend': patch ---- - -Add config schema for the rollbar & rollbar-backend plugins diff --git a/.changeset/long-birds-rush.md b/.changeset/long-birds-rush.md deleted file mode 100644 index b1ce6aa125..0000000000 --- a/.changeset/long-birds-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Make versions:bump install new versions of dependencies that were within the specified range as well as install new versions of transitive @backstage dependencies. diff --git a/.changeset/pink-goats-sort.md b/.changeset/pink-goats-sort.md deleted file mode 100644 index 828cccba84..0000000000 --- a/.changeset/pink-goats-sort.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/plugin-catalog-backend': patch ---- - -Start emitting all known relation types from the core entity kinds, based on their spec data. diff --git a/.changeset/short-singers-serve.md b/.changeset/short-singers-serve.md deleted file mode 100644 index 1bd0f590da..0000000000 --- a/.changeset/short-singers-serve.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. - -Code that consumes these fields should remove those usages as soon as possible and migrate to using -relations instead. Producers should fill the field `spec.providesApis` instead, which has the same -semantic. - -After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At -the first release after that, they will not be present in released packages either. - -If your catalog-info.yaml files still contain this field after the deletion, they will still be -valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either. diff --git a/.changeset/shy-humans-protect.md b/.changeset/shy-humans-protect.md deleted file mode 100644 index 612cb9faf8..0000000000 --- a/.changeset/shy-humans-protect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Bump Rollup diff --git a/.changeset/sixty-eyes-shout.md b/.changeset/sixty-eyes-shout.md deleted file mode 100644 index 36e1c93a26..0000000000 --- a/.changeset/sixty-eyes-shout.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/integration': patch ---- - -Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration diff --git a/.changeset/ten-doors-exist.md b/.changeset/ten-doors-exist.md deleted file mode 100644 index 2dafaf728d..0000000000 --- a/.changeset/ten-doors-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/plugin-auth-backend': patch ---- - -bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure diff --git a/.changeset/yellow-colts-burn.md b/.changeset/yellow-colts-burn.md deleted file mode 100644 index 5d4a132b66..0000000000 --- a/.changeset/yellow-colts-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Narrow down the version range of rollup-plugin-esbuild to avoid breaking change in newer version diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index f70cd198b4..a6c8da7fd9 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,27 @@ # example-app +## 0.2.4 + +### Patch Changes + +- Updated dependencies [294295453] +- Updated dependencies [f3bb55ee3] +- Updated dependencies [4b53294a6] +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [3a201c5d5] +- Updated dependencies [f538e2c56] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [8697dea5b] +- Updated dependencies [b623cc275] + - @backstage/cli@0.3.2 + - @backstage/plugin-api-docs@0.3.0 + - @backstage/plugin-techdocs@0.3.0 + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + - @backstage/plugin-rollbar@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 9e54005cd3..bde325ed46 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,14 +1,14 @@ { "name": "example-app", - "version": "0.2.3", + "version": "0.2.4", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.3.0", - "@backstage/cli": "^0.3.1", + "@backstage/catalog-model": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/core": "^0.3.2", - "@backstage/plugin-api-docs": "^0.2.2", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-api-docs": "^0.3.0", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-cloudbuild": "^0.2.2", "@backstage/plugin-cost-insights": "^0.4.1", @@ -22,12 +22,12 @@ "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-newrelic": "^0.2.1", "@backstage/plugin-register-component": "^0.2.2", - "@backstage/plugin-rollbar": "^0.2.3", + "@backstage/plugin-rollbar": "^0.2.4", "@backstage/plugin-scaffolder": "^0.3.1", "@backstage/plugin-sentry": "^0.2.3", "@backstage/plugin-search": "^0.2.1", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.3", + "@backstage/plugin-techdocs": "^0.3.0", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/plugin-welcome": "^0.2.1", "@backstage/test-utils": "^0.1.3", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 793a554905..aec6c37a49 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-common +## 0.3.2 + +### Patch Changes + +- 3aa7efb3f: Added support for passing false as a CSP field value, to drop it from the defaults in the backend +- b3d4e4e57: Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration +- Updated dependencies [b3d4e4e57] + - @backstage/integration@0.1.2 + ## 0.3.1 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 731e2679cb..ca92f70e98 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.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -32,7 +32,7 @@ "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.1", "@backstage/config-loader": "^0.3.0", - "@backstage/integration": "^0.1.1", + "@backstage/integration": "^0.1.2", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "archiver": "^5.0.2", @@ -67,7 +67,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@types/archiver": "^3.1.1", "@types/compression": "^1.7.0", diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index bc2a08e983..d47e9003a5 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,29 @@ # example-backend +## 0.2.4 + +### Patch Changes + +- Updated dependencies [50eff1d00] +- Updated dependencies [ff1301d28] +- Updated dependencies [4b53294a6] +- Updated dependencies [3aa7efb3f] +- Updated dependencies [1ec19a3f4] +- Updated dependencies [ab94c9542] +- Updated dependencies [3a201c5d5] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] +- Updated dependencies [700a212b4] + - @backstage/plugin-auth-backend@0.2.4 + - @backstage/plugin-app-backend@0.3.1 + - @backstage/plugin-techdocs-backend@0.3.0 + - @backstage/backend-common@0.3.2 + - @backstage/plugin-catalog-backend@0.2.3 + - @backstage/catalog-model@0.3.1 + - @backstage/plugin-rollbar-backend@0.1.4 + - example-app@0.2.4 + ## 0.2.3 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 5d3678a284..beb2567bd7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.3", + "version": "0.2.4", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,24 +18,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", - "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.3", - "@backstage/plugin-catalog-backend": "^0.2.2", + "@backstage/plugin-app-backend": "^0.3.1", + "@backstage/plugin-auth-backend": "^0.2.4", + "@backstage/plugin-catalog-backend": "^0.2.3", "@backstage/plugin-graphql-backend": "^0.1.3", "@backstage/plugin-kubernetes-backend": "^0.2.0", "@backstage/plugin-proxy-backend": "^0.2.1", - "@backstage/plugin-rollbar-backend": "^0.1.3", + "@backstage/plugin-rollbar-backend": "^0.1.4", "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-sentry-backend": "^0.1.3", - "@backstage/plugin-techdocs-backend": "^0.2.2", + "@backstage/plugin-techdocs-backend": "^0.3.0", "@gitbeaker/node": "^25.2.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.2.3", + "example-app": "^0.2.4", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.6", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 10bf540b46..630664e491 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/catalog-model +## 0.3.1 + +### Patch Changes + +- ab94c9542: Add `providesApis` and `consumesApis` to the component entity spec. +- 2daf18e80: Start emitting all known relation types from the core entity kinds, based on their spec data. +- 069cda35f: Marked the field `spec.implementsApis` on `Component` entities for deprecation on Dec 14th, 2020. + + Code that consumes these fields should remove those usages as soon as possible and migrate to using + relations instead. Producers should fill the field `spec.providesApis` instead, which has the same + semantic. + + After Dec 14th, the fields will be removed from types and classes of the Backstage repository. At + the first release after that, they will not be present in released packages either. + + If your catalog-info.yaml files still contain this field after the deletion, they will still be + valid and your ingestion will not break, but they won't be visible in the types for consuming code, and the expected relations will not be generated based on them either. + ## 0.3.0 ### Minor Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 7956454ca9..53f56c6600 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index d198444c86..d1770eaf1f 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/cli +## 0.3.2 + +### Patch Changes + +- 294295453: Only load config that applies to the target package for frontend build and serve tasks. Also added `--package ` flag to scope the config schema used by the `config:print` and `config:check` commands. +- f538e2c56: Make versions:bump install new versions of dependencies that were within the specified range as well as install new versions of transitive @backstage dependencies. +- 8697dea5b: Bump Rollup +- b623cc275: Narrow down the version range of rollup-plugin-esbuild to avoid breaking change in newer version + ## 0.3.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index db7c127128..5d8826703c 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.3.1", + "version": "0.3.2", "private": false, "publishConfig": { "access": "public" @@ -111,7 +111,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.3.1", + "@backstage/backend-common": "^0.3.2", "@backstage/config": "^0.1.1", "@backstage/core": "^0.3.2", "@backstage/dev-utils": "^0.1.4", diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index 63926daf24..79bed2dfa3 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core-api +## 0.2.3 + +### Patch Changes + +- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure + ## 0.2.2 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index c39dca3b7a..fe56d11b9e 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.2", + "version": "0.2.3", "private": false, "publishConfig": { "access": "public", @@ -42,7 +42,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 6e91a916a1..adb9d30465 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -37,28 +37,28 @@ "recursive-readdir": "^2.2.2" }, "devDependencies": { - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", - "@backstage/cli": "^0.3.1", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/config": "^0.1.1", "@backstage/core": "^0.3.2", - "@backstage/plugin-api-docs": "^0.2.2", - "@backstage/plugin-app-backend": "^0.3.0", - "@backstage/plugin-auth-backend": "^0.2.3", - "@backstage/plugin-catalog": "^0.2.3", - "@backstage/plugin-catalog-backend": "^0.2.2", + "@backstage/plugin-api-docs": "^0.3.0", + "@backstage/plugin-app-backend": "^0.3.1", + "@backstage/plugin-auth-backend": "^0.2.4", + "@backstage/plugin-catalog": "^0.2.4", + "@backstage/plugin-catalog-backend": "^0.2.3", "@backstage/plugin-circleci": "^0.2.2", "@backstage/plugin-explore": "^0.2.1", "@backstage/plugin-github-actions": "^0.2.2", "@backstage/plugin-lighthouse": "^0.2.3", "@backstage/plugin-proxy-backend": "^0.2.1", "@backstage/plugin-register-component": "^0.2.2", - "@backstage/plugin-rollbar-backend": "^0.1.3", + "@backstage/plugin-rollbar-backend": "^0.1.4", "@backstage/plugin-scaffolder": "^0.3.1", "@backstage/plugin-scaffolder-backend": "^0.3.2", "@backstage/plugin-tech-radar": "^0.3.0", - "@backstage/plugin-techdocs": "^0.2.3", - "@backstage/plugin-techdocs-backend": "^0.2.2", + "@backstage/plugin-techdocs": "^0.3.0", + "@backstage/plugin-techdocs-backend": "^0.3.0", "@backstage/plugin-user-settings": "^0.2.2", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index fde914fac7..12a776d448 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/integration +## 0.1.2 + +### Patch Changes + +- b3d4e4e57: Move the frontend visibility declarations of integrations config from @backstage/backend-common to @backstage/integration + ## 0.1.1 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 119ce51370..fd4bd0bd3a 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,7 +24,7 @@ "git-url-parse": "^11.4.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 60a5caf55b..efa94fb46f 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-api-docs +## 0.3.0 + +### Minor Changes + +- f3bb55ee3: APIs now have real entity pages that are customizable in the app. + Therefore the old entity page from this plugin is removed. + See the `packages/app` on how to create and customize the API entity page. + +### Patch Changes + +- 6f70ed7a9: Replace usage of implementsApis with relations +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index f364475bee..99f03f65ab 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.2.2", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/theme": "^0.2.1", "@kyma-project/asyncapi-react": "^0.14.2", "@material-icons/font": "^1.0.2", @@ -40,7 +40,7 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 3555da1959..1f2b3f008a 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-backend +## 0.3.1 + +### Patch Changes + +- ff1301d28: Warn if the app-backend can't start-up because the static directory that should be served is unavailable. +- Updated dependencies [3aa7efb3f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + ## 0.3.0 ### Minor Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index bf6167d46a..ac350a14b4 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", + "@backstage/backend-common": "^0.3.2", "@backstage/config-loader": "^0.3.0", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", @@ -31,7 +31,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^4.0.2" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 4a2161ad71..257c38a3e5 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-auth-backend +## 0.2.4 + +### Patch Changes + +- 50eff1d00: Allow the backend to register custom AuthProviderFactories +- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure +- Updated dependencies [3aa7efb3f] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + - @backstage/catalog-model@0.3.1 + ## 0.2.3 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 43e2c59f88..814c8c5258 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.1", + "@backstage/backend-common": "^0.3.2", "@backstage/catalog-client": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -55,7 +55,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 3bf25a494b..64f1a7ba87 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-backend +## 0.2.3 + +### Patch Changes + +- 1ec19a3f4: Ignore empty YAML documents. Having a YAML file like this is now ingested without an error: + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: Component + metadata: + name: web + spec: + type: website + --- + + ``` + + This behaves now the same way as Kubernetes handles multiple documents in a single YAML file. + +- ab94c9542: Add `providesApis` and `consumesApis` to the component entity spec. +- 2daf18e80: Start emitting all known relation types from the core entity kinds, based on their spec data. +- Updated dependencies [3aa7efb3f] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + - @backstage/catalog-model@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 26f7180d43..9a4933ea6e 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ }, "dependencies": { "@azure/msal-node": "^1.0.0-alpha.8", - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", @@ -48,7 +48,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/test-utils": "^0.1.3", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 9137b7e948..b1c3cefa51 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog +## 0.2.4 + +### Patch Changes + +- 6f70ed7a9: Replace usage of implementsApis with relations +- Updated dependencies [4b53294a6] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] + - @backstage/plugin-techdocs@0.3.0 + - @backstage/catalog-model@0.3.1 + ## 0.2.3 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 7b58358734..f039749f64 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", "@backstage/plugin-scaffolder": "^0.3.1", - "@backstage/plugin-techdocs": "^0.2.3", + "@backstage/plugin-techdocs": "^0.3.0", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,7 +43,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@microsoft/microsoft-graph-types": "^1.25.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index ec042b9ae1..8aba667366 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-rollbar-backend +## 0.1.4 + +### Patch Changes + +- 3a201c5d5: Add config schema for the rollbar & rollbar-backend plugins +- Updated dependencies [3aa7efb3f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + ## 0.1.3 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 0f0b8bc366..c91bf0cb9e 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar-backend", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.0", + "@backstage/backend-common": "^0.3.2", "@backstage/config": "^0.1.1", "@types/express": "^4.17.6", "axios": "^0.20.0", @@ -37,7 +37,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.3.0", + "@backstage/cli": "^0.3.2", "@types/supertest": "^2.0.8", "supertest": "^4.0.2" }, diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 1ccfe16c41..095f04104a 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-rollbar +## 0.2.4 + +### Patch Changes + +- 3a201c5d5: Add config schema for the rollbar & rollbar-backend plugins +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + ## 0.2.3 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 64850c0dee..35b057c054 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,7 +37,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index d9054230e2..0a396efd41 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-techdocs-backend +## 0.3.0 + +### Minor Changes + +- 4b53294a6: - Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. + - techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` + +### Patch Changes + +- Updated dependencies [3aa7efb3f] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [b3d4e4e57] + - @backstage/backend-common@0.3.2 + - @backstage/catalog-model@0.3.1 + ## 0.2.2 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index a09adb7380..bf67108457 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.2.2", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.3.1", - "@backstage/catalog-model": "^0.3.0", + "@backstage/backend-common": "^0.3.2", + "@backstage/catalog-model": "^0.3.1", "@backstage/config": "^0.1.1", "@types/dockerode": "^2.5.34", "@types/express": "^4.17.6", @@ -39,7 +39,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "supertest": "^4.0.2" }, "files": [ diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 3f46e1358b..5076362774 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-techdocs +## 0.3.0 + +### Minor Changes + +- 4b53294a6: - Use techdocs annotation to add repo_url if missing in mkdocs.yml. Having repo_url creates a Edit button on techdocs pages. + - techdocs-backend: API endpoint `/metadata/mkdocs/*` renamed to `/metadata/techdocs/*` + +### Patch Changes + +- Updated dependencies [6f70ed7a9] +- Updated dependencies [ab94c9542] +- Updated dependencies [2daf18e80] +- Updated dependencies [069cda35f] +- Updated dependencies [700a212b4] + - @backstage/plugin-catalog@0.2.4 + - @backstage/catalog-model@0.3.1 + - @backstage/core-api@0.2.3 + ## 0.2.3 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 23d7a25b57..c8bca7d07e 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.2.3", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.3.0", + "@backstage/catalog-model": "^0.3.1", "@backstage/core": "^0.3.2", - "@backstage/core-api": "^0.2.1", - "@backstage/plugin-catalog": "^0.2.3", + "@backstage/core-api": "^0.2.3", + "@backstage/plugin-catalog": "^0.2.4", "@backstage/test-utils": "^0.1.3", "@backstage/theme": "^0.2.1", "@material-ui/core": "^4.11.0", @@ -39,7 +39,7 @@ "sanitize-html": "^1.27.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 1162318d063521423d1a21ab959d3482c9a3cc0c Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Fri, 27 Nov 2020 11:55:00 +0100 Subject: [PATCH 106/195] Fix remaining test issues on Windows #3459 was on the right way but missed some places. --- .../config-loader/src/lib/schema/collect.test.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/config-loader/src/lib/schema/collect.test.ts b/packages/config-loader/src/lib/schema/collect.test.ts index 2427ab994d..94fe2154c1 100644 --- a/packages/config-loader/src/lib/schema/collect.test.ts +++ b/packages/config-loader/src/lib/schema/collect.test.ts @@ -115,15 +115,15 @@ describe('collectConfigSchemas', () => { await expect(collectConfigSchemas(['a'])).resolves.toEqual([ { - path: 'node_modules/b/package.json', + path: path.join('node_modules', 'b', 'package.json'), value: { ...mockSchema, title: 'b' }, }, { - path: 'node_modules/c1/package.json', + path: path.join('node_modules', 'c1', 'package.json'), value: { ...mockSchema, title: 'c1' }, }, { - path: 'node_modules/d1/package.json', + path: path.join('node_modules', 'd1', 'package.json'), value: { ...mockSchema, title: 'd1' }, }, ]); @@ -224,7 +224,11 @@ describe('collectConfigSchemas', () => { }); await expect(collectConfigSchemas(['a'])).rejects.toThrow( - 'Invalid schema in node_modules/a/schema.d.ts, missing Config export', + `Invalid schema in ${path.join( + 'node_modules', + 'a', + 'schema.d.ts', + )}, missing Config export`, ); }); }); From b08351ebb427387fefc341212a14c5f82e0ac407 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 27 Nov 2020 14:15:08 +0100 Subject: [PATCH 107/195] plugins: keep plugin package.json in sync --- plugins/circleci/package.json | 2 +- plugins/cloudbuild/package.json | 2 +- plugins/cost-insights/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/gcp-projects/package.json | 2 +- plugins/github-actions/package.json | 2 +- plugins/gitops-profiles/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/jenkins/package.json | 2 +- plugins/kubernetes/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/newrelic/package.json | 2 +- plugins/register-component/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/search/package.json | 2 +- plugins/sentry/package.json | 2 +- plugins/sonarqube/package.json | 2 +- plugins/tech-radar/package.json | 2 +- plugins/user-settings/package.json | 2 +- plugins/welcome/package.json | 2 +- 20 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index fd32ff9891..fda28733af 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 3e7f0c9175..c36669ccb9 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -39,7 +39,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 06083b855b..bc649e50ab 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -46,7 +46,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index f5f73b4faa..3c3619f2ce 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -33,7 +33,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 59bdcc547c..f07cd1b0e6 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -31,7 +31,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index b1924034f1..83ba62be95 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -40,7 +40,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 67edcca51e..78af131bac 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index bde5dea9a3..d3dc673c01 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -43,7 +43,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 961f551dfa..e47ec9edda 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 66929077f3..75b9ee098c 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index ad1264cfbb..29b5a81cb9 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -38,7 +38,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 1c2b951bc6..691b9ab862 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,7 +31,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 1564faae0e..ad4432be83 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -36,7 +36,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 791221bc62..5a6a970851 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -41,7 +41,7 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/search/package.json b/plugins/search/package.json index 021e3f71f3..ddc7a2d687 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -34,7 +34,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 4c10936148..4d8473c509 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -36,7 +36,7 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 6a9d0fe0d4..d5635c2770 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 849ce3bc4d..532fc26e9c 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -35,7 +35,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 80bcdeffdb..da4c2cd1cd 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 5d81e569b8..167bb07565 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -32,7 +32,7 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.3.1", + "@backstage/cli": "^0.3.2", "@backstage/dev-utils": "^0.1.4", "@backstage/test-utils": "^0.1.3", "@testing-library/jest-dom": "^5.10.1", From 993e039c846c035c6e19f68b1d191df7fb336752 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Fri, 27 Nov 2020 15:23:19 +0100 Subject: [PATCH 108/195] chore: run yarn to update yarn.lock --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index 64c636790a..9307f7aeed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,7 +1308,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.3.0" + version "0.3.1" dependencies: "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" @@ -21172,7 +21172,7 @@ rollup-plugin-dts@1.4.13: optionalDependencies: "@babel/code-frame" "^7.10.4" -rollup-plugin-esbuild@^2.0.0: +rollup-plugin-esbuild@2.3.x: version "2.3.0" resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" integrity sha512-lNbWDRaClwXauaIybuaiOBRWgxcp3GkZu0UUix9yH/OHFgVohKmzlU0NtNbvfbxAcfEooyuBRAv6YCaJl1Mbpw== From b26e3dacd9b4084e812f73f27afe59bede8b0e69 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 27 Nov 2020 16:37:48 +0100 Subject: [PATCH 109/195] Fixdependency version --- plugins/catalog-import/package.json | 2 +- yarn.lock | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 8efa765224..eb3ce1bd2c 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -21,7 +21,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.2.0", + "@backstage/catalog-model": "^0.3.0", "@backstage/core": "^0.3.2", "@backstage/plugin-catalog": "^0.2.0", "@backstage/plugin-catalog-backend": "^0.2.2", diff --git a/yarn.lock b/yarn.lock index b8093d125d..b37657b34a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,9 +1308,7 @@ to-fast-properties "^2.0.0" "@backstage/catalog-model@^0.2.0": - version "0.2.0" - resolved "https://registry.npmjs.org/@backstage/catalog-model/-/catalog-model-0.2.0.tgz#e3fe2a4ddeb6a9b6ec480c80cb2b9c39cb245576" - integrity sha512-Y1ocdRpBlxK/VrJQjHlQd0bgADECd1B2NRjwd8ss46ibT5hwLvMOfD80+Fa7oPLu0ktJrH4lq0pNIIJIml48zA== + version "0.3.0" dependencies: "@backstage/config" "^0.1.1" "@types/json-schema" "^7.0.5" From 3f947b70d18d536f9c054889513f1b4ca7542b80 Mon Sep 17 00:00:00 2001 From: Peter Colapietro Date: Fri, 27 Nov 2020 12:08:37 -0500 Subject: [PATCH 110/195] fix(react): use fragment (#3478) The empty state's description is already wrapped in a Typography component so the one in MissingImplementsApisEmptyState is redundant. https://github.com/backstage/backstage/blob/bbaadb63f88d0f7d42acb9fa1b1644f85bf1043e/packages/core/src/components/EmptyState/EmptyState.tsx#L57 By wrapping the EmptyState's description in a Typography the following warning is output to the console ``` react-dom.development.js?1930:89 Warning: validateDOMNesting(...):

cannot appear as a descendant of

. in p (created by ForwardRef(Typography)) in ForwardRef(Typography) (created by WithStyles(ForwardRef(Typography))) in WithStyles(ForwardRef(Typography)) (created by MissingImplementsApisEmptyState) in p (created by ForwardRef(Typography)) in ForwardRef(Typography) (created by WithStyles(ForwardRef(Typography))) in WithStyles(ForwardRef(Typography)) (created by EmptyState) in div (created by ForwardRef(Grid)) in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid))) in WithStyles(ForwardRef(Grid)) (created by EmptyState) in div (created by ForwardRef(Grid)) in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid))) in WithStyles(ForwardRef(Grid)) (created by EmptyState) in div (created by ForwardRef(Grid)) in ForwardRef(Grid) (created by WithStyles(ForwardRef(Grid))) in WithStyles(ForwardRef(Grid)) (created by EmptyState) in EmptyState (created by MissingImplementsApisEmptyState) in MissingImplementsApisEmptyState (created by Router) in Router (at EntityPage.tsx:78) in article (created by Content) in Content (created by Layout) in Layout (created by EntityPageLayout) in div (created by Page) in ThemeProvider (created by Page) in Page (created by EntityPageLayout) in EntityPageLayout (at EntityPage.tsx:64) in ServiceEntityPage (at EntityPage.tsx:127) in EntityPage (created by EntityPageSwitch) in EntityPageSwitch (created by Router) in EntityProvider (created by Router) in Route (created by Router) in Routes (created by Router) in Router (at App.tsx:46) in Route (at App.tsx:44) in Routes (at App.tsx:42) in div (created by SidebarPage) in SidebarPage (at App.tsx:40) in Route (created by AppRouter) in Routes (created by AppRouter) in Router (created by BrowserRouter) in BrowserRouter (created by AppRouter) in AppRouter (at App.tsx:39) in CssBaseline (created by WithStyles(CssBaseline)) in WithStyles(CssBaseline) (created by AppThemeProvider) in ThemeProvider (created by AppThemeProvider) in AppThemeProvider (created by Provider) in AppContextProvider (created by Provider) in ApiProvider (created by Provider) in Provider (at App.tsx:36) in App (at src/index.tsx:6) ``` Co-authored-by: Peter Colapietro --- .../MissingImplementsApisEmptyState.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx index f13848bd19..fbb8810088 100644 --- a/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx +++ b/plugins/api-docs/src/catalog/MissingImplementsApisEmptyState/MissingImplementsApisEmptyState.tsx @@ -47,10 +47,10 @@ export const MissingImplementsApisEmptyState = () => { missing="field" title="No APIs implemented by this entity" description={ - + <> Components can implement APIs that are displayed on this page. You need to fill the providesApis field to enable this tool. - + } action={ <> From b9d1dc9f58adf7f59a39c0d0b63f874bab2ec13e Mon Sep 17 00:00:00 2001 From: Peter Colapietro Date: Fri, 27 Nov 2020 11:15:04 -0500 Subject: [PATCH 111/195] fix(react): add key prop to NotFoundErrorPage route Avoids: ``` Warning: Each child in a list should have a unique "key" prop. Check the render method of `App`. See https://fb.me/react-warning-keys for more information. in Route in App (at src/index.tsx:6) ``` --- packages/core-api/src/app/App.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 9c38c20b44..3d32259337 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -184,7 +184,13 @@ export class PrivateAppImpl implements BackstageApp { } } - routes.push(} />); + routes.push( + } + />, + ); return routes; } From 4a655c89dbb196fd013a7e877214d4f5c1b40ebd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 28 Nov 2020 17:02:02 +0100 Subject: [PATCH 112/195] cli,create-app: bump esbuild + remove resolutions override in create-app --- .changeset/light-nails-crash.md | 5 +++ .changeset/perfect-dryers-sell.md | 13 ++++++++ packages/cli/package.json | 4 +-- packages/cli/src/lib/builder/packager.ts | 17 ++++++++++ .../templates/default-app/package.json.hbs | 3 -- yarn.lock | 33 ++++++++++++++----- 6 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 .changeset/light-nails-crash.md create mode 100644 .changeset/perfect-dryers-sell.md diff --git a/.changeset/light-nails-crash.md b/.changeset/light-nails-crash.md new file mode 100644 index 0000000000..c7791d1027 --- /dev/null +++ b/.changeset/light-nails-crash.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Bump versions of `esbuild` and `rollup-plugin-esbuild` diff --git a/.changeset/perfect-dryers-sell.md b/.changeset/perfect-dryers-sell.md new file mode 100644 index 0000000000..e1c5592162 --- /dev/null +++ b/.changeset/perfect-dryers-sell.md @@ -0,0 +1,13 @@ +--- +'@backstage/create-app': patch +--- + +Removed `"resolutions"` entry for `esbuild` in the root `package.json` in order to use the version specified by `@backstage/cli`. + +To apply this change to an existing app, remove the following from your root `package.json`: + +```json +"resolutions": { + "esbuild": "0.6.3" +}, +``` diff --git a/packages/cli/package.json b/packages/cli/package.json index 5d8826703c..eff546168e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -59,7 +59,7 @@ "css-loader": "^3.5.3", "dashify": "^2.0.0", "diff": "^4.0.2", - "esbuild": "^0.7.7", + "esbuild": "^0.8.16", "eslint": "^7.1.0", "eslint-config-prettier": "^6.0.0", "eslint-formatter-friendly": "^7.0.0", @@ -88,7 +88,7 @@ "replace-in-file": "^6.0.0", "rollup": "2.33.x", "rollup-plugin-dts": "1.4.13", - "rollup-plugin-esbuild": "2.3.x", + "rollup-plugin-esbuild": "2.6.x", "rollup-plugin-peer-deps-external": "^2.2.2", "rollup-plugin-postcss": "^3.1.1", "rollup-plugin-typescript2": "^0.27.3", diff --git a/packages/cli/src/lib/builder/packager.ts b/packages/cli/src/lib/builder/packager.ts index 65e33bcb7a..ed76e23df3 100644 --- a/packages/cli/src/lib/builder/packager.ts +++ b/packages/cli/src/lib/builder/packager.ts @@ -83,6 +83,23 @@ async function build(config: RollupOptions) { } export const buildPackage = async (options: BuildOptions) => { + try { + const { resolutions } = await fs.readJson( + paths.resolveTargetRoot('package.json'), + ); + if (resolutions?.esbuild) { + console.warn( + chalk.red( + 'Your root package.json contains a "resolutions" entry for "esbuild". This was ' + + 'included in older @backstage/create-app templates in order to work around build ' + + 'issues that have since been fixed. Please remove the entry and run `yarn install`', + ), + ); + } + } catch { + /* Errors ignored, this is just a warning */ + } + const configs = await makeConfigs(options); await fs.remove(paths.resolveTarget('dist')); await Promise.all(configs.map(build)); diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index 0116287a46..975a5a2ed2 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -32,9 +32,6 @@ "lerna": "^3.20.2", "prettier": "^1.19.1" }, - "resolutions": { - "esbuild": "0.6.3" - }, "prettier": "@spotify/prettier-config", "lint-staged": { "*.{js,jsx,ts,tsx}": [ diff --git a/yarn.lock b/yarn.lock index 9307f7aeed..cf67cf7aa2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3836,6 +3836,14 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^4.1.0": + version "4.1.0" + resolved "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.0.tgz#0dcc61c780e39257554feb7f77207dceca13c838" + integrity sha512-TrBhfJkFxA+ER+ew2U2/fHbebhLT/l/2pRk0hfj9KusXUuRXd2v0R58AfaZK9VXDQ4TogOSEmICVrQAA3zFnHQ== + dependencies: + estree-walker "^2.0.1" + picomatch "^2.2.2" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -11153,10 +11161,10 @@ es6-weak-map@^2.0.2: es6-iterator "^2.0.3" es6-symbol "^3.1.1" -esbuild@^0.7.7: - version "0.7.7" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.7.7.tgz#fd86332d1c0a047231bd6da930666028c40c14bf" - integrity sha512-1Ub4BBsWwPdxkwjyuXdKrgMIZROZ82ULIRFclOzXXVrqKOv9rMDoShRf23WEbPfeEA94z/BScKyUOyooyQ3XnQ== +esbuild@^0.8.16: + version "0.8.16" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.8.16.tgz#8ae34b15d938e8b8b5ac2459414fe3e7fd7dd6b2" + integrity sha512-HMvPNxDIhEGO/YUh8oO8oxQ1g+ttWz2anUF7NJmQglj2XfJS8zd8mP0Sb2y+jE1SVk3UjD/rYhdsEOFULN9/xw== escalade@^3.0.1: version "3.0.2" @@ -15106,6 +15114,11 @@ jose@^2.0.2: dependencies: "@panva/asn1.js" "^1.0.0" +joycon@^2.2.5: + version "2.2.5" + resolved "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615" + integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -21172,12 +21185,14 @@ rollup-plugin-dts@1.4.13: optionalDependencies: "@babel/code-frame" "^7.10.4" -rollup-plugin-esbuild@2.3.x: - version "2.3.0" - resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.3.0.tgz#7c107d4508af80d30966a148b306cb7d5b36b258" - integrity sha512-lNbWDRaClwXauaIybuaiOBRWgxcp3GkZu0UUix9yH/OHFgVohKmzlU0NtNbvfbxAcfEooyuBRAv6YCaJl1Mbpw== +rollup-plugin-esbuild@2.6.x: + version "2.6.0" + resolved "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-2.6.0.tgz#80336399b113a179ccb2af5bdf7c03f061f37146" + integrity sha512-wtyDAX8kNABrBfwkrrG2xLRKRGSWUyMBURS067IRvRMpgJlLRo14WcFl95uGc4iYWfdAx436/z1LMzYqdlY4ig== dependencies: - "@rollup/pluginutils" "^3.1.0" + "@rollup/pluginutils" "^4.1.0" + joycon "^2.2.5" + strip-json-comments "^3.1.1" rollup-plugin-peer-deps-external@^2.2.2: version "2.2.3" From 85af34ffd58901bb625328f8e1e5511098f3b98f Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sat, 28 Nov 2020 20:00:18 +0100 Subject: [PATCH 113/195] techdocs: Use EntityName from catalog-model as Entity type --- plugins/techdocs/dev/api.ts | 12 ++----- plugins/techdocs/src/api.ts | 32 +++++++------------ .../techdocs/src/reader/components/Reader.tsx | 13 ++++---- .../reader/components/TechDocsPageHeader.tsx | 10 +++--- .../src/reader/transformers/addBaseUrl.ts | 5 ++- plugins/techdocs/src/types.ts | 27 ---------------- 6 files changed, 28 insertions(+), 71 deletions(-) delete mode 100644 plugins/techdocs/src/types.ts diff --git a/plugins/techdocs/dev/api.ts b/plugins/techdocs/dev/api.ts index 60a17c3dc3..ae2b8c58ae 100644 --- a/plugins/techdocs/dev/api.ts +++ b/plugins/techdocs/dev/api.ts @@ -13,9 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { ParsedEntityId } from '../src/types'; - +import { EntityName } from '@backstage/catalog-model'; import { TechDocsStorage } from '../src/api'; export class TechDocsDevStorageApi implements TechDocsStorage { @@ -25,7 +23,7 @@ export class TechDocsDevStorageApi implements TechDocsStorage { this.apiOrigin = apiOrigin; } - async getEntityDocs(entityId: ParsedEntityId, path: string) { + async getEntityDocs(entityId: EntityName, path: string) { const { name } = entityId; const url = `${this.apiOrigin}/${name}/${path}`; @@ -41,11 +39,7 @@ export class TechDocsDevStorageApi implements TechDocsStorage { return request.text(); } - getBaseUrl( - oldBaseUrl: string, - entityId: ParsedEntityId, - path: string, - ): string { + getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string { const { name } = entityId; return new URL(oldBaseUrl, `${this.apiOrigin}/${name}/${path}`).toString(); } diff --git a/plugins/techdocs/src/api.ts b/plugins/techdocs/src/api.ts index 368705fa22..c8581ec5d3 100644 --- a/plugins/techdocs/src/api.ts +++ b/plugins/techdocs/src/api.ts @@ -15,7 +15,7 @@ */ import { createApiRef } from '@backstage/core'; -import { ParsedEntityId } from './types'; +import { EntityName } from '@backstage/catalog-model'; export const techdocsStorageApiRef = createApiRef({ id: 'plugin.techdocs.storageservice', @@ -28,17 +28,13 @@ export const techdocsApiRef = createApiRef({ }); export interface TechDocsStorage { - getEntityDocs(entityId: ParsedEntityId, path: string): Promise; - getBaseUrl( - oldBaseUrl: string, - entityId: ParsedEntityId, - path: string, - ): string; + getEntityDocs(entityId: EntityName, path: string): Promise; + getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string; } export interface TechDocs { - getTechDocsMetadata(entityId: ParsedEntityId): Promise; - getEntityMetadata(entityId: ParsedEntityId): Promise; + getTechDocsMetadata(entityId: EntityName): Promise; + getEntityMetadata(entityId: EntityName): Promise; } /** @@ -60,9 +56,9 @@ export class TechDocsApi implements TechDocs { * static files. It includes necessary data about the docs site. This method requests techdocs-backend * which retrieves the TechDocs metadata. * - * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + * @param {EntityName} entityId Object containing entity data like name, namespace, etc. */ - async getTechDocsMetadata(entityId: ParsedEntityId) { + async getTechDocsMetadata(entityId: EntityName) { const { kind, namespace, name } = entityId; const requestUrl = `${this.apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`; @@ -79,9 +75,9 @@ export class TechDocsApi implements TechDocs { * This method requests techdocs-backend which uses the catalog APIs to respond with filtered * information required here. * - * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + * @param {EntityName} entityId Object containing entity data like name, namespace, etc. */ - async getEntityMetadata(entityId: ParsedEntityId) { + async getEntityMetadata(entityId: EntityName) { const { kind, namespace, name } = entityId; const requestUrl = `${this.apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`; @@ -108,12 +104,12 @@ export class TechDocsStorageApi implements TechDocsStorage { /** * Fetch HTML content as text for an individual docs page in an entity's docs site. * - * @param {ParsedEntityId} entityId Object containing entity data like name, namespace, etc. + * @param {EntityName} entityId Object containing entity data like name, namespace, etc. * @param {string} path The unique path to an individual docs page e.g. overview/what-is-new * @returns {string} HTML content of the docs page as string * @throws {Error} Throws error when the page is not found. */ - async getEntityDocs(entityId: ParsedEntityId, path: string) { + async getEntityDocs(entityId: EntityName, path: string) { const { kind, namespace, name } = entityId; const url = `${this.apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`; @@ -135,11 +131,7 @@ export class TechDocsStorageApi implements TechDocsStorage { return request.text(); } - getBaseUrl( - oldBaseUrl: string, - entityId: ParsedEntityId, - path: string, - ): string { + getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string { const { kind, namespace, name } = entityId; return new URL( diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index fe3a34564c..bdf91936ba 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import React from 'react'; -import { useApi } from '@backstage/core'; -import { useShadowDom } from '..'; import { useAsync } from 'react-use'; -import { techdocsStorageApiRef } from '../../api'; -import { useParams, useNavigate } from 'react-router-dom'; -import { ParsedEntityId } from '../../types'; import { useTheme } from '@material-ui/core'; +import { useParams, useNavigate } from 'react-router-dom'; +import { EntityName } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core'; import { BackstageTheme } from '@backstage/theme'; +import { useShadowDom } from '..'; +import { techdocsStorageApiRef } from '../../api'; import TechDocsProgressBar from './TechDocsProgressBar'; import transformer, { @@ -39,7 +38,7 @@ import transformer, { import { TechDocsNotFound } from './TechDocsNotFound'; type Props = { - entityId: ParsedEntityId; + entityId: EntityName; onReady?: () => void; }; diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx index 475b87da6f..d7a5a3c48f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPageHeader.tsx @@ -15,14 +15,14 @@ */ import React from 'react'; -import CodeIcon from '@material-ui/icons/Code'; -import { Header, HeaderLabel, Link } from '@backstage/core'; -import { CircularProgress } from '@material-ui/core'; -import { ParsedEntityId } from '../../types'; import { AsyncState } from 'react-use/lib/useAsync'; +import { CircularProgress } from '@material-ui/core'; +import CodeIcon from '@material-ui/icons/Code'; +import { EntityName } from '@backstage/catalog-model'; +import { Header, HeaderLabel, Link } from '@backstage/core'; type TechDocsPageHeaderProps = { - entityId: ParsedEntityId; + entityId: EntityName; metadataRequest: { entity: AsyncState; techdocs: AsyncState; diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts index 7346ca7ce2..8d9d5a7294 100644 --- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts +++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { EntityName } from '@backstage/catalog-model'; import type { Transformer } from './index'; import { TechDocsStorage } from '../../api'; -import { ParsedEntityId } from '../../types'; type AddBaseUrlOptions = { techdocsStorageApi: TechDocsStorage; - entityId: ParsedEntityId; + entityId: EntityName; path: string; }; diff --git a/plugins/techdocs/src/types.ts b/plugins/techdocs/src/types.ts deleted file mode 100644 index 341d3f0c92..0000000000 --- a/plugins/techdocs/src/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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. - */ - -/** - * To uniquely identify an entity in Backstage. - * @property {string} kind - * @property {string} namespace - * @property {string} name - */ -export type ParsedEntityId = { - kind: string; - namespace?: string; - name: string; -}; From da2ad65cb5a0079cfd67a911e6e2ad3c6d0a12bc Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sat, 28 Nov 2020 21:09:35 +0100 Subject: [PATCH 114/195] techdocs: Add changeset for entity type change --- .changeset/tidy-actors-repair.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tidy-actors-repair.md diff --git a/.changeset/tidy-actors-repair.md b/.changeset/tidy-actors-repair.md new file mode 100644 index 0000000000..4869400556 --- /dev/null +++ b/.changeset/tidy-actors-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Use type EntityName from catalog-model for entities From d40eddac9d2515cf84e5eb03c3c08f114a1f8b45 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 29 Nov 2020 00:22:10 -0500 Subject: [PATCH 115/195] Refactor visibility typo --- packages/cli/src/commands/config/print.ts | 12 ++++++------ packages/cli/src/lib/config.ts | 2 +- .../config-loader/src/lib/schema/compile.test.ts | 2 +- .../config-loader/src/lib/schema/filtering.test.ts | 4 ++-- packages/config-loader/src/lib/schema/load.test.ts | 6 +++--- packages/config-loader/src/lib/schema/load.ts | 6 +++--- packages/config-loader/src/lib/schema/types.ts | 2 +- plugins/app-backend/src/lib/config.ts | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 8148bfc9f8..8be88adce7 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -25,7 +25,7 @@ export default async (cmd: Command) => { args: cmd.config, fromPackage: cmd.package, }); - const visibility = getVisiblityOption(cmd); + const visibility = getVisibilityOption(cmd); const data = serializeConfigData(appConfigs, schema, visibility); if (cmd.format === 'json') { @@ -35,7 +35,7 @@ export default async (cmd: Command) => { } }; -function getVisiblityOption(cmd: Command): ConfigVisibility { +function getVisibilityOption(cmd: Command): ConfigVisibility { if (cmd.frontend && cmd.withSecrets) { throw new Error('Not allowed to combine frontend and secret config'); } @@ -50,14 +50,14 @@ function getVisiblityOption(cmd: Command): ConfigVisibility { function serializeConfigData( appConfigs: AppConfig[], schema: ConfigSchema, - visiblity: ConfigVisibility, + visibility: ConfigVisibility, ) { - if (visiblity === 'frontend') { + if (visibility === 'frontend') { const frontendConfigs = schema.process(appConfigs, { - visiblity: ['frontend'], + visibility: ['frontend'], }); return ConfigReader.fromConfigs(frontendConfigs).get(); - } else if (visiblity === 'secret') { + } else if (visibility === 'secret') { return ConfigReader.fromConfigs(appConfigs).get(); } diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index f6cc9c4269..88aac1e33c 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -51,7 +51,7 @@ export async function loadCliConfig(options: Options) { try { const frontendAppConfigs = schema.process(appConfigs, { - visiblity: ['frontend'], + visibility: ['frontend'], }); const frontendConfig = ConfigReader.fromConfigs(frontendAppConfigs); diff --git a/packages/config-loader/src/lib/schema/compile.test.ts b/packages/config-loader/src/lib/schema/compile.test.ts index 91e7aa687e..f03d7e6d10 100644 --- a/packages/config-loader/src/lib/schema/compile.test.ts +++ b/packages/config-loader/src/lib/schema/compile.test.ts @@ -89,7 +89,7 @@ describe('compileConfigSchemas', () => { }); }); - it('should reject visiblity conflicts', () => { + it('should reject visibility conflicts', () => { expect(() => compileConfigSchemas([ { diff --git a/packages/config-loader/src/lib/schema/filtering.test.ts b/packages/config-loader/src/lib/schema/filtering.test.ts index bfad2ca95a..f6926e3560 100644 --- a/packages/config-loader/src/lib/schema/filtering.test.ts +++ b/packages/config-loader/src/lib/schema/filtering.test.ts @@ -38,7 +38,7 @@ const data = { objS: { never: 'here' }, }; -const visiblity = new Map( +const visibility = new Map( Object.entries({ '.arr.0': 'frontend', '.arr.1': 'backend', @@ -100,6 +100,6 @@ describe('filterByVisibility', () => { ], [['frontend', 'backend', 'secret'], data], ])('should filter correctly with %p', (filter, expected) => { - expect(filterByVisibility(data, filter, visiblity)).toEqual(expected); + expect(filterByVisibility(data, filter, visibility)).toEqual(expected); }); }); diff --git a/packages/config-loader/src/lib/schema/load.test.ts b/packages/config-loader/src/lib/schema/load.test.ts index a13f36be20..baf63525bb 100644 --- a/packages/config-loader/src/lib/schema/load.test.ts +++ b/packages/config-loader/src/lib/schema/load.test.ts @@ -61,12 +61,12 @@ describe('loadConfigSchema', () => { const configs = [{ data: { key1: 'a', key2: 2 }, context: 'test' }]; expect(schema.process(configs)).toEqual(configs); - expect(schema.process(configs, { visiblity: ['frontend'] })).toEqual([ + expect(schema.process(configs, { visibility: ['frontend'] })).toEqual([ { data: { key1: 'a' }, context: 'test' }, ]); expect( schema.process(configs, { - visiblity: ['frontend'], + visibility: ['frontend'], valueTransform: () => 'X', }), ).toEqual([{ data: { key1: 'X' }, context: 'test' }]); @@ -79,7 +79,7 @@ describe('loadConfigSchema', () => { const serialized = schema.serialize(); const schema2 = await loadConfigSchema({ serialized }); - expect(schema2.process(configs, { visiblity: ['frontend'] })).toEqual([ + expect(schema2.process(configs, { visibility: ['frontend'] })).toEqual([ { data: { key1: 'a' }, context: 'test' }, ]); expect(() => diff --git a/packages/config-loader/src/lib/schema/load.ts b/packages/config-loader/src/lib/schema/load.ts index 01a9499983..67b9762f51 100644 --- a/packages/config-loader/src/lib/schema/load.ts +++ b/packages/config-loader/src/lib/schema/load.ts @@ -57,7 +57,7 @@ export async function loadConfigSchema( return { process( configs: AppConfig[], - { visiblity, valueTransform } = {}, + { visibility, valueTransform } = {}, ): AppConfig[] { const result = validate(configs); if (result.errors) { @@ -70,12 +70,12 @@ export async function loadConfigSchema( let processedConfigs = configs; - if (visiblity) { + if (visibility) { processedConfigs = processedConfigs.map(({ data, context }) => ({ context, data: filterByVisibility( data, - visiblity, + visibility, result.visibilityByPath, valueTransform, ), diff --git a/packages/config-loader/src/lib/schema/types.ts b/packages/config-loader/src/lib/schema/types.ts index 7705242b31..db3d964aa0 100644 --- a/packages/config-loader/src/lib/schema/types.ts +++ b/packages/config-loader/src/lib/schema/types.ts @@ -87,7 +87,7 @@ type ConfigProcessingOptions = { * The visibilities that should be included in the output data. * If omitted, the data will not be filtered by visibility. */ - visiblity?: ConfigVisibility[]; + visibility?: ConfigVisibility[]; /** * A transform function that can be used to transform primitive configuration values diff --git a/plugins/app-backend/src/lib/config.ts b/plugins/app-backend/src/lib/config.ts index f076967280..99590c1e2d 100644 --- a/plugins/app-backend/src/lib/config.ts +++ b/plugins/app-backend/src/lib/config.ts @@ -88,7 +88,7 @@ export async function readConfigs(options: ReadOptions): Promise { const frontendConfigs = await schema.process( [{ data: config.get() as JsonObject, context: 'app' }], - { visiblity: ['frontend'] }, + { visibility: ['frontend'] }, ); appConfigs.push(...frontendConfigs); } From 4e70917597a65e86742b93372868f1f46f4d30b3 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 29 Nov 2020 00:28:48 -0500 Subject: [PATCH 116/195] Reference changeset --- .changeset/grumpy-crews-build.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/grumpy-crews-build.md diff --git a/.changeset/grumpy-crews-build.md b/.changeset/grumpy-crews-build.md new file mode 100644 index 0000000000..2b7eece4e7 --- /dev/null +++ b/.changeset/grumpy-crews-build.md @@ -0,0 +1,7 @@ +--- +'example-app': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +--- + +Fix typo of "visibility" in multiple references From 74175b988e4fca1647fd0a0523bfd1a3045fa647 Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Sun, 29 Nov 2020 02:50:33 -0500 Subject: [PATCH 117/195] Refactor typo in variable name (#3486) --- .../src/components/ApiExplorerTable/ApiExplorerTable.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx index 408dc44151..494695e277 100644 --- a/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx +++ b/plugins/api-docs/src/components/ApiExplorerTable/ApiExplorerTable.test.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { apiDocsConfigRef } from '../../config'; import { ApiExplorerTable } from './ApiExplorerTable'; -const entites: Entity[] = [ +const entities: Entity[] = [ { apiVersion: 'backstage.io/v1alpha1', kind: 'API', @@ -70,7 +70,7 @@ describe('ApiCatalogTable component', () => { const rendered = render( wrapInTestApp( - + , ), ); From 4122b3031fb01ef7b5e780799deea91c857ce717 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Sun, 29 Nov 2020 13:35:38 +0100 Subject: [PATCH 118/195] feat(backstage.io): Allow to zoom images like Medium --- .github/styles/vocab.txt | 1 + docs/features/techdocs/architecture.md | 4 ++-- microsite/README.md | 5 +++++ microsite/siteConfig.js | 6 +++++- microsite/static/css/custom.css | 5 +++++ microsite/static/js/medium-zoom.js | 11 +++++++++++ 6 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 microsite/static/js/medium-zoom.js diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index a0841fe7d0..7f889392ac 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -230,3 +230,4 @@ yaml Zalando Zhou Zolotusky +zoomable diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md index 01497b31bc..f502b3836e 100644 --- a/docs/features/techdocs/architecture.md +++ b/docs/features/techdocs/architecture.md @@ -9,7 +9,7 @@ description: Documentation on TechDocs Architecture When you deploy Backstage (with TechDocs enabled by default), you get a basic out-of-the box experience. -![TechDocs Architecture diagram](../../assets/techdocs/architecture-basic.drawio.svg) +TechDocs Architecture diagram > Note: See below for our recommended deployment architecture which takes care > of stability, scalability and speed. @@ -43,7 +43,7 @@ channel to talk about it. This is how we recommend deploying TechDocs in production environment. -![TechDocs Architecture diagram](../../assets/techdocs/architecture-recommended.drawio.svg) +TechDocs Architecture diagram The key difference in the recommended deployment approach is where the docs are built. diff --git a/microsite/README.md b/microsite/README.md index d244caa22a..9589def3e3 100644 --- a/microsite/README.md +++ b/microsite/README.md @@ -204,3 +204,8 @@ For more information about custom pages, click [here](https://docusaurus.io/docs # Full Documentation Full documentation can be found on the [website](https://docusaurus.io/). + +## Additional notes + +- If you want to make images zoomable on click, add the `data-zoomable` attribute to your `img` element. + - In a docs or blog `.md` file, convert `![This is image](/microsite/static/img/code.png)` syntax to `This is image` diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js index 6e8f8edddf..517cac2496 100644 --- a/microsite/siteConfig.js +++ b/microsite/siteConfig.js @@ -86,7 +86,11 @@ const siteConfig = { }, // Add custom scripts here that would be placed in