From ac3b033665e1d5b9833e40faf269cc28db66433a Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Wed, 21 Oct 2020 10:01:10 +0200 Subject: [PATCH 01/94] 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 02/94] 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 03/94] 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 04/94] 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 05/94] 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 06/94] 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 07/94] 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 14/94] 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 16/94] 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 17/94] 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 18/94] 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 19/94] 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 20/94] 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 21/94] 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 22/94] 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 23/94] 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 24/94] 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 25/94] 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 26/94] 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 27/94] 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 28/94] 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 29/94] 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 30/94] 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 31/94] 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 86ab16244a01b0183648c9e818179836e1f1c45e Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 16:55:41 -0500 Subject: [PATCH 32/94] 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 33/94] 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 34/94] 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 1b5e8fbae61dff3e0b6ced8f8b103242b1c7ef4e Mon Sep 17 00:00:00 2001 From: Brenda Sukh Date: Tue, 24 Nov 2020 17:48:01 -0500 Subject: [PATCH 35/94] 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 36/94] 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 37/94] 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 38/94] 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 39/94] 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 bd9578870d3787c1f0b03e6647b6bff6a564fc1f Mon Sep 17 00:00:00 2001 From: Ryan Vazquez Date: Wed, 25 Nov 2020 11:13:59 -0500 Subject: [PATCH 40/94] 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 faa73389f2c87f7afcd99674c6655a2bf4c628f5 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 06:29:29 +0100 Subject: [PATCH 41/94] 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 42/94] 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 43/94] 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 0dedb26e10e4972951c1c7d9c6cdf72c07bb086e Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 11:54:20 +0100 Subject: [PATCH 44/94] 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 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 45/94] 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 852229a3b88b77f1f3221a84967e61e6178deb1a Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Thu, 26 Nov 2020 15:47:21 +0100 Subject: [PATCH 46/94] 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 47/94] 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 1b7a9d7317aee61398460f46572ba9f4621794ec Mon Sep 17 00:00:00 2001 From: Dominik Henneke Date: Thu, 26 Nov 2020 16:14:42 +0100 Subject: [PATCH 48/94] 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 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 49/94] 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 50/94] 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 51/94] 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 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 52/94] 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 53/94] 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 54/94] 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 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 55/94] 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 56/94] 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 57/94] 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 b26e3dacd9b4084e812f73f27afe59bede8b0e69 Mon Sep 17 00:00:00 2001 From: Marek Calus Date: Fri, 27 Nov 2020 16:37:48 +0100 Subject: [PATCH 58/94] 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 b9d1dc9f58adf7f59a39c0d0b63f874bab2ec13e Mon Sep 17 00:00:00 2001 From: Peter Colapietro Date: Fri, 27 Nov 2020 11:15:04 -0500 Subject: [PATCH 59/94] 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 60/94] 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 61/94] 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 62/94] 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 63/94] 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 64/94] 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 65/94] 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 66/94] 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