From f74aa0c08d449defe77501800389c84e3065d7ed Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 22 Sep 2022 15:18:59 +0200 Subject: [PATCH 001/221] backend-tests: increase timeout for setting up test databases Signed-off-by: Patrik Oldsberg Signed-off-by: Spencer Henry --- .../backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts index f6177b57f7..d61948a929 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -45,7 +45,7 @@ describe('PluginTaskManagerImpl', () => { ); jest.useFakeTimers(); - }, 30_000); + }, 60_000); async function init(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); From a6075e94b5a39e96b604d2be79b30ca4dd857b71 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Thu, 22 Sep 2022 12:58:41 -0600 Subject: [PATCH 002/221] Add default prom metrics to scaffolder Signed-off-by: Spencer Henry --- .changeset/silver-games-yell.md | 51 ++++++ plugins/scaffolder-backend/package.json | 1 + .../tasks/NunjucksWorkflowRunner.ts | 163 +++++++++++++++--- .../src/scaffolder/util/metrics.ts | 73 ++++++++ yarn.lock | 10 ++ 5 files changed, 277 insertions(+), 21 deletions(-) create mode 100644 .changeset/silver-games-yell.md create mode 100644 plugins/scaffolder-backend/src/scaffolder/util/metrics.ts diff --git a/.changeset/silver-games-yell.md b/.changeset/silver-games-yell.md new file mode 100644 index 0000000000..3f9d3ec8bf --- /dev/null +++ b/.changeset/silver-games-yell.md @@ -0,0 +1,51 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-catalog-backend': patch +--- + +Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: + +- `scaffolder_task_success_count`: Tracks successful task runs. + + Labels: + + - `template`: The entity ref of the scaffolded template + - `user`: The entity ref of the user that invoked the template run + +- scaffolder_task_error_count: a count that track how many task runs error out + + Labels: + + - `template`: The entity ref of the scaffolded template + - `user`: The entity ref of the user that invoked the template run + +- scaffolder_task_duration: a histogram which tracks the duration of a task run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `result`: A boolean describing whether the task ran successfully + +- scaffolder_step_success_count: a count that tracks each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + +- scaffolder_step_error_count: a count that tracks how many steps error out + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + +- scaffolder_step_duration: a histogram which tracks the duration of each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A boolean describing whether the task ran successfully + +You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 35f7fe01e2..b96be75294 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -72,6 +72,7 @@ "octokit": "^2.0.0", "octokit-plugin-create-pull-request": "^3.10.0", "p-limit": "^3.1.0", + "prom-client": "14.0.1", "uuid": "^8.2.0", "vm2": "^3.9.11", "winston": "^3.2.1", diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 009ea1357d..f4776bc033 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -26,7 +26,7 @@ import { PassThrough } from 'stream'; import { generateExampleOutput, isTruthy } from './helper'; import { validate as validateJsonSchema } from 'jsonschema'; import { parseRepoUrl } from '../actions/builtin/publish/util'; -import { TemplateActionRegistry } from '../actions'; +import { TemplateAction, TemplateActionRegistry } from '../actions'; import { TemplateFilter, SecureTemplater, @@ -38,6 +38,7 @@ import { TaskStep, } from '@backstage/plugin-scaffolder-common'; import { UserEntity } from '@backstage/catalog-model'; +import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -96,6 +97,7 @@ const createStepLogger = ({ export class NunjucksWorkflowRunner implements WorkflowRunner { constructor(private readonly options: NunjucksWorkflowRunnerOptions) {} + private readonly tracker = scaffoldingTracker(); private isSingleTemplateString(input: string) { const { parser, nodes } = nunjucks as unknown as { @@ -200,10 +202,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { }); try { + const taskTrack = await this.tracker.taskStart(task); await fs.ensureDir(workspacePath); - await task.emitLog( - `Starting up task with ${task.spec.steps.length} steps`, - ); const context: TemplateContext = { parameters: task.spec.parameters, @@ -212,6 +212,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { }; for (const step of task.spec.steps) { + const stepTrack = await this.tracker.stepStart(task, step); try { if (step.if) { const ifResult = await this.render( @@ -220,23 +221,16 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { renderTemplate, ); if (!isTruthy(ifResult)) { - await task.emitLog( - `Skipping step ${step.id} because it's if condition was false`, - { stepId: step.id, status: 'skipped' }, - ); + await stepTrack.skipFalsy(); continue; } } - await task.emitLog(`Beginning step ${step.name}`, { - stepId: step.id, - status: 'processing', - }); - const action = this.options.actionRegistry.get(step.action); const { taskLogger, streamLogger } = createStepLogger({ task, step }); if (task.isDryRun) { + await taskTrack.skipDryRun(step, action); const redactedSecrets = Object.fromEntries( Object.entries(task.secrets ?? {}).map(secret => [ secret[0], @@ -339,20 +333,16 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { context.steps[step.id] = { output: stepOutput }; - await task.emitLog(`Finished step ${step.name}`, { - stepId: step.id, - status: 'completed', - }); + await stepTrack.markSuccessful(); } catch (err) { - await task.emitLog(String(err.stack), { - stepId: step.id, - status: 'failed', - }); + await taskTrack.markFailed(step, err); + stepTrack.markFailed(); throw err; } } const output = this.render(task.spec.output, context, renderTemplate); + taskTrack.markSuccessful(); return { output }; } finally { @@ -362,3 +352,134 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } } } + +function scaffoldingTracker() { + const taskSuccesses = createCounterMetric({ + name: 'scaffolder_task_success_count', + help: 'Count of succesful task runs', + labelNames: ['template', 'user'], + }); + const taskErrors = createCounterMetric({ + name: 'scaffolder_task_error_count', + help: 'Count of failed task runs', + labelNames: ['template', 'user'], + }); + const taskDuration = createHistogramMetric({ + name: 'scaffolder_task_duration', + help: 'Duration of a task run', + labelNames: ['template', 'result'], + }); + const stepSuccesses = createCounterMetric({ + name: 'scaffolder_step_success_count', + help: 'Count of successful step runs', + labelNames: ['template', 'step'], + }); + const stepErrors = createCounterMetric({ + name: 'scaffolder_step_error_count', + help: 'Count of failed step runs', + labelNames: ['template', 'step'], + }); + const stepDuration = createHistogramMetric({ + name: 'scaffolder_step_duration', + help: 'Duration of a step runs', + labelNames: ['template', 'step', 'result'], + }); + + async function taskStart(task: TaskContext) { + await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`); + const template = task.spec.templateInfo?.entityRef || ''; + const user = task.spec.user?.ref || ''; + + const taskTimer = taskDuration.startTimer({ + template, + }); + + async function skipDryRun( + step: TaskStep, + action: TemplateAction, + ) { + task.emitLog(`Skipping because ${action.id} does not support dry-run`, { + stepId: step.id, + status: 'skipped', + }); + } + + function markSuccessful() { + taskSuccesses.inc({ + template, + user, + }); + taskTimer({ result: 'ok' }); + } + + async function markFailed(step: TaskStep, err: Error) { + await task.emitLog(String(err.stack), { + stepId: step.id, + status: 'failed', + }); + taskErrors.inc({ + template, + user, + }); + taskTimer({ result: 'failed' }); + } + + return { + skipDryRun, + markSuccessful, + markFailed, + }; + } + + async function stepStart(task: TaskContext, step: TaskStep) { + await task.emitLog(`Beginning step ${step.name}`, { + stepId: step.id, + status: 'processing', + }); + const template = task.spec.templateInfo?.entityRef || ''; + + const stepTimer = stepDuration.startTimer({ + template, + step: step.name, + }); + + async function markSuccessful() { + await task.emitLog(`Finished step ${step.name}`, { + stepId: step.id, + status: 'completed', + }); + stepSuccesses.inc({ + template, + step: step.name, + }); + stepTimer({ result: 'ok' }); + } + + function markFailed() { + stepErrors.inc({ + template, + step: step.name, + }); + stepTimer({ result: 'failed' }); + } + + async function skipFalsy() { + await task.emitLog( + `Skipping step ${step.id} because it's if condition was false`, + { stepId: step.id, status: 'skipped' }, + ); + stepTimer({ result: 'skipped' }); + } + + return { + markSuccessful, + markFailed, + skipFalsy, + }; + } + + return { + taskStart, + stepStart, + }; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/util/metrics.ts b/plugins/scaffolder-backend/src/scaffolder/util/metrics.ts new file mode 100644 index 0000000000..207135f234 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/util/metrics.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Counter, + CounterConfiguration, + Gauge, + GaugeConfiguration, + Histogram, + HistogramConfiguration, + register, + Summary, + SummaryConfiguration, +} from 'prom-client'; + +export function createCounterMetric( + config: CounterConfiguration, +): Counter { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Counter(config); + register.registerMetric(metric); + } + return metric as Counter; +} + +export function createGaugeMetric( + config: GaugeConfiguration, +): Gauge { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Gauge(config); + register.registerMetric(metric); + } + return metric as Gauge; +} + +export function createSummaryMetric( + config: SummaryConfiguration, +): Summary { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Summary(config); + register.registerMetric(metric); + } + + return metric as Summary; +} + +export function createHistogramMetric( + config: HistogramConfiguration, +): Histogram { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Histogram(config); + register.registerMetric(metric); + } + + return metric as Histogram; +} diff --git a/yarn.lock b/yarn.lock index 1632a3a59e..774ad8e3ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6395,6 +6395,7 @@ __metadata: octokit: ^2.0.0 octokit-plugin-create-pull-request: ^3.10.0 p-limit: ^3.1.0 + prom-client: 14.0.1 supertest: ^6.1.3 uuid: ^8.2.0 vm2: ^3.9.11 @@ -32362,6 +32363,15 @@ __metadata: languageName: node linkType: hard +"prom-client@npm:14.0.1": + version: 14.0.1 + resolution: "prom-client@npm:14.0.1" + dependencies: + tdigest: ^0.1.1 + checksum: 864c19b7086eda8fae652385bc8b8aeb155f85922e58672d07a64918a603341e120e65e08f9d77ccab546518dc18930284da8743c2aac3c968f626d7063d6bba + languageName: node + linkType: hard + "prom-client@npm:^14.0.1": version: 14.1.0 resolution: "prom-client@npm:14.1.0" From 685b693fc0e9ce5d59f5883e1c4a7e43bd51d111 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Thu, 22 Sep 2022 13:32:53 -0600 Subject: [PATCH 003/221] move util folder up a dir Signed-off-by: Spencer Henry --- plugins/scaffolder-backend/src/{scaffolder => }/util/metrics.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/scaffolder-backend/src/{scaffolder => }/util/metrics.ts (100%) diff --git a/plugins/scaffolder-backend/src/scaffolder/util/metrics.ts b/plugins/scaffolder-backend/src/util/metrics.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/util/metrics.ts rename to plugins/scaffolder-backend/src/util/metrics.ts From ea14eb62a2f22a236ce99933255cce1b2c2b9e08 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Thu, 22 Sep 2022 15:37:45 -0600 Subject: [PATCH 004/221] redo changeset Signed-off-by: Spencer Henry --- .changeset/{silver-games-yell.md => flat-items-perform.md} | 1 - 1 file changed, 1 deletion(-) rename .changeset/{silver-games-yell.md => flat-items-perform.md} (97%) diff --git a/.changeset/silver-games-yell.md b/.changeset/flat-items-perform.md similarity index 97% rename from .changeset/silver-games-yell.md rename to .changeset/flat-items-perform.md index 3f9d3ec8bf..3b3dec9013 100644 --- a/.changeset/silver-games-yell.md +++ b/.changeset/flat-items-perform.md @@ -1,6 +1,5 @@ --- '@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-catalog-backend': patch --- Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: From a92cbc2668e3fbed48329b36b6942a106ea68033 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Tue, 27 Sep 2022 08:12:00 -0600 Subject: [PATCH 005/221] add carat to package.json Signed-off-by: Spencer Henry --- plugins/scaffolder-backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index b96be75294..613211b290 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -72,7 +72,7 @@ "octokit": "^2.0.0", "octokit-plugin-create-pull-request": "^3.10.0", "p-limit": "^3.1.0", - "prom-client": "14.0.1", + "prom-client": "^14.0.1", "uuid": "^8.2.0", "vm2": "^3.9.11", "winston": "^3.2.1", From 92672b15a5cd7e789b870ad43d2973d532871304 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Tue, 27 Sep 2022 08:16:47 -0600 Subject: [PATCH 006/221] rerun yarn Signed-off-by: Spencer Henry --- yarn.lock | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index 774ad8e3ce..a039eaa65c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6395,7 +6395,7 @@ __metadata: octokit: ^2.0.0 octokit-plugin-create-pull-request: ^3.10.0 p-limit: ^3.1.0 - prom-client: 14.0.1 + prom-client: ^14.0.1 supertest: ^6.1.3 uuid: ^8.2.0 vm2: ^3.9.11 @@ -32363,15 +32363,6 @@ __metadata: languageName: node linkType: hard -"prom-client@npm:14.0.1": - version: 14.0.1 - resolution: "prom-client@npm:14.0.1" - dependencies: - tdigest: ^0.1.1 - checksum: 864c19b7086eda8fae652385bc8b8aeb155f85922e58672d07a64918a603341e120e65e08f9d77ccab546518dc18930284da8743c2aac3c968f626d7063d6bba - languageName: node - linkType: hard - "prom-client@npm:^14.0.1": version: 14.1.0 resolution: "prom-client@npm:14.1.0" From c51c1bbc26cb9598977c86ff9a5188b7202e6b0a Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Tue, 27 Sep 2022 09:50:34 -0600 Subject: [PATCH 007/221] md cleanup, add to prom tutorial doc Signed-off-by: Spencer Henry --- .changeset/flat-items-perform.md | 10 +++++----- contrib/docs/tutorials/prometheus-metrics.md | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.changeset/flat-items-perform.md b/.changeset/flat-items-perform.md index 3b3dec9013..949814c064 100644 --- a/.changeset/flat-items-perform.md +++ b/.changeset/flat-items-perform.md @@ -11,35 +11,35 @@ Added a set of default Prometheus metrics around scaffolding. See below for a li - `template`: The entity ref of the scaffolded template - `user`: The entity ref of the user that invoked the template run -- scaffolder_task_error_count: a count that track how many task runs error out +- `scaffolder_task_error_count`: a count that track how many task runs error out Labels: - `template`: The entity ref of the scaffolded template - `user`: The entity ref of the user that invoked the template run -- scaffolder_task_duration: a histogram which tracks the duration of a task run +- `scaffolder_task_duration`: a histogram which tracks the duration of a task run Labels: - `template`: The entity ref of the scaffolded template - `result`: A boolean describing whether the task ran successfully -- scaffolder_step_success_count: a count that tracks each step run +- `scaffolder_step_success_count`: a count that tracks each step run Labels: - `template`: The entity ref of the scaffolded template - `step`: The name of the step that was run -- scaffolder_step_error_count: a count that tracks how many steps error out +- `scaffolder_step_error_count`: a count that tracks how many steps error out Labels: - `template`: The entity ref of the scaffolded template - `step`: The name of the step that was run -- scaffolder_step_duration: a histogram which tracks the duration of each step run +- `scaffolder_step_duration`: a histogram which tracks the duration of each step run Labels: diff --git a/contrib/docs/tutorials/prometheus-metrics.md b/contrib/docs/tutorials/prometheus-metrics.md index 5b7dc61df3..a46a2286cd 100644 --- a/contrib/docs/tutorials/prometheus-metrics.md +++ b/contrib/docs/tutorials/prometheus-metrics.md @@ -106,3 +106,9 @@ There are some custom metrics that have been added to Backstage will be output f - `catalog_processing_duration_seconds`: Time spent executing the full processing flow - `catalog_processors_duration_seconds`: Time spent executing catalog processors - `catalog_processing_queue_delay_seconds`: The amount of delay between being scheduled for processing, and the start of actually being processed +- `scaffolder_task_success_count`: Tracks successful task runs. +- `scaffolder_task_error_count`: a count that track how many task runs error out +- `scaffolder_task_duration`: a histogram which tracks the duration of a task run +- `scaffolder_step_success_count`: a count that tracks each step run +- `scaffolder_step_error_count`: a count that tracks how many steps error out +- `scaffolder_step_duration`: a histogram which tracks the duration of each step run From e806bb0eb6449a96c5641d8a13d8e22372c6510d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20K=C3=B6hler?= <9337156+axdotl@users.noreply.github.com> Date: Thu, 29 Sep 2022 15:49:29 +0200 Subject: [PATCH 008/221] Update to most recent mkdocs-techdocs-core version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Axel Köhler <9337156+axdotl@users.noreply.github.com> --- docs/features/techdocs/how-to-guides.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 4f2dc6c348..e185487d58 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -437,7 +437,7 @@ FROM python:3.8-alpine RUN apk update && apk --no-cache add gcc musl-dev openjdk11-jdk curl graphviz ttf-dejavu fontconfig -RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==1.0.1 +RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==1.1.7 RUN pip install mkdocs-kroki-plugin From 2c45c9d7038efff5ea3a2d20d4a9e8ddc5613046 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Thu, 29 Sep 2022 09:15:38 -0600 Subject: [PATCH 009/221] kick off E2E tests Signed-off-by: Spencer Henry From 8037c55ea90698690c6a400ae5def4aa75eb478f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 30 Sep 2022 16:13:51 +0200 Subject: [PATCH 010/221] initial backend docs Signed-off-by: Johan Haals --- docs/api/backend.md | 128 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index 0990343a01..fd3da4fbd8 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -4,4 +4,130 @@ title: Backend description: About Backend --- -## TODO +## Backend System + +**DISCLAMER: The new backend system is under active development and is not considered stable** + +### Overview + +The default backend provides several services out of the box which are available to all plugins but there might cases where you want to provide a completely new service in your installation. + +### Service Refs + +A serviceRef is a named reference to an interface which are later used to resolve the actual service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. +Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database. + +On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them. +ServiceRefs does contain a scope which is used to determine if the serviceFactory creating the service will create a new instance for each plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin and `root` scoped services will be created once per backend instance. + +#### Defining a ServiceRef + +In its simplest form the serviceRef can be defined like this referencing the type of the actual implementation. + +```ts +import { + createServiceFactory, + pluginMetadataServiceRef, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; +import { ExampleImpl } from './ExampleImpl'; + +export interface ExampleApi { + doSomething(): Promise; +} + +export const exampleServiceRef = createServiceRef({ + id: 'example', + scope: 'plugin', // can be 'root' or 'plugin' + + // The defaultFactory is optional to implement but it will be used if no other factory is provided to the backend. + // This is allows for the backend to provide a default implementation of the service without having to wire it beforehand. + defaultFactory: async service => + createServiceFactory({ + service, + deps: { + logger: loggerServiceRef, + plugin: pluginMetadataServiceRef, + }, + // Logger is available directly in the factory as it's a root scoped service and will be created once per backend instance. + async factory({ logger }) { + + // plugin is available as it's a plugin scoped service and will be created once per plugin. + return async ({ plugin }) => { + // This block will be executed once per plugin depending on this serviceRef + logger.info(`Creating example service for for plugin ${plugin.id}`); + return new ExampleImpl({logger}); + }; + }, + }), +}), +``` + +### Overriding services + +In this example replace the default log implementation with a custom one. + +```ts +import { + createServiceFactory, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; +export const gcpLoggerFactory = createServiceFactory({ + service: loggerServiceRef, + deps: {}, + async factory({}) { + return async ({}) => { + // This custom implementation conform with the type of the loggerServiceRef + return new GoogleCloudLogger(); + }; + }, +}); + +// packages/backend/src/index.ts +const backend = createBackend({ + services: [ + // supplies additional/replacement services to the backend + gcpLoggerFactory, + ], +}) +``` + +#### API Overview +`createBackend` +`createBackendPlugin` +`createBackendModule` +`createServiceRef` +`createExtensionPoint` +### Writing Plugins + +### Writing modules + +Some facts about modules + +- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin. +- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. +- A module is always initialized before the plugin it extends. + +A module depend on the extensionPoint exported by the plugins library package(eg `catalog-node`, `scaffolder-backend`) and does not directly declare a dependency on the plugin package itself. + + + +### Overwriting services + + +### Extension Points + +```ts +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +export interface ScaffolderActionsExtensionPoint { + addAction(action: ScaffolderAction): void; +} + +export const ScaffolderActionsExtensionPoint = + createExtensionPoint({ + id: 'scaffolder.actions', + }); +``` + +### Testing From 6a8f7e5a731097b1bfdf84f1d64e7675692e5a61 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 4 Oct 2022 16:18:27 +0200 Subject: [PATCH 011/221] chore: more text Signed-off-by: Johan Haals --- docs/api/backend.md | 122 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 16 deletions(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index fd3da4fbd8..5f04d38214 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -8,22 +8,32 @@ description: About Backend **DISCLAMER: The new backend system is under active development and is not considered stable** +This is an example of how you create, start and add existing plugins to your backend. + +```ts +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +// backend.add(catalogPlugin()); +await backend.start(); +``` + ### Overview -The default backend provides several services out of the box which are available to all plugins but there might cases where you want to provide a completely new service in your installation. +The default backend provides several _services_ out of the box which includes access to config, logging, scheduling and more. +Service are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. ### Service Refs -A serviceRef is a named reference to an interface which are later used to resolve the actual service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. +A serviceRef is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database. On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them. -ServiceRefs does contain a scope which is used to determine if the serviceFactory creating the service will create a new instance for each plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin and `root` scoped services will be created once per backend instance. +ServiceRefs contain a scope which is used to determine if the serviceFactory creating the service will create a new instance scoped per plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin/module and `root` scoped services will be created once per backend instance. #### Defining a ServiceRef -In its simplest form the serviceRef can be defined like this referencing the type of the actual implementation. - ```ts import { createServiceFactory, @@ -65,7 +75,7 @@ export const exampleServiceRef = createServiceRef({ ### Overriding services -In this example replace the default log implementation with a custom one. +In this example replace the default log implementation with a custom logger. ```ts import { @@ -92,15 +102,39 @@ const backend = createBackend({ }) ``` -#### API Overview -`createBackend` -`createBackendPlugin` -`createBackendModule` -`createServiceRef` -`createExtensionPoint` -### Writing Plugins +## Writing Plugins -### Writing modules +```ts +import { configServiceRef, createBackendPlugin } from '@backstage/backend-plugin-api'; + +// export type ExamplePluginOptions = { exampleOption: boolean }; +export const examplePlugin = createBackendPlugin({ + // unique id for the plugin + id: 'example', + // It's possible to provide options to the plugin + // register(env, options: ExamplePluginOptions) { + register(env) { + env.registerInit({ + deps: { + logger: loggerServiceRef, + }, + // logger is provided by the backend based on the dependency on loggerServiceRef above. + async init({ logger }) { + logger.info('Hello from example plugin'); + }, + }); + }, +}); +``` + +The plugin can then be installed to the backend using + +```ts +backend.add(examplePlugin()); +// Options can be passed to the plugin +// backend.add(examplePlugin({ exampleOption: true})); +``` +## Writing Modules Some facts about modules @@ -110,13 +144,35 @@ Some facts about modules A module depend on the extensionPoint exported by the plugins library package(eg `catalog-node`, `scaffolder-backend`) and does not directly declare a dependency on the plugin package itself. +Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint` +```ts +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { MyCustomProcessor } from './processor'; -### Overwriting services - +export const exampleCustomProcessorCatalogModule = createBackendModule({ + moduleId: 'exampleCustomProcessor', + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addProcessor(new MyCustomProcessor()); + }, + }); + }, +}); +``` ### Extension Points +Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. + +#### Defining an Extension Point + ```ts import { createExtensionPoint } from '@backstage/backend-plugin-api'; @@ -130,4 +186,38 @@ export const ScaffolderActionsExtensionPoint = }); ``` +#### Registering an Extension Point + +Extension points are registered by a plugin and extended by modules. + + ### Testing + +Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. + +```ts +import { startTestBackend } from '@backstage/backend-test-utils'; + +describe('Example', () => { + it('should do something', async () => { + await startTestBackend({ + // mock services can be provided to the backend + services: [someServiceFactory], + // plugins and modules for testing + features: [testModule()], + }); + // assertions + }); +}); +``` + + +## Package structure + +The package relationship between plugins, modules and extension are illustrated in the following diagram. + +Taken with an artificial foobar backend plugin. + +- `plugin-foobar-backend` houses the plugin and registers the extension points into the backend system. +- `plugin-foobar-common` houses the shared types including the Extension Point registered by the backend. +- `plugin-foobar-XYZ-module` houses the modules that extend the foobar backend with extension points imported from `plugin-foobar-common` From 0cf317ee370471c3167596e85d1d251980533731 Mon Sep 17 00:00:00 2001 From: Leon Date: Thu, 6 Oct 2022 11:47:12 +0200 Subject: [PATCH 012/221] Add a delay to the fact retrievers to prevent errors Signed-off-by: Leon --- .../src/service/fact/FactRetrieverEngine.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts index 2d738b7de8..a316b984ed 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts @@ -126,6 +126,9 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { frequency: { cron: cronExpression }, fn: this.createFactRetrieverHandler(factRetriever, lifecycle), timeout: timeLimit, + // We add a delay in order to prevent errors due to the + // fact that the backend is not yet online in a cold-start scenario + initialDelay: Duration.fromObject({ seconds: 5 }), }); newRegs.push(factRetriever.id); } catch (e) { From 06cf8f1cf22590c370e179d4c6f142f3a2b31809 Mon Sep 17 00:00:00 2001 From: Leon Date: Thu, 6 Oct 2022 12:07:24 +0200 Subject: [PATCH 013/221] Changeset Signed-off-by: Leon --- .changeset/tame-ads-appear.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tame-ads-appear.md diff --git a/.changeset/tame-ads-appear.md b/.changeset/tame-ads-appear.md new file mode 100644 index 0000000000..00eb4a7dcd --- /dev/null +++ b/.changeset/tame-ads-appear.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights-backend': minor +--- + +Add a default delay to the fact retrievers to prevent cold-start errors From 7a319ebce0a023d3f7e04e5bbd58322c4e85f946 Mon Sep 17 00:00:00 2001 From: marcofaggian Date: Fri, 7 Oct 2022 13:32:09 +0200 Subject: [PATCH 014/221] fix(plugins/tech-radar): list rerenders on hover Signed-off-by: marcofaggian --- .../tech-radar/src/components/Radar/Radar.tsx | 34 ++- .../tech-radar/src/components/Radar/utils.ts | 76 +++--- .../RadarLegend/RadarLegend.test.tsx | 11 +- .../components/RadarLegend/RadarLegend.tsx | 224 ++---------------- .../RadarLegend/RadarLegendLink.tsx | 77 ++++++ .../RadarLegend/RadarLegendQuadrant.tsx | 69 ++++++ .../RadarLegend/RadarLegendRing.tsx | 67 ++++++ .../src/components/RadarLegend/types.ts | 29 +++ .../src/components/RadarLegend/utils.ts | 63 +++++ 9 files changed, 389 insertions(+), 261 deletions(-) create mode 100644 plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx create mode 100644 plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx create mode 100644 plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx create mode 100644 plugins/tech-radar/src/components/RadarLegend/types.ts create mode 100644 plugins/tech-radar/src/components/RadarLegend/utils.ts diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index e6ae6d385d..b2d4ed22e2 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import React, { useState, useRef } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; +import type { Entry, Quadrant, Ring } from '../../utils/types'; import RadarPlot from '../RadarPlot'; -import type { Ring, Quadrant, Entry } from '../../utils/types'; -import { adjustQuadrants, adjustRings, adjustEntries } from './utils'; +import { adjustEntries, adjustQuadrants, adjustRings } from './utils'; export type Props = { width: number; @@ -28,17 +28,27 @@ export type Props = { svgProps?: object; }; -const Radar = (props: Props): JSX.Element => { - const { width, height, quadrants, rings, entries } = props; +const Radar = ({ width, height, quadrants, rings, entries, ...props }: Props): JSX.Element => { + const [adjustedQuadrants, setAdjustedQuadrants] = useState>(quadrants); + const [adjustedRings, setAdjustedRings] = useState>(rings); + const [adjustedEntries, setAdjustedEntries] = useState>(entries); + const radius = Math.min(width, height) / 2; const [activeEntry, setActiveEntry] = useState(); const node = useRef(null); - // TODO(dflemstr): most of this can be heavily memoized if performance becomes a problem - adjustQuadrants(quadrants, radius, width, height); - adjustRings(rings, radius); - adjustEntries(entries, quadrants, rings, radius, activeEntry); + useEffect(() => { + setAdjustedQuadrants(adjustQuadrants(quadrants, radius, width, height)); + }, [quadrants, radius, width, height]) + + useEffect(() => { + setAdjustedRings(adjustRings(rings, radius)); + }, [radius, rings]) + + useEffect(() => { + setAdjustedEntries(adjustEntries(entries, adjustedQuadrants, adjustedRings, radius, activeEntry)); + }, [entries, adjustedQuadrants, adjustedRings, radius, activeEntry]) return ( @@ -46,9 +56,9 @@ const Radar = (props: Props): JSX.Element => { width={width} height={height} radius={radius} - entries={entries} - quadrants={quadrants} - rings={rings} + entries={adjustedEntries} + quadrants={adjustedQuadrants} + rings={adjustedRings} activeEntry={activeEntry} onEntryMouseEnter={entry => setActiveEntry(entry)} onEntryMouseLeave={() => setActiveEntry(undefined)} diff --git a/plugins/tech-radar/src/components/Radar/utils.ts b/plugins/tech-radar/src/components/Radar/utils.ts index e41b0a27ab..19db2a0cd1 100644 --- a/plugins/tech-radar/src/components/Radar/utils.ts +++ b/plugins/tech-radar/src/components/Radar/utils.ts @@ -17,7 +17,7 @@ import color from 'color'; import { forceCollide, forceSimulation } from 'd3-force'; import Segment from '../../utils/segment'; -import type { Ring, Quadrant, Entry } from '../../utils/types'; +import type { Entry, Quadrant, Ring } from '../../utils/types'; export const adjustQuadrants = ( quadrants: Quadrant[], @@ -81,30 +81,33 @@ export const adjustQuadrants = ( }, ]; - quadrants.forEach((quadrant, index) => { + return quadrants.slice().map((quadrant, index) => { const legendParam = legendParams[index % 4]; - quadrant.index = index; - quadrant.radialMin = (index * Math.PI) / 2; - quadrant.radialMax = ((index + 1) * Math.PI) / 2; - quadrant.offsetX = index % 4 === 0 || index % 4 === 3 ? 1 : -1; - quadrant.offsetY = index % 4 === 0 || index % 4 === 1 ? 1 : -1; - quadrant.legendX = legendParam.x; - quadrant.legendY = legendParam.y; - quadrant.legendWidth = legendParam.width; - quadrant.legendHeight = legendParam.height; + return ({ + ...quadrant, + index, + radialMin: (index * Math.PI) / 2, + radialMax: ((index + 1) * Math.PI) / 2, + offsetX: index % 4 === 0 || index % 4 === 3 ? 1 : -1, + offsetY: index % 4 === 0 || index % 4 === 1 ? 1 : -1, + legendX: legendParam.x, + legendY: legendParam.y, + legendWidth: legendParam.width, + legendHeight: legendParam.height + }) }); }; export const adjustEntries = ( - entries: Entry[], + _entries: Entry[], quadrants: Quadrant[], rings: Ring[], radius: number, activeEntry?: Entry, ) => { let seed = 42; - entries.forEach((entry, index) => { + const entries = _entries.map((entry, index) => { const quadrant = quadrants.find(q => { const match = typeof entry.quadrant === 'object' ? entry.quadrant.id : entry.quadrant; @@ -123,18 +126,21 @@ export const adjustEntries = ( if (!ring) { throw new Error(`Unknown ring ${entry.ring} for entry ${entry.id}!`); } + const segment = new Segment(quadrant, ring, radius, () => seed++) + const point = segment?.random(); - entry.index = index; - entry.quadrant = quadrant; - entry.ring = ring; - entry.segment = new Segment(quadrant, ring, radius, () => seed++); - const point = entry.segment.random(); - entry.x = point.x; - entry.y = point.y; - entry.active = activeEntry ? entry.id === activeEntry.id : false; - entry.color = entry.active - ? entry.ring.color - : color(entry.ring.color).desaturate(0.5).lighten(0.1).string(); + return ({ + ...entry, + index: index, + quadrant: quadrant, + ring: ring, + segment, + x: point.x, + y: point.y, + color: activeEntry && entry.id === activeEntry?.id + ? entry.ring.color + : color(entry.ring.color).desaturate(0.5).lighten(0.1).string(), + }) }); const simulation = forceSimulation() @@ -145,9 +151,9 @@ export const adjustEntries = ( for ( let i = 0, - n = Math.ceil( - Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()), - ); + n = Math.ceil( + Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()), + ); i < n; ++i ) { @@ -160,13 +166,13 @@ export const adjustEntries = ( } } } + + return entries }; -export const adjustRings = (rings: Ring[], radius: number) => { - rings.forEach((ring, index) => { - ring.index = index; - ring.outerRadius = ((index + 2) / (rings.length + 1)) * radius; - ring.innerRadius = - ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius; - }); -}; +export const adjustRings = (rings: Ring[], radius: number) => rings.slice().map((ring, index) => ({ + ...ring, + index, + outerRadius: ((index + 2) / (rings.length + 1)) * radius, + innerRadius: ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius +})) diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx index 1f74efe650..e0b5f09a63 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx @@ -14,15 +14,16 @@ * limitations under the License. */ -import React from 'react'; -import { render } from '@testing-library/react'; -import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; +import { ThemeProvider } from '@material-ui/core'; +import { render } from '@testing-library/react'; +import React from 'react'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarLegend, { Props } from './RadarLegend'; +import RadarLegend from './RadarLegend'; +import { RadarLegendProps } from './types'; -const minProps: Props = { +const minProps: RadarLegendProps = { quadrants: [{ id: 'languages', name: 'Languages' }], rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], entries: [ diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index a398113b98..9a4b861b56 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2022 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,21 +15,10 @@ */ import { makeStyles, Theme } from '@material-ui/core'; import React from 'react'; -import { WithLink } from '../../utils/components'; -import type { Entry, Quadrant, Ring } from '../../utils/types'; -import { RadarDescription } from '../RadarDescription'; +import { RadarLegendQuadrant } from './RadarLegendQuadrant'; +import { RadarLegendProps } from './types'; +import { setupSegments } from './utils'; -type Segments = { - [k: number]: { [k: number]: Entry[] }; -}; - -export type Props = { - quadrants: Quadrant[]; - rings: Ring[]; - entries: Entry[]; - onEntryMouseEnter?: (entry: Entry) => void; - onEntryMouseLeave?: (entry: Entry) => void; -}; const useStyles = makeStyles(theme => ({ quadrant: { @@ -85,209 +74,26 @@ const useStyles = makeStyles(theme => ({ }, })); -const RadarLegend = (props: Props): JSX.Element => { +const RadarLegend = ({ + quadrants, + rings, + entries, + onEntryMouseEnter, + onEntryMouseLeave, + ...props +}: RadarLegendProps +): JSX.Element => { const classes = useStyles(props); - const getSegment = ( - segmented: Segments, - quadrant: Quadrant, - ring: Ring, - ringOffset = 0, - ) => { - const quadrantIndex = quadrant.index; - const ringIndex = ring.index; - const segmentedData = - quadrantIndex === undefined ? {} : segmented[quadrantIndex] || {}; - return ringIndex === undefined - ? [] - : segmentedData[ringIndex + ringOffset] || []; - }; - - type RadarLegendRingProps = { - ring: Ring; - entries: Entry[]; - onEntryMouseEnter?: Props['onEntryMouseEnter']; - onEntryMouseLeave?: Props['onEntryMouseEnter']; - }; - - type RadarLegendLinkProps = { - url?: string; - description?: string; - title?: string; - }; - - const RadarLegendLink = ({ - url, - description, - title, - }: RadarLegendLinkProps) => { - const [open, setOpen] = React.useState(false); - - const handleClickOpen = () => { - setOpen(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - const toggle = () => { - setOpen(!open); - }; - - if (description) { - return ( - <> - - {title} - - {open && ( - - )} - - ); - } - return ( - - {title} - - ); - }; - - const RadarLegendRing = ({ - ring, - entries, - onEntryMouseEnter, - onEntryMouseLeave, - }: RadarLegendRingProps) => { - return ( -
-

{ring.name}

- {entries.length === 0 ? ( -

(empty)

- ) : ( -
    - {entries.map(entry => ( -
  1. onEntryMouseEnter(entry)) - } - onMouseLeave={ - onEntryMouseLeave && (() => onEntryMouseLeave(entry)) - } - > - -
  2. - ))} -
- )} -
- ); - }; - - type RadarLegendQuadrantProps = { - segments: Segments; - quadrant: Quadrant; - rings: Ring[]; - onEntryMouseEnter: Props['onEntryMouseEnter']; - onEntryMouseLeave: Props['onEntryMouseLeave']; - }; - - const RadarLegendQuadrant = ({ - segments, - quadrant, - rings, - onEntryMouseEnter, - onEntryMouseLeave, - }: RadarLegendQuadrantProps) => { - return ( - -
-

{quadrant.name}

-
- {rings.map(ring => ( - - ))} -
-
-
- ); - }; - - const setupSegments = (entries: Entry[]) => { - const segments: Segments = {}; - - for (const entry of entries) { - const quadrantIndex = entry.quadrant.index; - const ringIndex = entry.ring.index; - let quadrantData: { [k: number]: Entry[] } = {}; - if (quadrantIndex !== undefined) { - if (segments[quadrantIndex] === undefined) { - segments[quadrantIndex] = {}; - } - - quadrantData = segments[quadrantIndex]; - } - - let ringData = []; - if (ringIndex !== undefined) { - if (quadrantData[ringIndex] === undefined) { - quadrantData[ringIndex] = []; - } - - ringData = quadrantData[ringIndex]; - } - - ringData.push(entry); - } - - return segments; - }; - - const { quadrants, rings, entries, onEntryMouseEnter, onEntryMouseLeave } = - props; - - const segments: Segments = setupSegments(entries); - return ( {quadrants.map(quadrant => ( diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx new file mode 100644 index 0000000000..f6626aa0d2 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClassNameMap } from "@material-ui/core/styles/withStyles"; +import React from "react"; +import { WithLink } from "../../utils/components"; +import { RadarDescription } from "../RadarDescription"; + +type RadarLegendLinkProps = { + url?: string; + description?: string; + title?: string; + classes: ClassNameMap +}; + +export const RadarLegendLink = ({ + url, + description, + title, + classes +}: RadarLegendLinkProps) => { + const [open, setOpen] = React.useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const toggle = () => { + setOpen(!open); + }; + + if (description) { + return ( + <> + + {title} + + {open && ( + + )} + + ); + } + return ( + + {title} + + ); +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx new file mode 100644 index 0000000000..af79230dfc --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx @@ -0,0 +1,69 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ClassNameMap } from "@material-ui/core/styles/withStyles"; +import React from "react"; +import { Quadrant, Ring } from "../../utils/types"; +import { RadarLegendRing } from "./RadarLegendRing"; +import { RadarLegendProps, Segments } from "./types"; +import { getSegment } from "./utils"; + + + +type RadarLegendQuadrantProps = { + segments: Segments; + quadrant: Quadrant; + rings: Ring[]; + classes: ClassNameMap, + onEntryMouseEnter: RadarLegendProps['onEntryMouseEnter']; + onEntryMouseLeave: RadarLegendProps['onEntryMouseLeave']; +}; + +export const RadarLegendQuadrant = ({ + segments, + quadrant, + rings, + classes, + onEntryMouseEnter, + onEntryMouseLeave, +}: RadarLegendQuadrantProps) => { + return ( + +
+

{quadrant.name}

+
+ {rings.map(ring => ( + + ))} +
+
+
+ ); +}; \ No newline at end of file diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx new file mode 100644 index 0000000000..c9070ba0dd --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ClassNameMap } from "@material-ui/core/styles/withStyles"; +import React from "react"; +import { Entry, Ring } from "../../utils/types"; +import { RadarLegendLink } from "./RadarLegendLink"; +import { RadarLegendProps } from "./types"; + +type RadarLegendRingProps = { + ring: Ring; + entries: Entry[]; + classes: ClassNameMap; + onEntryMouseEnter?: RadarLegendProps['onEntryMouseEnter']; + onEntryMouseLeave?: RadarLegendProps['onEntryMouseEnter']; +}; + +export const RadarLegendRing = ({ + ring, + entries, + classes, + onEntryMouseEnter, + onEntryMouseLeave, +}: RadarLegendRingProps) => { + return ( +
+

{ring.name}

+ {entries.length === 0 ? ( +

(empty)

+ ) : ( +
    + {entries.map(entry => ( +
  1. onEntryMouseEnter(entry)) + } + onMouseLeave={ + onEntryMouseLeave && (() => onEntryMouseLeave(entry)) + } + > + +
  2. + ))} +
+ )} +
+ ); +}; \ No newline at end of file diff --git a/plugins/tech-radar/src/components/RadarLegend/types.ts b/plugins/tech-radar/src/components/RadarLegend/types.ts new file mode 100644 index 0000000000..2165646afc --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entry, Quadrant, Ring } from "../../utils/types"; + +export type Segments = { + [k: number]: { [k: number]: Entry[] }; +}; + +export type RadarLegendProps = { + quadrants: Quadrant[]; + rings: Ring[]; + entries: Entry[]; + onEntryMouseEnter?: (entry: Entry) => void; + onEntryMouseLeave?: (entry: Entry) => void; +}; \ No newline at end of file diff --git a/plugins/tech-radar/src/components/RadarLegend/utils.ts b/plugins/tech-radar/src/components/RadarLegend/utils.ts new file mode 100644 index 0000000000..036388ece9 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/utils.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entry, Quadrant, Ring } from "../../utils/types"; +import { Segments } from "./types"; + +export const setupSegments = (entries: Entry[]) => { + const segments: Segments = {}; + + for (const entry of entries) { + const quadrantIndex = entry.quadrant.index; + const ringIndex = entry.ring.index; + let quadrantData: { [k: number]: Entry[] } = {}; + if (quadrantIndex !== undefined) { + if (segments[quadrantIndex] === undefined) { + segments[quadrantIndex] = {}; + } + + quadrantData = segments[quadrantIndex]; + } + + let ringData = []; + if (ringIndex !== undefined) { + if (quadrantData[ringIndex] === undefined) { + quadrantData[ringIndex] = []; + } + + ringData = quadrantData[ringIndex]; + } + + ringData.push(entry); + } + + return segments; +}; + +export const getSegment = ( + segmented: Segments, + quadrant: Quadrant, + ring: Ring, + ringOffset = 0, +) => { + const quadrantIndex = quadrant.index; + const ringIndex = ring.index; + const segmentedData = + quadrantIndex === undefined ? {} : segmented[quadrantIndex] || {}; + return ringIndex === undefined + ? [] + : segmentedData[ringIndex + ringOffset] || []; +}; \ No newline at end of file From 1f888af5f6fb7ea69ae8e9655791131d38be46e1 Mon Sep 17 00:00:00 2001 From: marcofaggian Date: Fri, 7 Oct 2022 13:41:13 +0200 Subject: [PATCH 015/221] chore(plugins/tech-radar): changeset Signed-off-by: marcofaggian --- .changeset/little-bikes-eat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/little-bikes-eat.md diff --git a/.changeset/little-bikes-eat.md b/.changeset/little-bikes-eat.md new file mode 100644 index 0000000000..0992198146 --- /dev/null +++ b/.changeset/little-bikes-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Fixed bug in Tech Radar where, on hover, the tech list quadrant would rerender and scroll top From c1e0355d0dea234e10814e453345e2ffef8a04fb Mon Sep 17 00:00:00 2001 From: marcofaggian Date: Fri, 7 Oct 2022 14:17:54 +0200 Subject: [PATCH 016/221] chore(plugins/tech-radar): prettier Signed-off-by: marcofaggian --- .../tech-radar/src/components/Radar/Radar.tsx | 28 ++++++++++--- .../tech-radar/src/components/Radar/utils.ts | 40 ++++++++++--------- .../components/RadarLegend/RadarLegend.tsx | 4 +- .../RadarLegend/RadarLegendLink.tsx | 12 +++--- .../RadarLegend/RadarLegendQuadrant.tsx | 18 ++++----- .../RadarLegend/RadarLegendRing.tsx | 12 +++--- .../src/components/RadarLegend/types.ts | 4 +- .../src/components/RadarLegend/utils.ts | 6 +-- 8 files changed, 69 insertions(+), 55 deletions(-) diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index b2d4ed22e2..15fcb697c2 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -28,8 +28,16 @@ export type Props = { svgProps?: object; }; -const Radar = ({ width, height, quadrants, rings, entries, ...props }: Props): JSX.Element => { - const [adjustedQuadrants, setAdjustedQuadrants] = useState>(quadrants); +const Radar = ({ + width, + height, + quadrants, + rings, + entries, + ...props +}: Props): JSX.Element => { + const [adjustedQuadrants, setAdjustedQuadrants] = + useState>(quadrants); const [adjustedRings, setAdjustedRings] = useState>(rings); const [adjustedEntries, setAdjustedEntries] = useState>(entries); @@ -40,15 +48,23 @@ const Radar = ({ width, height, quadrants, rings, entries, ...props }: Props): J useEffect(() => { setAdjustedQuadrants(adjustQuadrants(quadrants, radius, width, height)); - }, [quadrants, radius, width, height]) + }, [quadrants, radius, width, height]); useEffect(() => { setAdjustedRings(adjustRings(rings, radius)); - }, [radius, rings]) + }, [radius, rings]); useEffect(() => { - setAdjustedEntries(adjustEntries(entries, adjustedQuadrants, adjustedRings, radius, activeEntry)); - }, [entries, adjustedQuadrants, adjustedRings, radius, activeEntry]) + setAdjustedEntries( + adjustEntries( + entries, + adjustedQuadrants, + adjustedRings, + radius, + activeEntry, + ), + ); + }, [entries, adjustedQuadrants, adjustedRings, radius, activeEntry]); return ( diff --git a/plugins/tech-radar/src/components/Radar/utils.ts b/plugins/tech-radar/src/components/Radar/utils.ts index 19db2a0cd1..5b458068e6 100644 --- a/plugins/tech-radar/src/components/Radar/utils.ts +++ b/plugins/tech-radar/src/components/Radar/utils.ts @@ -84,7 +84,7 @@ export const adjustQuadrants = ( return quadrants.slice().map((quadrant, index) => { const legendParam = legendParams[index % 4]; - return ({ + return { ...quadrant, index, radialMin: (index * Math.PI) / 2, @@ -94,8 +94,8 @@ export const adjustQuadrants = ( legendX: legendParam.x, legendY: legendParam.y, legendWidth: legendParam.width, - legendHeight: legendParam.height - }) + legendHeight: legendParam.height, + }; }); }; @@ -126,10 +126,10 @@ export const adjustEntries = ( if (!ring) { throw new Error(`Unknown ring ${entry.ring} for entry ${entry.id}!`); } - const segment = new Segment(quadrant, ring, radius, () => seed++) + const segment = new Segment(quadrant, ring, radius, () => seed++); const point = segment?.random(); - return ({ + return { ...entry, index: index, quadrant: quadrant, @@ -137,10 +137,11 @@ export const adjustEntries = ( segment, x: point.x, y: point.y, - color: activeEntry && entry.id === activeEntry?.id - ? entry.ring.color - : color(entry.ring.color).desaturate(0.5).lighten(0.1).string(), - }) + color: + activeEntry && entry.id === activeEntry?.id + ? entry.ring.color + : color(entry.ring.color).desaturate(0.5).lighten(0.1).string(), + }; }); const simulation = forceSimulation() @@ -151,9 +152,9 @@ export const adjustEntries = ( for ( let i = 0, - n = Math.ceil( - Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()), - ); + n = Math.ceil( + Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()), + ); i < n; ++i ) { @@ -167,12 +168,13 @@ export const adjustEntries = ( } } - return entries + return entries; }; -export const adjustRings = (rings: Ring[], radius: number) => rings.slice().map((ring, index) => ({ - ...ring, - index, - outerRadius: ((index + 2) / (rings.length + 1)) * radius, - innerRadius: ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius -})) +export const adjustRings = (rings: Ring[], radius: number) => + rings.slice().map((ring, index) => ({ + ...ring, + index, + outerRadius: ((index + 2) / (rings.length + 1)) * radius, + innerRadius: ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius, + })); diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index 9a4b861b56..6177041c70 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -19,7 +19,6 @@ import { RadarLegendQuadrant } from './RadarLegendQuadrant'; import { RadarLegendProps } from './types'; import { setupSegments } from './utils'; - const useStyles = makeStyles(theme => ({ quadrant: { height: '100%', @@ -81,8 +80,7 @@ const RadarLegend = ({ onEntryMouseEnter, onEntryMouseLeave, ...props -}: RadarLegendProps -): JSX.Element => { +}: RadarLegendProps): JSX.Element => { const classes = useStyles(props); return ( diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx index f6626aa0d2..3ed1b7574a 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClassNameMap } from "@material-ui/core/styles/withStyles"; -import React from "react"; -import { WithLink } from "../../utils/components"; -import { RadarDescription } from "../RadarDescription"; +import { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import React from 'react'; +import { WithLink } from '../../utils/components'; +import { RadarDescription } from '../RadarDescription'; type RadarLegendLinkProps = { url?: string; description?: string; title?: string; - classes: ClassNameMap + classes: ClassNameMap; }; export const RadarLegendLink = ({ url, description, title, - classes + classes, }: RadarLegendLinkProps) => { const [open, setOpen] = React.useState(false); diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx index af79230dfc..60a5870348 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx @@ -14,20 +14,18 @@ * limitations under the License. */ -import { ClassNameMap } from "@material-ui/core/styles/withStyles"; -import React from "react"; -import { Quadrant, Ring } from "../../utils/types"; -import { RadarLegendRing } from "./RadarLegendRing"; -import { RadarLegendProps, Segments } from "./types"; -import { getSegment } from "./utils"; - - +import { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import React from 'react'; +import { Quadrant, Ring } from '../../utils/types'; +import { RadarLegendRing } from './RadarLegendRing'; +import { RadarLegendProps, Segments } from './types'; +import { getSegment } from './utils'; type RadarLegendQuadrantProps = { segments: Segments; quadrant: Quadrant; rings: Ring[]; - classes: ClassNameMap, + classes: ClassNameMap; onEntryMouseEnter: RadarLegendProps['onEntryMouseEnter']; onEntryMouseLeave: RadarLegendProps['onEntryMouseLeave']; }; @@ -66,4 +64,4 @@ export const RadarLegendQuadrant = ({ ); -}; \ No newline at end of file +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx index c9070ba0dd..f54e4ab3b3 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClassNameMap } from "@material-ui/core/styles/withStyles"; -import React from "react"; -import { Entry, Ring } from "../../utils/types"; -import { RadarLegendLink } from "./RadarLegendLink"; -import { RadarLegendProps } from "./types"; +import { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import React from 'react'; +import { Entry, Ring } from '../../utils/types'; +import { RadarLegendLink } from './RadarLegendLink'; +import { RadarLegendProps } from './types'; type RadarLegendRingProps = { ring: Ring; @@ -64,4 +64,4 @@ export const RadarLegendRing = ({ )} ); -}; \ No newline at end of file +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/types.ts b/plugins/tech-radar/src/components/RadarLegend/types.ts index 2165646afc..cdac1bfe9d 100644 --- a/plugins/tech-radar/src/components/RadarLegend/types.ts +++ b/plugins/tech-radar/src/components/RadarLegend/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entry, Quadrant, Ring } from "../../utils/types"; +import { Entry, Quadrant, Ring } from '../../utils/types'; export type Segments = { [k: number]: { [k: number]: Entry[] }; @@ -26,4 +26,4 @@ export type RadarLegendProps = { entries: Entry[]; onEntryMouseEnter?: (entry: Entry) => void; onEntryMouseLeave?: (entry: Entry) => void; -}; \ No newline at end of file +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/utils.ts b/plugins/tech-radar/src/components/RadarLegend/utils.ts index 036388ece9..0c2d7eb8f0 100644 --- a/plugins/tech-radar/src/components/RadarLegend/utils.ts +++ b/plugins/tech-radar/src/components/RadarLegend/utils.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Entry, Quadrant, Ring } from "../../utils/types"; -import { Segments } from "./types"; +import { Entry, Quadrant, Ring } from '../../utils/types'; +import { Segments } from './types'; export const setupSegments = (entries: Entry[]) => { const segments: Segments = {}; @@ -60,4 +60,4 @@ export const getSegment = ( return ringIndex === undefined ? [] : segmentedData[ringIndex + ringOffset] || []; -}; \ No newline at end of file +}; From 8d1a5e08ca5bbb7cb190619168f0643f844bd73e Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 7 Oct 2022 15:23:48 +0200 Subject: [PATCH 017/221] feat(catalog/msgraph): Add option to configure schedule via `app-config.yaml` Relates-to: PR #13859 Relates-to: PR #14034 Signed-off-by: Patrick Jungermann --- .changeset/dirty-birds-burn.md | 8 + .../catalog-backend-module-msgraph/README.md | 26 +- .../api-report.md | 6 +- .../config.d.ts | 22 +- .../package.json | 1 + .../src/microsoftGraph/config.test.ts | 13 + .../src/microsoftGraph/config.ts | 14 + .../MicrosoftGraphOrgEntityProvider.test.ts | 265 ++++++++++++++---- .../MicrosoftGraphOrgEntityProvider.ts | 43 ++- yarn.lock | 1 + 10 files changed, 312 insertions(+), 87 deletions(-) create mode 100644 .changeset/dirty-birds-burn.md diff --git a/.changeset/dirty-birds-burn.md b/.changeset/dirty-birds-burn.md new file mode 100644 index 0000000000..98f2c5967d --- /dev/null +++ b/.changeset/dirty-birds-burn.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +`MicrosoftGraphOrgEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-msgraph#readme diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index 9bf979f6a6..6f8c95b667 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -85,6 +85,13 @@ catalog: # in order to add extra information to your groups that can be used on your custom groupTransformers # See https://docs.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0 select: ['id', 'displayName', 'description'] + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { hours: 1 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 50 } + # supports ISO duration, "human duration" as used in code + initialDelay: { seconds: 15}, ``` `user.filter` and `userGroupMember.filter` are mutually exclusive, only one can be provided. If both are provided, an error will be thrown. @@ -116,13 +123,21 @@ yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-msgraph + builder.addEntityProvider( + MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ scheduler, ++ }), ++ ); +``` + +Instead of configuring the refresh schedule inside the config (per provider instance), +you can define it in code (for all of them): + +```diff +- scheduler, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { hours: 1 }, + timeout: { minutes: 50 }, -+ initialDelay: { seconds: 15} ++ initialDelay: { seconds: 15}, + }), -+ }), -+ ); ``` ## Customize the Processor or Entity Provider @@ -161,10 +176,7 @@ export async function myGroupTransformer( builder.addEntityProvider( MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 5 }, - timeout: { minutes: 3 }, - }), + scheduler, + groupTransformer: myGroupTransformer, }), ); diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 8b77ddf7c3..f5a77ceb51 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -12,8 +12,10 @@ import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; import { TaskRunner } from '@backstage/backend-tasks'; +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { TokenCredential } from '@azure/identity'; import { UserEntity } from '@backstage/catalog-model'; @@ -147,7 +149,8 @@ export type MicrosoftGraphOrgEntityProviderOptions = | MicrosoftGraphOrgEntityProviderLegacyOptions | { logger: Logger; - schedule: 'manual' | TaskRunner; + schedule?: 'manual' | TaskRunner; + scheduler?: PluginTaskScheduler; userTransformer?: UserTransformer | Record; groupTransformer?: GroupTransformer | Record; organizationTransformer?: @@ -202,6 +205,7 @@ export type MicrosoftGraphProviderConfig = { groupSearch?: string; groupSelect?: string[]; queryMode?: 'basic' | 'advanced'; + schedule?: TaskScheduleDefinition; }; // @public diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index 9fe1cb81c9..904ce1f646 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -14,14 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { - /** - * Configuration options for the catalog plugin. - */ catalog?: { - /** - * List of processor-specific options and attributes - */ processors?: { /** * MicrosoftGraphOrgReaderProcessor configuration @@ -109,9 +105,7 @@ export interface Config { }>; }; }; - /** - * List of provider-specific options and attributes - */ + providers?: { /** * MicrosoftGraphOrgEntityProvider configuration. @@ -209,6 +203,11 @@ export interface Config { */ search?: string; }; + + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -292,6 +291,11 @@ export interface Config { */ search?: string; }; + + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index d3ac35f743..0a5aed29f9 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -51,6 +51,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", + "luxon": "^3.0.0", "msw": "^0.47.0" }, "files": [ diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts index 3331e3609d..68c174665d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readMicrosoftGraphConfig, readProviderConfigs } from './config'; describe('readMicrosoftGraphConfig', () => { @@ -172,6 +173,12 @@ describe('readProviderConfigs', () => { filter: 'securityEnabled eq false', select: ['id', 'displayName', 'description'], }, + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, }, }, }, @@ -192,6 +199,12 @@ describe('readProviderConfigs', () => { groupExpand: 'member', groupSelect: ['id', 'displayName', 'description'], groupFilter: 'securityEnabled eq false', + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, }, ]; expect(actual).toEqual(expected); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index b8565e0d72..506951ff10 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { trimEnd } from 'lodash'; @@ -121,6 +125,11 @@ export type MicrosoftGraphProviderConfig = { * Some features like `$expand` are not available for advanced queries, though. */ queryMode?: 'basic' | 'advanced'; + + /** + * Schedule configuration for refresh tasks. + */ + schedule?: TaskScheduleDefinition; }; /** @@ -296,6 +305,10 @@ export function readProviderConfig( throw new Error(`clientId must be provided when clientSecret is defined.`); } + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, target, @@ -312,5 +325,6 @@ export function readProviderConfig( queryMode, userGroupMemberFilter, userGroupMemberSearch, + schedule, }; } diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index ff68235c4c..8af327cf27 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -14,6 +14,11 @@ * limitations under the License. */ import { getVoidLogger } from '@backstage/backend-common'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { ANNOTATION_LOCATION, @@ -43,10 +48,21 @@ const readMicrosoftGraphOrgMocked = readMicrosoftGraphOrg as jest.Mock< Promise<{ users: UserEntity[]; groups: GroupEntity[] }> >; -describe('MicrosoftGraphOrgEntityProvider', () => { - afterEach(() => jest.resetAllMocks()); +class PersistingTaskRunner implements TaskRunner { + private tasks: TaskInvocationDefinition[] = []; - it('should apply mutation', async () => { + getTasks() { + return this.tasks; + } + + run(task: TaskInvocationDefinition): Promise { + this.tasks.push(task); + return Promise.resolve(undefined); + } +} + +describe('MicrosoftGraphOrgEntityProvider', () => { + beforeEach(() => { jest .spyOn(MicrosoftGraphClient, 'create') .mockReturnValue({} as unknown as MicrosoftGraphClient); @@ -78,8 +94,65 @@ describe('MicrosoftGraphOrgEntityProvider', () => { }, ], }); + }); - const config = { + afterEach(() => jest.resetAllMocks()); + + const logger = getVoidLogger(); + const taskRunner = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => taskRunner, + } as unknown as PluginTaskScheduler; + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const expectedMutation = { + entities: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'msgraph:customProviderId/u1', + 'backstage.io/managed-by-origin-location': + 'msgraph:customProviderId/u1', + }, + name: 'u1', + }, + spec: { + memberOf: [], + }, + }, + locationKey: 'msgraph-org-provider:customProviderId', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'msgraph:customProviderId/g1', + 'backstage.io/managed-by-origin-location': + 'msgraph:customProviderId/g1', + }, + name: 'g1', + }, + spec: { + children: [], + type: 'team', + }, + }, + locationKey: 'msgraph-org-provider:customProviderId', + }, + ], + type: 'full', + }; + + it('should apply mutation - manual', async () => { + const config = new ConfigReader({ catalog: { providers: { microsoftGraphOrg: { @@ -92,67 +165,143 @@ describe('MicrosoftGraphOrgEntityProvider', () => { }, }, }, - }; - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - const provider = MicrosoftGraphOrgEntityProvider.fromConfig( - new ConfigReader(config), - { - logger: getVoidLogger(), - schedule: 'manual', - }, - )[0]; - - provider.connect(entityProviderConnection); + }); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + schedule: 'manual', + })[0]; + await provider.connect(entityProviderConnection); await provider.read(); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - entities: [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'msgraph:customProviderId/u1', - 'backstage.io/managed-by-origin-location': - 'msgraph:customProviderId/u1', - }, - name: 'u1', - }, - spec: { - memberOf: [], + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expectedMutation, + ); + }); + + it('should apply mutation - schedule', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', }, }, - locationKey: 'msgraph-org-provider:customProviderId', }, - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'msgraph:customProviderId/g1', - 'backstage.io/managed-by-origin-location': - 'msgraph:customProviderId/g1', - }, - name: 'g1', - }, - spec: { - children: [], - type: 'team', - }, - }, - locationKey: 'msgraph-org-provider:customProviderId', - }, - ], - type: 'full', + }, }); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + schedule: taskRunner, + })[0]; + expect(provider.getProviderName()).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId', + ); + + await provider.connect(entityProviderConnection); + + const taskDef = taskRunner.getTasks()[0]; + expect(taskDef.id).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId:refresh', + ); + await (taskDef.fn as () => Promise)(); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expectedMutation, + ); + }); + + it('should apply mutation - scheduler', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + scheduler, + })[0]; + expect(provider.getProviderName()).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId', + ); + + await provider.connect(entityProviderConnection); + + const taskDef = taskRunner.getTasks()[0]; + expect(taskDef.id).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId:refresh', + ); + await (taskDef.fn as () => Promise)(); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expectedMutation, + ); + }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }, + }, + }, + }); + + expect(() => + MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }, + }, + }, + }); + + expect(() => + MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for MicrosoftGraphOrgEntityProvider:customProviderId', + ); }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 67f3039873..32892e7201 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -67,7 +67,13 @@ export type MicrosoftGraphOrgEntityProviderOptions = * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule: 'manual' | TaskRunner; + schedule?: 'manual' | TaskRunner; + + /** + * Scheduler used to schedule refreshes based on + * the schedule config. + */ + scheduler?: PluginTaskScheduler; /** * The function that transforms a user entry in msgraph to an entity. @@ -168,6 +174,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { ]; } + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + function getTransformer( id: string, transformers?: T | Record, @@ -180,6 +190,16 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { } return readProviderConfigs(configRoot).map(providerConfig => { + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for MicrosoftGraphOrgEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + const provider = new MicrosoftGraphOrgEntityProvider({ id: providerConfig.id, provider: providerConfig, @@ -197,7 +217,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { options.organizationTransformer, ), }); - provider.schedule(options.schedule); + + if (taskRunner !== 'manual') { + provider.schedule(taskRunner); + } return provider; }); @@ -238,7 +261,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { provider, }); - result.schedule(options.schedule); + if (options.schedule !== 'manual') { + result.schedule(options.schedule); + } return result; } @@ -311,16 +336,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule( - schedule: MicrosoftGraphOrgEntityProviderOptions['schedule'], - ) { - if (schedule === 'manual') { - return; - } - + private schedule(taskRunner: TaskRunner) { this.scheduleFn = async () => { const id = `${this.getProviderName()}:refresh`; - await schedule.run({ + await taskRunner.run({ id, fn: async () => { const logger = this.options.logger.child({ diff --git a/yarn.lock b/yarn.lock index ac27eb8215..0c9a4343ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4652,6 +4652,7 @@ __metadata: "@types/lodash": ^4.14.151 "@types/node-fetch": ^2.5.12 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 p-limit: ^3.0.2 From 1416e69de9f73601094bf0a84631036f7ee04a46 Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 16:32:19 +1100 Subject: [PATCH 018/221] Add a config field to configure entity validation This adds a new config field that can be used to configure entity validation. It defaults to false to ensure backwards compatibility. It is not permitted when catalog paths contain wildcards, due to limitations with how the GraphQL query will work. Signed-off-by: Nikolas Skoufis --- .../GitHubEntityProviderConfig.test.ts | 37 +++++++++++++++++++ .../providers/GitHubEntityProviderConfig.ts | 12 ++++++ 2 files changed, 49 insertions(+) diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts index 66a5463bf5..16f9d96010 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts @@ -101,6 +101,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + validateLocationsExist: false, }); expect(providerConfigs[1]).toEqual({ id: 'providerCustomCatalogPath', @@ -115,6 +116,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + validateLocationsExist: false, }); expect(providerConfigs[2]).toEqual({ id: 'providerWithRepositoryFilter', @@ -129,6 +131,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + validateLocationsExist: false, }); expect(providerConfigs[3]).toEqual({ id: 'providerWithBranchFilter', @@ -143,6 +146,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + validateLocationsExist: false, }); expect(providerConfigs[4]).toEqual({ id: 'providerWithTopicFilter', @@ -157,6 +161,7 @@ describe('readProviderConfigs', () => { exclude: ['backstage-exclude'], }, }, + validateLocationsExist: false, }); expect(providerConfigs[5]).toEqual({ id: 'providerWithHost', @@ -171,6 +176,38 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + validateLocationsExist: false, }); }); + + it('defaults validateLocationsExist to false', () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + }, + }, + }, + }); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs[0].validateLocationsExist).toEqual(false); + }); + + it('throws an error when a wildcard catalog path is configured with validation of locations', () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + validateLocationsExist: true, + catalogPath: '/*/catalog-info.yaml', + }, + }, + }, + }); + + expect(() => readProviderConfigs(config)).toThrow(); + }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts index d40b206751..fd5482d1b6 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts @@ -29,6 +29,7 @@ export type GitHubEntityProviderConfig = { branch?: string; topic?: GithubTopicFilters; }; + validateLocationsExist: boolean; }; export type GithubTopicFilters = { @@ -72,6 +73,16 @@ function readProviderConfig( const topicFilterExclude = config?.getOptionalStringArray( 'filters.topic.exclude', ); + const validateLocationsExist = + config?.getOptionalBoolean('validateLocationsExist') ?? false; + + const catalogPathContainsWildcard = catalogPath.includes('*'); + + if (validateLocationsExist && catalogPathContainsWildcard) { + throw Error( + `Error while processing GitHub provider config. The catalog path ${catalogPath} contains a wildcard, which is incompatible with validation of locations existing before emitting them. Ensure that validateLocationsExist is set to false.`, + ); + } return { id, @@ -88,6 +99,7 @@ function readProviderConfig( exclude: topicFilterExclude, }, }, + validateLocationsExist, }; } /** From 143cae25da50e978cff03053c9e0f3ce1f7797af Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 17:15:40 +1100 Subject: [PATCH 019/221] Implement filtering of github entities based on presence This implements the config from the previous commit. The getRepositories graphql query is updated to include information about the specified catalog info file. When validation is turned on, if the file is not present or empty, the location will not be emitted. Signed-off-by: Nikolas Skoufis --- .../src/lib/github.test.ts | 18 ++- .../src/lib/github.ts | 17 ++- .../providers/GitHubEntityProvider.test.ts | 134 ++++++++++++++++++ .../src/providers/GitHubEntityProvider.ts | 11 ++ 4 files changed, 175 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index e2303c16e6..25878c2005 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -213,6 +213,7 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'demo', @@ -222,6 +223,11 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'acb123', + text: 'some yaml', + }, }, ], pageInfo: { @@ -243,6 +249,7 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'demo', @@ -252,6 +259,11 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'acb123', + text: 'some yaml', + }, }, ], }; @@ -262,9 +274,9 @@ describe('github', () => { ), ); - await expect(getOrganizationRepositories(graphql, 'a')).resolves.toEqual( - output, - ); + await expect( + getOrganizationRepositories(graphql, 'a', 'catalog-info.yaml'), + ).resolves.toEqual(output); }); }); }); diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 6a944af330..d854cc9043 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -65,6 +65,11 @@ export type Repository = { defaultBranchRef: { name: string; } | null; + catalogInfoFile: { + __typename: string; + id: string; + text: string; + } | null; }; type RepositoryTopics = { @@ -266,14 +271,22 @@ export async function getOrganizationTeams( export async function getOrganizationRepositories( client: typeof graphql, org: string, + catalogPath: string, ): Promise<{ repositories: Repository[] }> { + const catalogPathRef = `HEAD:${catalogPath}`; const query = ` - query repositories($org: String!, $cursor: String) { + query repositories($org: String!, $catalogPathRef: String!, $cursor: String) { repositoryOwner(login: $org) { login repositories(first: 100, after: $cursor) { nodes { name + catalogInfoFile: object(expression: $catalogPathRef) { + __typename + ... on Blob { + id + } + } url isArchived repositoryTopics(first: 100) { @@ -302,7 +315,7 @@ export async function getOrganizationRepositories( query, r => r.repositoryOwner?.repositories, x => x, - { org }, + { org, catalogPathRef }, ); return { repositories }; diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts index 63fb77288b..8b315a8a1e 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts @@ -170,6 +170,11 @@ describe('GitHubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -267,6 +272,11 @@ describe('GitHubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -341,6 +351,11 @@ describe('GitHubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -381,6 +396,110 @@ describe('GitHubEntityProvider', () => { entities: expectedEntities, }); }); + + it('should filter out invalid locations when validateLocationsExist is set to true', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + myProvider: { + organization: 'test-org', + catalogPath: 'catalog-custom.yaml', + filters: { + branch: 'main', + }, + validateLocationsExist: true, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const provider = GitHubEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + const mockGetOrganizationRepositories = jest.spyOn( + helpers, + 'getOrganizationRepositories', + ); + + mockGetOrganizationRepositories.mockReturnValue( + Promise.resolve({ + repositories: [ + { + name: 'test-repo', + url: 'https://github.com/test-org/test-repo', + repositoryTopics: { + nodes: [], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + catalogInfoFile: null, + }, + { + name: 'another-repo', + url: 'https://github.com/test-org/another-repo', + repositoryTopics: { + nodes: [], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, + }, + ], + }), + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const url = `https://github.com/test-org/another-repo/blob/main/catalog-custom.yaml`; + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${url}`, + 'backstage.io/managed-by-origin-location': `url:${url}`, + }, + name: 'generated-934f500db2ba2e8ea3524567926f45a73bb0b532', + }, + spec: { + presence: 'optional', + target: `${url}`, + type: 'url', + }, + }, + locationKey: 'github-provider:myProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); + }); }); it('apply full update on scheduled execution with topic exclusion taking priority over topic inclusion', async () => { @@ -437,6 +556,11 @@ it('apply full update on scheduled execution with topic exclusion taking priorit defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, { name: 'test-repo-2', @@ -455,6 +579,11 @@ it('apply full update on scheduled execution with topic exclusion taking priorit defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, { name: 'test-repo-3', @@ -470,6 +599,11 @@ it('apply full update on scheduled execution with topic exclusion taking priorit defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts index 3909b5a3c2..42dd6afa5d 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts @@ -163,6 +163,7 @@ export class GitHubEntityProvider implements EntityProvider { private async findCatalogFiles(): Promise { const organization = this.config.organization; const host = this.integration.host; + const catalogPath = this.config.catalogPath; const orgUrl = `https://${host}/${organization}`; const { headers } = await this.githubCredentialsProvider.getCredentials({ @@ -177,8 +178,18 @@ export class GitHubEntityProvider implements EntityProvider { const { repositories } = await getOrganizationRepositories( client, organization, + catalogPath, ); + if (this.config.validateLocationsExist) { + return repositories.filter(repository => { + return ( + repository.catalogInfoFile?.__typename === 'Blob' && + repository.catalogInfoFile.text !== '' + ); + }); + } + return repositories; } From abab3ce38de8f4f2ed3ff09709a87c7b43a9a17a Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 17:23:00 +1100 Subject: [PATCH 020/221] Add docs for the new validateLocationsExist option Signed-off-by: Nikolas Skoufis --- docs/integrations/github/discovery.md | 16 +++++++++++++++- .../processors/GithubDiscoveryProcessor.test.ts | 13 +++++++++++++ .../src/processors/GithubDiscoveryProcessor.ts | 6 +++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 9b60b41682..49ee712c6e 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -96,6 +96,13 @@ catalog: topic: include: ['backstage-include'] # optional array of strings exclude: ['experiments'] # optional array of strings + validateLocationsExist: + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string + filters: + branch: 'main' # string + repository: '.*' # Regex + validateLocationsExist: true # optional boolean enterpriseProviderId: host: ghe.example.net organization: 'backstage' # string @@ -110,7 +117,8 @@ This provider supports multiple organizations via unique provider IDs. - **`catalogPath`** _(optional)_: Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. - You can use wildcards - `*` or `**` - to search the path and/or the filename + You can use wildcards - `*` or `**` - to search the path and/or the filename. + Wildcards cannot be used if the `validateLocationsExist` option is set to `true`. - **filters** _(optional)_: - **branch** _(optional)_: String used to filter results based on the branch name. @@ -131,6 +139,12 @@ This provider supports multiple organizations via unique provider IDs. If you want to add multiple organizations, you need to add one provider config each. - **host** _(optional)_: The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). +- **validateLocationsExist** _(optional)_: + Whether to validate locations that exist before emitting them. + This option avoids generating locations for catalog info files that do not exist in the source repository. + Defaults to `false`. + Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in + conjunction with wildcards in the `catalogPath`. ## GitHub API Rate Limits diff --git a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts index c915e8b136..b69728cc62 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts @@ -153,6 +153,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'master', }, + catalogInfoFile: null, }, { name: 'demo', @@ -162,6 +163,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, ], }); @@ -203,6 +205,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, ], }); @@ -234,6 +237,7 @@ describe('GithubDiscoveryProcessor', () => { repositoryTopics: { nodes: [] }, isArchived: false, defaultBranchRef: null, + catalogInfoFile: null, }, ], }); @@ -259,6 +263,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'master', }, + catalogInfoFile: null, }, ], }); @@ -293,6 +298,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'techdocs-cli', @@ -302,6 +308,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'techdocs-container', @@ -311,6 +318,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'techdocs-durp', @@ -318,6 +326,7 @@ describe('GithubDiscoveryProcessor', () => { repositoryTopics: { nodes: [] }, isArchived: false, defaultBranchRef: null, + catalogInfoFile: null, }, ], }); @@ -360,6 +369,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'test', @@ -369,6 +379,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'test-archived', @@ -378,6 +389,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'testxyz', @@ -387,6 +399,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, ], }); diff --git a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts index 97c77bf033..593f5f72f1 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts @@ -121,7 +121,11 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { const startTimestamp = Date.now(); this.logger.info(`Reading GitHub repositories from ${location.target}`); - const { repositories } = await getOrganizationRepositories(client, org); + const { repositories } = await getOrganizationRepositories( + client, + org, + catalogPath, + ); const matching = repositories.filter( r => !r.isArchived && repoSearchPath.test(r.name), ); From f64d66a45c2ffa9c29b4e161aecc6cc9c6a81c81 Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 17:32:39 +1100 Subject: [PATCH 021/221] Add a changeset for my changes Signed-off-by: Nikolas Skoufis --- .changeset/three-poems-think.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .changeset/three-poems-think.md diff --git a/.changeset/three-poems-think.md b/.changeset/three-poems-think.md new file mode 100644 index 0000000000..13fc9cd328 --- /dev/null +++ b/.changeset/three-poems-think.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +Added the ability for the GitHub discovery processor to validate that catalog files exist before emitting them. + +Users can now set the `validateLocationsExist` property to `true` in their GitHub discovery configuration to opt in to this feature. +This feature only works with `catalogPath`s that do not contain wildcards. + +When `validateLocationsExist` is set to `true`, the GitHub discovery processor will retrieve the object from the +repository at the provided `catalogPath`. +If this file exists and is non-empty, then it will be emitted as a location for further processing. +If this file does not exist or is empty, then it will not be emitted. +Not emitting locations that do not exist allows for far fewer calls to the GitHub API to validate locations that do not exist. From f9f2bcaa121d84bf02b7357480b86e3fcd1ac58d Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 18:00:13 +1100 Subject: [PATCH 022/221] Fix referring to the processor when I meant provider Signed-off-by: Nikolas Skoufis --- .changeset/three-poems-think.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/three-poems-think.md b/.changeset/three-poems-think.md index 13fc9cd328..aad3cae117 100644 --- a/.changeset/three-poems-think.md +++ b/.changeset/three-poems-think.md @@ -2,12 +2,12 @@ '@backstage/plugin-catalog-backend-module-github': minor --- -Added the ability for the GitHub discovery processor to validate that catalog files exist before emitting them. +Added the ability for the GitHub discovery provider to validate that catalog files exist before emitting them. Users can now set the `validateLocationsExist` property to `true` in their GitHub discovery configuration to opt in to this feature. This feature only works with `catalogPath`s that do not contain wildcards. -When `validateLocationsExist` is set to `true`, the GitHub discovery processor will retrieve the object from the +When `validateLocationsExist` is set to `true`, the GitHub discovery provider will retrieve the object from the repository at the provided `catalogPath`. If this file exists and is non-empty, then it will be emitted as a location for further processing. If this file does not exist or is empty, then it will not be emitted. From 0f0bbd70fe5898e9453ee8080ee6faca4598ae3e Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 18:19:05 +1100 Subject: [PATCH 023/221] Add back text field to query Signed-off-by: Nikolas Skoufis --- plugins/catalog-backend-module-github/src/lib/github.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index d854cc9043..7fc69d0f86 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -285,6 +285,7 @@ export async function getOrganizationRepositories( __typename ... on Blob { id + text } } url From 4a5fd284ee536ecde94967f24596b99a0b965945 Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 18:31:00 +1100 Subject: [PATCH 024/221] Strip leading slash if present in catalog path ref Without this, the graphql query fails to return matching catalog paths Signed-off-by: Nikolas Skoufis --- plugins/catalog-backend-module-github/src/lib/github.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 7fc69d0f86..ef6a7f817a 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -273,7 +273,14 @@ export async function getOrganizationRepositories( org: string, catalogPath: string, ): Promise<{ repositories: Repository[] }> { - const catalogPathRef = `HEAD:${catalogPath}`; + let relativeCatalogPathRef: string; + // We must strip the leading slash or the query for objects does not work + if (catalogPath.startsWith('/')) { + relativeCatalogPathRef = catalogPath.substring(1); + } else { + relativeCatalogPathRef = catalogPath; + } + const catalogPathRef = `HEAD:${relativeCatalogPathRef}`; const query = ` query repositories($org: String!, $catalogPathRef: String!, $cursor: String) { repositoryOwner(login: $org) { From 334dd9042c47a66442c044e2d4184eb45cce1520 Mon Sep 17 00:00:00 2001 From: Nikolas Skoufis Date: Mon, 10 Oct 2022 18:33:50 +1100 Subject: [PATCH 025/221] Fix linting issue in docs Signed-off-by: Nikolas Skoufis --- docs/integrations/github/discovery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 49ee712c6e..742f40f8af 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -143,7 +143,7 @@ This provider supports multiple organizations via unique provider IDs. Whether to validate locations that exist before emitting them. This option avoids generating locations for catalog info files that do not exist in the source repository. Defaults to `false`. - Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in + Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in conjunction with wildcards in the `catalogPath`. ## GitHub API Rate Limits From 1d4a87eea44f6a5fc5c8982c6c3432eab3fed98c Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Mon, 10 Oct 2022 09:34:34 -0600 Subject: [PATCH 026/221] condense metrics and use a result label Signed-off-by: Spencer Henry --- .changeset/flat-items-perform.md | 22 ++++---------- .../tasks/NunjucksWorkflowRunner.ts | 30 ++++++++----------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/.changeset/flat-items-perform.md b/.changeset/flat-items-perform.md index 949814c064..ce5b95c7ca 100644 --- a/.changeset/flat-items-perform.md +++ b/.changeset/flat-items-perform.md @@ -4,19 +4,13 @@ Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: -- `scaffolder_task_success_count`: Tracks successful task runs. - - Labels: - - - `template`: The entity ref of the scaffolded template - - `user`: The entity ref of the user that invoked the template run - -- `scaffolder_task_error_count`: a count that track how many task runs error out +- `scaffolder_task_count`: Tracks successful task runs. Labels: - `template`: The entity ref of the scaffolded template - `user`: The entity ref of the user that invoked the template run + - `result`: A string describing whether the task ran successfully, errored out, or was skipped - `scaffolder_task_duration`: a histogram which tracks the duration of a task run @@ -25,19 +19,13 @@ Added a set of default Prometheus metrics around scaffolding. See below for a li - `template`: The entity ref of the scaffolded template - `result`: A boolean describing whether the task ran successfully -- `scaffolder_step_success_count`: a count that tracks each step run - - Labels: - - - `template`: The entity ref of the scaffolded template - - `step`: The name of the step that was run - -- `scaffolder_step_error_count`: a count that tracks how many steps error out +- `scaffolder_step_count`: a count that tracks each step run Labels: - `template`: The entity ref of the scaffolded template - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, errored out, or was skipped - `scaffolder_step_duration`: a histogram which tracks the duration of each step run @@ -45,6 +33,6 @@ Added a set of default Prometheus metrics around scaffolding. See below for a li - `template`: The entity ref of the scaffolded template - `step`: The name of the step that was run - - `result`: A boolean describing whether the task ran successfully + - `result`: A string describing whether the task ran successfully, errored out, or was skipped You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index f4776bc033..a63bb48694 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -354,30 +354,20 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } function scaffoldingTracker() { - const taskSuccesses = createCounterMetric({ + const taskCount = createCounterMetric({ name: 'scaffolder_task_success_count', help: 'Count of succesful task runs', - labelNames: ['template', 'user'], - }); - const taskErrors = createCounterMetric({ - name: 'scaffolder_task_error_count', - help: 'Count of failed task runs', - labelNames: ['template', 'user'], + labelNames: ['template', 'user', 'result'], }); const taskDuration = createHistogramMetric({ name: 'scaffolder_task_duration', help: 'Duration of a task run', labelNames: ['template', 'result'], }); - const stepSuccesses = createCounterMetric({ + const stepCount = createCounterMetric({ name: 'scaffolder_step_success_count', help: 'Count of successful step runs', - labelNames: ['template', 'step'], - }); - const stepErrors = createCounterMetric({ - name: 'scaffolder_step_error_count', - help: 'Count of failed step runs', - labelNames: ['template', 'step'], + labelNames: ['template', 'step', 'result'], }); const stepDuration = createHistogramMetric({ name: 'scaffolder_step_duration', @@ -405,9 +395,10 @@ function scaffoldingTracker() { } function markSuccessful() { - taskSuccesses.inc({ + taskCount.inc({ template, user, + result: 'ok' }); taskTimer({ result: 'ok' }); } @@ -417,9 +408,10 @@ function scaffoldingTracker() { stepId: step.id, status: 'failed', }); - taskErrors.inc({ + taskCount.inc({ template, user, + result: 'failed' }); taskTimer({ result: 'failed' }); } @@ -448,17 +440,19 @@ function scaffoldingTracker() { stepId: step.id, status: 'completed', }); - stepSuccesses.inc({ + stepCount.inc({ template, step: step.name, + result: 'ok' }); stepTimer({ result: 'ok' }); } function markFailed() { - stepErrors.inc({ + stepCount.inc({ template, step: step.name, + result: 'failed' }); stepTimer({ result: 'failed' }); } From 1a9d19dc6d8cb1d06cd1398a500316a688cbeded Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Mon, 10 Oct 2022 09:54:32 -0600 Subject: [PATCH 027/221] linting fixes Signed-off-by: Spencer Henry --- .changeset/flat-items-perform.md | 6 +++--- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.changeset/flat-items-perform.md b/.changeset/flat-items-perform.md index ce5b95c7ca..46626ac667 100644 --- a/.changeset/flat-items-perform.md +++ b/.changeset/flat-items-perform.md @@ -10,7 +10,7 @@ Added a set of default Prometheus metrics around scaffolding. See below for a li - `template`: The entity ref of the scaffolded template - `user`: The entity ref of the user that invoked the template run - - `result`: A string describing whether the task ran successfully, errored out, or was skipped + - `result`: A string describing whether the task ran successfully, failed, or was skipped - `scaffolder_task_duration`: a histogram which tracks the duration of a task run @@ -25,7 +25,7 @@ Added a set of default Prometheus metrics around scaffolding. See below for a li - `template`: The entity ref of the scaffolded template - `step`: The name of the step that was run - - `result`: A string describing whether the task ran successfully, errored out, or was skipped + - `result`: A string describing whether the task ran successfully, failed, or was skipped - `scaffolder_step_duration`: a histogram which tracks the duration of each step run @@ -33,6 +33,6 @@ Added a set of default Prometheus metrics around scaffolding. See below for a li - `template`: The entity ref of the scaffolded template - `step`: The name of the step that was run - - `result`: A string describing whether the task ran successfully, errored out, or was skipped + - `result`: A string describing whether the task ran successfully, failed, or was skipped You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index a63bb48694..f651be4830 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -398,7 +398,7 @@ function scaffoldingTracker() { taskCount.inc({ template, user, - result: 'ok' + result: 'ok', }); taskTimer({ result: 'ok' }); } @@ -411,7 +411,7 @@ function scaffoldingTracker() { taskCount.inc({ template, user, - result: 'failed' + result: 'failed', }); taskTimer({ result: 'failed' }); } @@ -443,7 +443,7 @@ function scaffoldingTracker() { stepCount.inc({ template, step: step.name, - result: 'ok' + result: 'ok', }); stepTimer({ result: 'ok' }); } @@ -452,7 +452,7 @@ function scaffoldingTracker() { stepCount.inc({ template, step: step.name, - result: 'failed' + result: 'failed', }); stepTimer({ result: 'failed' }); } From d940d9a4aaad98a0f99eacd0473b2bce2c99db48 Mon Sep 17 00:00:00 2001 From: marcofaggian Date: Tue, 11 Oct 2022 12:14:47 +0200 Subject: [PATCH 028/221] refactor(plugins/tech-radar): useMemo, avoid slice Signed-off-by: marcofaggian --- .../tech-radar/src/components/Radar/Radar.tsx | 33 +++++++++---------- .../tech-radar/src/components/Radar/utils.ts | 16 ++++----- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index 15fcb697c2..0193c75eb1 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useEffect, useRef, useState } from 'react'; +import { useMemo, useRef, useState } from 'react'; import type { Entry, Quadrant, Ring } from '../../utils/types'; import RadarPlot from '../RadarPlot'; import { adjustEntries, adjustQuadrants, adjustRings } from './utils'; @@ -36,26 +36,23 @@ const Radar = ({ entries, ...props }: Props): JSX.Element => { - const [adjustedQuadrants, setAdjustedQuadrants] = - useState>(quadrants); - const [adjustedRings, setAdjustedRings] = useState>(rings); - const [adjustedEntries, setAdjustedEntries] = useState>(entries); - const radius = Math.min(width, height) / 2; + // State const [activeEntry, setActiveEntry] = useState(); const node = useRef(null); - useEffect(() => { - setAdjustedQuadrants(adjustQuadrants(quadrants, radius, width, height)); - }, [quadrants, radius, width, height]); - - useEffect(() => { - setAdjustedRings(adjustRings(rings, radius)); - }, [radius, rings]); - - useEffect(() => { - setAdjustedEntries( + // Adjusted props + const adjustedQuadrants = useMemo( + () => adjustQuadrants(quadrants, radius, width, height), + [quadrants, radius, width, height], + ); + const adjustedRings = useMemo( + () => adjustRings(rings, radius), + [radius, rings], + ); + const adjustedEntries = useMemo( + () => adjustEntries( entries, adjustedQuadrants, @@ -63,8 +60,8 @@ const Radar = ({ radius, activeEntry, ), - ); - }, [entries, adjustedQuadrants, adjustedRings, radius, activeEntry]); + [entries, adjustedQuadrants, adjustedRings, radius, activeEntry], + ); return ( diff --git a/plugins/tech-radar/src/components/Radar/utils.ts b/plugins/tech-radar/src/components/Radar/utils.ts index 5b458068e6..7fff038023 100644 --- a/plugins/tech-radar/src/components/Radar/utils.ts +++ b/plugins/tech-radar/src/components/Radar/utils.ts @@ -81,7 +81,7 @@ export const adjustQuadrants = ( }, ]; - return quadrants.slice().map((quadrant, index) => { + return quadrants.map((quadrant, index) => { const legendParam = legendParams[index % 4]; return { @@ -100,14 +100,14 @@ export const adjustQuadrants = ( }; export const adjustEntries = ( - _entries: Entry[], + entries: Entry[], quadrants: Quadrant[], rings: Ring[], radius: number, activeEntry?: Entry, -) => { +): Entry[] => { let seed = 42; - const entries = _entries.map((entry, index) => { + const adjustedEntries = entries.map((entry, index) => { const quadrant = quadrants.find(q => { const match = typeof entry.quadrant === 'object' ? entry.quadrant.id : entry.quadrant; @@ -145,7 +145,7 @@ export const adjustEntries = ( }); const simulation = forceSimulation() - .nodes(entries) + .nodes(adjustedEntries) .velocityDecay(0.19) .force('collision', forceCollide().radius(12).strength(0.85)) .stop(); @@ -160,7 +160,7 @@ export const adjustEntries = ( ) { simulation.tick(); - for (const entry of entries) { + for (const entry of adjustedEntries) { if (entry.segment) { entry.x = entry.segment.clipx(entry); entry.y = entry.segment.clipy(entry); @@ -168,11 +168,11 @@ export const adjustEntries = ( } } - return entries; + return adjustedEntries; }; export const adjustRings = (rings: Ring[], radius: number) => - rings.slice().map((ring, index) => ({ + rings.map((ring, index) => ({ ...ring, index, outerRadius: ((index + 2) / (rings.length + 1)) * radius, From 1329a9a7365ac568a4cb31b15d2b2b91f0fa2c58 Mon Sep 17 00:00:00 2001 From: marcofaggian Date: Tue, 11 Oct 2022 12:26:33 +0200 Subject: [PATCH 029/221] fix(plugins/tech-radar): missing React import Signed-off-by: marcofaggian --- plugins/tech-radar/src/components/Radar/Radar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index 0193c75eb1..8ba5af9811 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { useMemo, useRef, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import type { Entry, Quadrant, Ring } from '../../utils/types'; import RadarPlot from '../RadarPlot'; import { adjustEntries, adjustQuadrants, adjustRings } from './utils'; From 384f99c2766868b2c067e594afe16a65dc4fbe95 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 7 Oct 2022 15:52:22 +0200 Subject: [PATCH 030/221] feat(catalog/msgraph): Add backend plugin Add `microsoftGraphOrgEntityProviderCatalogModule` (new backend-plugin-api, alpha). Relates-to: PR #13859 Relates-to: PR #14034 Signed-off-by: Patrick Jungermann --- .changeset/mean-files-fly.md | 5 + .../api-report.md | 15 +++ .../package.json | 14 ++- .../src/index.ts | 4 +- ...raphOrgEntityProviderCatalogModule.test.ts | 89 ++++++++++++++++++ ...softGraphOrgEntityProviderCatalogModule.ts | 93 +++++++++++++++++++ yarn.lock | 2 + 7 files changed, 216 insertions(+), 6 deletions(-) create mode 100644 .changeset/mean-files-fly.md create mode 100644 plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.test.ts create mode 100644 plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.ts diff --git a/.changeset/mean-files-fly.md b/.changeset/mean-files-fly.md new file mode 100644 index 0000000000..a7a79bcf74 --- /dev/null +++ b/.changeset/mean-files-fly.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Add `microsoftGraphOrgEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index f5a77ceb51..921d682b89 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -133,6 +134,20 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { read(options?: { logger?: Logger }): Promise; } +// @alpha +export const microsoftGraphOrgEntityProviderCatalogModule: ( + options?: MicrosoftGraphOrgEntityProviderCatalogModuleOptions | undefined, +) => BackendFeature; + +// @alpha +export interface MicrosoftGraphOrgEntityProviderCatalogModuleOptions { + groupTransformer?: GroupTransformer | Record; + organizationTransformer?: + | OrganizationTransformer + | Record; + userTransformer?: UserTransformer | Record; +} + // @public @deprecated export interface MicrosoftGraphOrgEntityProviderLegacyOptions { groupTransformer?: GroupTransformer; diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 0a5aed29f9..91a3b9973a 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -7,6 +7,7 @@ "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,20 +24,22 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@azure/identity": "^2.1.0", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", @@ -55,8 +58,9 @@ "msw": "^0.47.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-msgraph/src/index.ts b/plugins/catalog-backend-module-msgraph/src/index.ts index 6a7adedc58..b80a0b3863 100644 --- a/plugins/catalog-backend-module-msgraph/src/index.ts +++ b/plugins/catalog-backend-module-msgraph/src/index.ts @@ -20,5 +20,7 @@ * @packageDocumentation */ -export * from './processors'; export * from './microsoftGraph'; +export * from './processors'; +export { microsoftGraphOrgEntityProviderCatalogModule } from './service/MicrosoftGraphOrgEntityProviderCatalogModule'; +export type { MicrosoftGraphOrgEntityProviderCatalogModuleOptions } from './service/MicrosoftGraphOrgEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..764f504898 --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { microsoftGraphOrgEntityProviderCatalogModule } from './MicrosoftGraphOrgEntityProviderCatalogModule'; +import { MicrosoftGraphOrgEntityProvider } from '../processors'; + +describe('awsS3EntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [microsoftGraphOrgEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('PT30M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..9e817636fa --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { + GroupTransformer, + OrganizationTransformer, + UserTransformer, +} from '../microsoftGraph'; +import { MicrosoftGraphOrgEntityProvider } from '../processors'; + +/** + * Options for {@link microsoftGraphOrgEntityProviderCatalogModule}. + * + * @alpha + */ +export interface MicrosoftGraphOrgEntityProviderCatalogModuleOptions { + /** + * The function that transforms a user entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + userTransformer?: UserTransformer | Record; + + /** + * The function that transforms a group entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + groupTransformer?: GroupTransformer | Record; + + /** + * The function that transforms an organization entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + organizationTransformer?: + | OrganizationTransformer + | Record; +} + +/** + * Registers the MicrosoftGraphOrgEntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const microsoftGraphOrgEntityProviderCatalogModule = createBackendModule( + { + pluginId: 'catalog', + moduleId: 'microsoftGraphOrgEntityProvider', + register( + env, + options?: MicrosoftGraphOrgEntityProviderCatalogModuleOptions, + ) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: configServiceRef, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ catalog, config, logger, scheduler }) { + catalog.addEntityProvider( + MicrosoftGraphOrgEntityProvider.fromConfig(config, { + groupTransformer: options?.groupTransformer, + logger: loggerToWinstonLogger(logger), + organizationTransformer: options?.organizationTransformer, + scheduler, + userTransformer: options?.userTransformer, + }), + ); + }, + }); + }, + }, +); diff --git a/yarn.lock b/yarn.lock index 0c9a4343ba..fbc337dac6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4642,12 +4642,14 @@ __metadata: dependencies: "@azure/identity": ^2.1.0 "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@microsoft/microsoft-graph-types": ^2.6.0 "@types/lodash": ^4.14.151 "@types/node-fetch": ^2.5.12 From 3328ae8889b2e498ffb2188bb43373e27e7c17b9 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Fri, 23 Sep 2022 17:25:50 +0200 Subject: [PATCH 031/221] rename all incident entities to alert Signed-off-by: Marko Simon --- plugins/ilert/src/api/client.ts | 129 ++++++++---------- plugins/ilert/src/api/index.ts | 8 +- plugins/ilert/src/api/types.ts | 50 +++---- .../AlertActionsMenu.tsx} | 76 +++++------ .../AlertAssignModal.tsx} | 67 +++++---- .../IncidentLink.tsx => Alert/AlertLink.tsx} | 17 +-- .../AlertNewModal.tsx} | 52 +++---- .../AlertStatus.tsx} | 14 +- .../{IncidentsPage => Alert}/index.ts | 4 +- .../AlertsPage.tsx} | 52 +++---- .../AlertsTable.tsx} | 74 +++++----- .../StatusChip.tsx | 12 +- .../TableTitle.tsx | 34 ++--- .../{Incident => AlertsPage}/index.ts | 4 +- .../src/components/ILertCard/ILertCard.tsx | 50 +++---- .../ILertCard/ILertCardActionsHeader.tsx | 26 ++-- .../src/components/ILertPage/ILertPage.tsx | 20 +-- ...eIncidentActions.ts => useAlertActions.ts} | 31 ++--- plugins/ilert/src/hooks/useAlertSource.ts | 6 +- .../ilert/src/hooks/useAlertSourceOnCalls.ts | 6 +- .../hooks/{useIncidents.ts => useAlerts.ts} | 86 ++++++------ ...useAssignIncident.ts => useAssignAlert.ts} | 34 ++--- .../{useNewIncident.ts => useNewAlert.ts} | 8 +- plugins/ilert/src/types.ts | 44 +++--- 24 files changed, 430 insertions(+), 474 deletions(-) rename plugins/ilert/src/components/{Incident/IncidentActionsMenu.tsx => Alert/AlertActionsMenu.tsx} (73%) rename plugins/ilert/src/components/{Incident/IncidentAssignModal.tsx => Alert/AlertAssignModal.tsx} (76%) rename plugins/ilert/src/components/{Incident/IncidentLink.tsx => Alert/AlertLink.tsx} (80%) rename plugins/ilert/src/components/{Incident/IncidentNewModal.tsx => Alert/AlertNewModal.tsx} (92%) rename plugins/ilert/src/components/{Incident/IncidentStatus.tsx => Alert/AlertStatus.tsx} (79%) rename plugins/ilert/src/components/{IncidentsPage => Alert}/index.ts (90%) rename plugins/ilert/src/components/{IncidentsPage/IncidentsPage.tsx => AlertsPage/AlertsPage.tsx} (71%) rename plugins/ilert/src/components/{IncidentsPage/IncidentsTable.tsx => AlertsPage/AlertsTable.tsx} (80%) rename plugins/ilert/src/components/{IncidentsPage => AlertsPage}/StatusChip.tsx (82%) rename plugins/ilert/src/components/{IncidentsPage => AlertsPage}/TableTitle.tsx (76%) rename plugins/ilert/src/components/{Incident => AlertsPage}/index.ts (89%) rename plugins/ilert/src/hooks/{useIncidentActions.ts => useAlertActions.ts} (69%) rename plugins/ilert/src/hooks/{useIncidents.ts => useAlerts.ts} (57%) rename plugins/ilert/src/hooks/{useAssignIncident.ts => useAssignAlert.ts} (68%) rename plugins/ilert/src/hooks/{useNewIncident.ts => useNewAlert.ts} (95%) diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts index 7ab9a6d08c..2fa6f38c6e 100644 --- a/plugins/ilert/src/api/client.ts +++ b/plugins/ilert/src/api/client.ts @@ -13,30 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AuthenticationError, ResponseError } from '@backstage/errors'; import { + ConfigApi, + createApiRef, + DiscoveryApi, +} from '@backstage/core-plugin-api'; +import { AuthenticationError, ResponseError } from '@backstage/errors'; +import { DateTime as dt } from 'luxon'; +import { + Alert, + AlertAction, + AlertResponder, AlertSource, EscalationPolicy, - Incident, - IncidentAction, - IncidentResponder, OnCall, Schedule, UptimeMonitor, User, } from '../types'; import { - ILertApi, - GetIncidentsOpts, - GetIncidentsCountOpts, EventRequest, + GetAlertsCountOpts, + GetAlertsOpts, + ILertApi, } from './types'; -import { DateTime as dt } from 'luxon'; -import { - ConfigApi, - createApiRef, - DiscoveryApi, -} from '@backstage/core-plugin-api'; /** @public */ export const ilertApiRef = createApiRef({ @@ -102,7 +102,7 @@ export class ILertClient implements ILertApi { return await response.json(); } - async fetchIncidents(opts?: GetIncidentsOpts): Promise { + async fetchAlerts(opts?: GetAlertsOpts): Promise { const init = { headers: JSON_HEADERS, }; @@ -126,15 +126,12 @@ export class ILertClient implements ILertApi { query.append('state', state); }); } - const response = await this.fetch( - `/api/v1/incidents?${query.toString()}`, - init, - ); + const response = await this.fetch(`/api/alerts?${query.toString()}`, init); return response; } - async fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise { + async fetchAlertsCount(opts?: GetAlertsCountOpts): Promise { const init = { headers: JSON_HEADERS, }; @@ -145,96 +142,85 @@ export class ILertClient implements ILertApi { }); } const response = await this.fetch( - `/api/v1/incidents/count?${query.toString()}`, + `/api/alerts/count?${query.toString()}`, init, ); return response && response.count ? response.count : 0; } - async fetchIncident(id: number): Promise { + async fetchAlert(id: number): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(id)}`, + `/api/alerts/${encodeURIComponent(id)}`, init, ); return response; } - async fetchIncidentResponders( - incident: Incident, - ): Promise { + async fetchAlertResponders(alert: Alert): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/responders`, + `/api/alerts/${encodeURIComponent(alert.id)}/responders`, init, ); return response; } - async fetchIncidentActions(incident: Incident): Promise { + async fetchAlertActions(alert: Alert): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + `/api/alerts/${encodeURIComponent(alert.id)}/actions`, init, ); return response; } - async acceptIncident( - incident: Incident, - userName: string, - ): Promise { + async acceptAlert(alert: Alert, userName: string): Promise { const init = { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ - apiKey: incident.alertSource?.integrationKey || '', - incidentKey: incident.incidentKey, + apiKey: alert.alertSource?.integrationKey || '', + alertKey: alert.alertKey, summary: `from ${userName} via Backstage plugin`, eventType: 'ACCEPT', }), }; - await this.fetch('/api/v1/events', init); - return this.fetchIncident(incident.id); + await this.fetch('/api/events', init); + return this.fetchAlert(alert.id); } - async resolveIncident( - incident: Incident, - userName: string, - ): Promise { + async resolveAlert(alert: Alert, userName: string): Promise { const init = { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ - apiKey: incident.alertSource?.integrationKey || '', - incidentKey: incident.incidentKey, + apiKey: alert.alertSource?.integrationKey || '', + alertKey: alert.alertKey, summary: `from ${userName} via Backstage plugin`, eventType: 'RESOLVE', }), }; - await this.fetch('/api/v1/events', init); - return this.fetchIncident(incident.id); + await this.fetch('/api/events', init); + return this.fetchAlert(alert.id); } - async assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise { + async assignAlert(alert: Alert, responder: AlertResponder): Promise { const init = { method: 'PUT', headers: JSON_HEADERS, @@ -254,19 +240,14 @@ export class ILertClient implements ILertApi { } const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent( - incident.id, - )}/assign?${query.toString()}`, + `/api/alerts/${encodeURIComponent(alert.id)}/assign?${query.toString()}`, init, ); return response; } - async triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise { + async triggerAlertAction(alert: Alert, action: AlertAction): Promise { const init = { method: 'POST', headers: JSON_HEADERS, @@ -279,12 +260,12 @@ export class ILertClient implements ILertApi { }; await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + `/api/alerts/${encodeURIComponent(alert.id)}/actions`, init, ); } - async createIncident(eventRequest: EventRequest): Promise { + async createAlert(eventRequest: EventRequest): Promise { const init = { method: 'POST', headers: JSON_HEADERS, @@ -305,7 +286,7 @@ export class ILertClient implements ILertApi { }), }; - const response = await this.fetch('/api/v1/events', init); + const response = await this.fetch('/api/events', init); return response; } @@ -314,7 +295,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/uptime-monitors', init); + const response = await this.fetch('/api/uptime-monitors', init); return response; } @@ -325,7 +306,7 @@ export class ILertClient implements ILertApi { }; const response: UptimeMonitor = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(id)}`, + `/api/uptime-monitors/${encodeURIComponent(id)}`, init, ); @@ -342,7 +323,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, + `/api/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, init, ); @@ -359,7 +340,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, + `/api/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, init, ); @@ -371,7 +352,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/alert-sources', init); + const response = await this.fetch('/api/alert-sources', init); return response; } @@ -384,7 +365,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(idOrIntegrationKey)}`, + `/api/alert-sources/${encodeURIComponent(idOrIntegrationKey)}`, init, ); @@ -397,7 +378,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/on-calls?policies=${encodeURIComponent( + `/api/on-calls?policies=${encodeURIComponent( alertSource.escalationPolicy.id, )}&expand=user&expand=escalationPolicy&timezone=${dt.local().zoneName}`, init, @@ -414,7 +395,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + `/api/alert-sources/${encodeURIComponent(alertSource.id)}`, init, ); @@ -429,7 +410,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + `/api/alert-sources/${encodeURIComponent(alertSource.id)}`, init, ); @@ -453,7 +434,7 @@ export class ILertClient implements ILertApi { }), }; - const response = await this.fetch('/api/v1/maintenance-windows', init); + const response = await this.fetch('/api/maintenance-windows', init); return response; } @@ -463,7 +444,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/schedules', init); + const response = await this.fetch('/api/schedules', init); return response; } @@ -473,7 +454,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/users', init); + const response = await this.fetch('/api/users', init); return response; } @@ -491,17 +472,15 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/schedules/${encodeURIComponent(scheduleId)}/overrides`, + `/api/schedules/${encodeURIComponent(scheduleId)}/overrides`, init, ); return response; } - getIncidentDetailsURL(incident: Incident): string { - return `${this.baseUrl}/incident/view.jsf?id=${encodeURIComponent( - incident.id, - )}`; + getAlertDetailsURL(alert: Alert): string { + return `${this.baseUrl}/alert/view.jsf?id=${encodeURIComponent(alert.id)}`; } getAlertSourceDetailsURL(alertSource: AlertSource | null): string { diff --git a/plugins/ilert/src/api/index.ts b/plugins/ilert/src/api/index.ts index e1163090ec..b33a702a5c 100644 --- a/plugins/ilert/src/api/index.ts +++ b/plugins/ilert/src/api/index.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -export { ILertClient, ilertApiRef } from './client'; +export { ilertApiRef, ILertClient } from './client'; export type { - ILertApi, EventRequest, - GetIncidentsCountOpts, - GetIncidentsOpts, + GetAlertsCountOpts, + GetAlertsOpts, + ILertApi, TableState, } from './types'; diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index 2e9ea30f2f..b7af3d845d 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -15,16 +15,16 @@ */ import { + Alert, + AlertAction, + AlertResponder, AlertSource, - Incident, - User, - IncidentStatus, - UptimeMonitor, + AlertStatus, EscalationPolicy, - Schedule, - IncidentResponder, - IncidentAction, OnCall, + Schedule, + UptimeMonitor, + User, } from '../types'; /** @public */ @@ -34,16 +34,16 @@ export type TableState = { }; /** @public */ -export type GetIncidentsOpts = { +export type GetAlertsOpts = { maxResults?: number; startIndex?: number; - states?: IncidentStatus[]; + states?: AlertStatus[]; alertSources?: number[]; }; /** @public */ -export type GetIncidentsCountOpts = { - states?: IncidentStatus[]; +export type GetAlertsCountOpts = { + states?: AlertStatus[]; }; /** @public */ @@ -57,22 +57,16 @@ export type EventRequest = { /** @public */ export interface ILertApi { - fetchIncidents(opts?: GetIncidentsOpts): Promise; - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - fetchIncident(id: number): Promise; - fetchIncidentResponders(incident: Incident): Promise; - fetchIncidentActions(incident: Incident): Promise; - acceptIncident(incident: Incident, userName: string): Promise; - resolveIncident(incident: Incident, userName: string): Promise; - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; - createIncident(eventRequest: EventRequest): Promise; - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; + fetchAlerts(opts?: GetAlertsOpts): Promise; + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + fetchAlert(id: number): Promise; + fetchAlertResponders(alert: Alert): Promise; + fetchAlertActions(alert: Alert): Promise; + acceptAlert(alert: Alert, userName: string): Promise; + resolveAlert(alert: Alert, userName: string): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; + createAlert(eventRequest: EventRequest): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; fetchUptimeMonitors(): Promise; pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; @@ -100,7 +94,7 @@ export interface ILertApi { end: string, ): Promise; - getIncidentDetailsURL(incident: Incident): string; + getAlertDetailsURL(alert: Alert): string; getAlertSourceDetailsURL(alertSource: AlertSource | null): string; getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; diff --git a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx b/plugins/ilert/src/components/Alert/AlertActionsMenu.tsx similarity index 73% rename from plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx rename to plugins/ilert/src/components/Alert/AlertActionsMenu.tsx index 909e4dc362..ff20e39b24 100644 --- a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx +++ b/plugins/ilert/src/components/Alert/AlertActionsMenu.tsx @@ -13,42 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; import { ilertApiRef } from '../../api'; -import { Incident, IncidentAction } from '../../types'; -import { IncidentAssignModal } from './IncidentAssignModal'; -import { useIncidentActions } from '../../hooks/useIncidentActions'; +import { useAlertActions } from '../../hooks/useAlertActions'; +import { Alert, AlertAction } from '../../types'; +import { AlertAssignModal } from './AlertAssignModal'; +import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; +import { Link, Progress } from '@backstage/core-components'; import { alertApiRef, - useApi, identityApiRef, + useApi, } from '@backstage/core-plugin-api'; -import { Progress, Link } from '@backstage/core-components'; -import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; -export const IncidentActionsMenu = ({ - incident, - onIncidentChanged, +export const AlertActionsMenu = ({ + alert, + onAlertChanged, setIsLoading, }: { - incident: Incident; - onIncidentChanged?: (incident: Incident) => void; + alert: Alert; + onAlertChanged?: (alert: Alert) => void; setIsLoading?: (isLoading: boolean) => void; }) => { const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); const [anchorEl, setAnchorEl] = React.useState(null); - const callback = onIncidentChanged || ((_: Incident): void => {}); + const callback = onAlertChanged || ((_: Alert): void => {}); const setProcessing = setIsLoading || ((_: boolean): void => {}); - const [isAssignIncidentModalOpened, setIsAssignIncidentModalOpened] = + const [isAssignAlertModalOpened, setIsAssignAlertModalOpened] = React.useState(false); - const [{ incidentActions, isLoading }] = useIncidentActions( - incident, + const [{ alertActions, isLoading }] = useAlertActions( + alert, Boolean(anchorEl), ); @@ -70,10 +70,10 @@ export const IncidentActionsMenu = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - const newIncident = await ilertApi.acceptIncident(incident, userName); - alertApi.post({ message: 'Incident accepted.' }); + const newAlert = await ilertApi.acceptAlert(alert, userName); + alertApi.post({ message: 'Alert accepted.' }); - callback(newIncident); + callback(newAlert); setProcessing(false); } catch (err) { setProcessing(false); @@ -90,10 +90,10 @@ export const IncidentActionsMenu = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - const newIncident = await ilertApi.resolveIncident(incident, userName); - alertApi.post({ message: 'Incident resolved.' }); + const newAlert = await ilertApi.resolveAlert(alert, userName); + alertApi.post({ message: 'Alert resolved.' }); - callback(newIncident); + callback(newAlert); setProcessing(false); } catch (err) { setProcessing(false); @@ -103,15 +103,15 @@ export const IncidentActionsMenu = ({ const handleAssign = () => { handleCloseMenu(); - setIsAssignIncidentModalOpened(true); + setIsAssignAlertModalOpened(true); }; - const handleTriggerAction = (action: IncidentAction) => async () => { + const handleTriggerAction = (action: AlertAction) => async () => { try { handleCloseMenu(); setProcessing(true); - await ilertApi.triggerIncidentAction(incident, action); - alertApi.post({ message: 'Incident action triggered.' }); + await ilertApi.triggerAlertAction(alert, action); + alertApi.post({ message: 'Alert action triggered.' }); setProcessing(false); } catch (err) { setProcessing(false); @@ -119,7 +119,7 @@ export const IncidentActionsMenu = ({ } }; - const actions: React.ReactNode[] = incidentActions.map(a => { + const actions: React.ReactNode[] = alertActions.map(a => { const successTrigger = a.history ? a.history.find(h => h.success) : undefined; @@ -152,7 +152,7 @@ export const IncidentActionsMenu = ({ - {incident.status === 'PENDING' ? ( + {alert.status === 'PENDING' ? ( Accept @@ -169,7 +169,7 @@ export const IncidentActionsMenu = ({ ) : null} - {incident.status !== 'RESOLVED' ? ( + {alert.status !== 'RESOLVED' ? ( Resolve @@ -177,7 +177,7 @@ export const IncidentActionsMenu = ({ ) : null} - {incident.status !== 'RESOLVED' ? ( + {alert.status !== 'RESOLVED' ? ( Assign @@ -195,17 +195,15 @@ export const IncidentActionsMenu = ({ - - View in iLert - + View in iLert - ); diff --git a/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx b/plugins/ilert/src/components/Alert/AlertAssignModal.tsx similarity index 76% rename from plugins/ilert/src/components/Incident/IncidentAssignModal.tsx rename to plugins/ilert/src/components/Alert/AlertAssignModal.tsx index 5fd8a97df3..d1ecefafa1 100644 --- a/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx +++ b/plugins/ilert/src/components/Alert/AlertAssignModal.tsx @@ -13,21 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import { Typography } from '@material-ui/core'; import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import MUIAlert from '@material-ui/lab/Alert'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import { useAssignIncident } from '../../hooks/useAssignIncident'; -import { Typography } from '@material-ui/core'; +import React from 'react'; import { ilertApiRef } from '../../api'; -import { Incident } from '../../types'; -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import { useAssignAlert } from '../../hooks/useAssignAlert'; +import { Alert } from '../../types'; const useStyles = makeStyles(() => ({ container: { @@ -55,45 +55,42 @@ const useStyles = makeStyles(() => ({ }, })); -export const IncidentAssignModal = ({ - incident, +export const AlertAssignModal = ({ + alert, isModalOpened, setIsModalOpened, - onIncidentChanged, + onAlertChanged, }: { - incident: Incident | null; + alert: Alert | null; isModalOpened: boolean; setIsModalOpened: (open: boolean) => void; - onIncidentChanged?: (incident: Incident) => void; + onAlertChanged?: (alert: Alert) => void; }) => { const [ - { incidentRespondersList, incidentResponder, isLoading }, - { setIsLoading, setIncidentResponder, setIncidentRespondersList }, - ] = useAssignIncident(incident, isModalOpened); - const callback = onIncidentChanged || ((_: Incident): void => {}); + { alertRespondersList, alertResponder, isLoading }, + { setIsLoading, setAlertResponder, setAlertRespondersList }, + ] = useAssignAlert(alert, isModalOpened); + const callback = onAlertChanged || ((_: Alert): void => {}); const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const classes = useStyles(); const handleClose = () => { - setIncidentRespondersList([]); + setAlertRespondersList([]); setIsModalOpened(false); }; const handleAssign = () => { - if (!incident || !incidentResponder) { + if (!alert || !alertResponder) { return; } setIsLoading(true); - setIncidentRespondersList([]); + setAlertRespondersList([]); setTimeout(async () => { try { - const newIncident = await ilertApi.assignIncident( - incident, - incidentResponder, - ); - callback(newIncident); - alertApi.post({ message: 'Incident assigned.' }); + const newAlert = await ilertApi.assignAlert(alert, alertResponder); + callback(newAlert); + alertApi.post({ message: 'Alert assigned.' }); } catch (err) { alertApi.post({ message: err, severity: 'error' }); } @@ -102,33 +99,33 @@ export const IncidentAssignModal = ({ }, 250); }; - const canAssign = !!incidentResponder; + const canAssign = !!alertResponder; return ( - + Select responder to assign - + - This action will assign the incident to the selected responder. + This action will assign the alert to the selected responder. - + { - setIncidentResponder(newValue); + setAlertResponder(newValue); }} autoHighlight groupBy={option => { diff --git a/plugins/ilert/src/components/Incident/IncidentLink.tsx b/plugins/ilert/src/components/Alert/AlertLink.tsx similarity index 80% rename from plugins/ilert/src/components/Incident/IncidentLink.tsx rename to plugins/ilert/src/components/Alert/AlertLink.tsx index 381a656b72..8c526899aa 100644 --- a/plugins/ilert/src/components/Incident/IncidentLink.tsx +++ b/plugins/ilert/src/components/Alert/AlertLink.tsx @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { Incident } from '../../types'; +import React from 'react'; import { ilertApiRef } from '../../api'; +import { Alert } from '../../types'; -import { useApi } from '@backstage/core-plugin-api'; import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; const useStyles = makeStyles({ link: { @@ -27,20 +27,17 @@ const useStyles = makeStyles({ }, }); -export const IncidentLink = ({ incident }: { incident: Incident | null }) => { +export const AlertLink = ({ alert }: { alert: Alert | null }) => { const ilertApi = useApi(ilertApiRef); const classes = useStyles(); - if (!incident) { + if (!alert) { return null; } return ( - - #{incident.id} + + #{alert.id} ); }; diff --git a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx b/plugins/ilert/src/components/Alert/AlertNewModal.tsx similarity index 92% rename from plugins/ilert/src/components/Incident/IncidentNewModal.tsx rename to plugins/ilert/src/components/Alert/AlertNewModal.tsx index 561047d45e..66928e07e3 100644 --- a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx +++ b/plugins/ilert/src/components/Alert/AlertNewModal.tsx @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; -import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; -import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Autocomplete from '@material-ui/lab/Autocomplete'; -import { useNewIncident } from '../../hooks/useNewIncident'; -import { Typography } from '@material-ui/core'; -import useMediaQuery from '@material-ui/core/useMediaQuery'; -import { ilertApiRef } from '../../api'; -import { AlertSource } from '../../types'; import { alertApiRef, identityApiRef, useApi, } from '@backstage/core-plugin-api'; +import { Typography } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; +import Alert from '@material-ui/lab/Alert'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { useNewAlert } from '../../hooks/useNewAlert'; +import { AlertSource } from '../../types'; const useStyles = makeStyles(() => ({ container: { @@ -61,23 +61,23 @@ const useStyles = makeStyles(() => ({ }, })); -export const IncidentNewModal = ({ +export const AlertNewModal = ({ isModalOpened, setIsModalOpened, - refetchIncidents, + refetchAlerts, initialAlertSource, entityName, }: { isModalOpened: boolean; setIsModalOpened: (open: boolean) => void; - refetchIncidents: () => void; + refetchAlerts: () => void; initialAlertSource?: AlertSource | null; entityName?: string; }) => { const [ { alertSources, alertSource, summary, details, isLoading }, { setAlertSource, setSummary, setDetails, setIsLoading }, - ] = useNewIncident(isModalOpened, initialAlertSource); + ] = useNewAlert(isModalOpened, initialAlertSource); const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); @@ -107,15 +107,15 @@ export const IncidentNewModal = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - await ilertApi.createIncident({ + await ilertApi.createAlert({ integrationKey, summary, details, userName, source, }); - alertApi.post({ message: 'Incident created.' }); - refetchIncidents(); + alertApi.post({ message: 'Alert created.' }); + refetchAlerts(); } catch (err) { alertApi.post({ message: err, severity: 'error' }); } @@ -129,16 +129,16 @@ export const IncidentNewModal = ({ - + {entityName ? (
- This action will trigger an incident for{' '} + This action will trigger an alert for{' '} "{entityName}".
) : ( - 'New incident' + 'New alert' )}
diff --git a/plugins/ilert/src/components/Incident/IncidentStatus.tsx b/plugins/ilert/src/components/Alert/AlertStatus.tsx similarity index 79% rename from plugins/ilert/src/components/Incident/IncidentStatus.tsx rename to plugins/ilert/src/components/Alert/AlertStatus.tsx index e6eb1525f7..9be81205dd 100644 --- a/plugins/ilert/src/components/Incident/IncidentStatus.tsx +++ b/plugins/ilert/src/components/Alert/AlertStatus.tsx @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import { StatusError, StatusOK } from '@backstage/core-components'; import { makeStyles } from '@material-ui/core/styles'; import Tooltip from '@material-ui/core/Tooltip'; -import { ACCEPTED, Incident, PENDING, RESOLVED } from '../../types'; -import { StatusError, StatusOK } from '@backstage/core-components'; +import React from 'react'; +import { ACCEPTED, Alert, PENDING, RESOLVED } from '../../types'; const useStyles = makeStyles({ denseListIcon: { @@ -29,19 +29,19 @@ const useStyles = makeStyles({ }, }); -export const incidentStatusLabels = { +export const alertStatusLabels = { [RESOLVED]: 'Resolved', [ACCEPTED]: 'Accepted', [PENDING]: 'Pending', } as Record; -export const IncidentStatus = ({ incident }: { incident: Incident }) => { +export const AlertStatus = ({ alert }: { alert: Alert }) => { const classes = useStyles(); return ( - +
- {incident.status === 'PENDING' ? : } + {alert.status === 'PENDING' ? : }
); diff --git a/plugins/ilert/src/components/IncidentsPage/index.ts b/plugins/ilert/src/components/Alert/index.ts similarity index 90% rename from plugins/ilert/src/components/IncidentsPage/index.ts rename to plugins/ilert/src/components/Alert/index.ts index a5ad4e65e6..a569777b21 100644 --- a/plugins/ilert/src/components/IncidentsPage/index.ts +++ b/plugins/ilert/src/components/Alert/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './IncidentsPage'; -export * from './IncidentsTable'; +export * from './AlertActionsMenu'; +export * from './AlertStatus'; diff --git a/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx b/plugins/ilert/src/components/AlertsPage/AlertsPage.tsx similarity index 71% rename from plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx rename to plugins/ilert/src/components/AlertsPage/AlertsPage.tsx index 3dc0907777..c7df9101a5 100644 --- a/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx +++ b/plugins/ilert/src/components/AlertsPage/AlertsPage.tsx @@ -13,37 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { AuthenticationError } from '@backstage/errors'; -import Button from '@material-ui/core/Button'; -import AddIcon from '@material-ui/icons/Add'; -import { IncidentsTable } from './IncidentsTable'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useIncidents } from '../../hooks/useIncidents'; -import { IncidentNewModal } from '../Incident/IncidentNewModal'; import { Content, ContentHeader, - SupportButton, ResponseErrorPanel, + SupportButton, } from '@backstage/core-components'; +import { AuthenticationError } from '@backstage/errors'; +import Button from '@material-ui/core/Button'; +import AddIcon from '@material-ui/icons/Add'; +import React from 'react'; +import { useAlerts } from '../../hooks/useAlerts'; +import { AlertNewModal } from '../Alert/AlertNewModal'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { AlertsTable } from './AlertsTable'; -export const IncidentsPage = () => { +export const AlertsPage = () => { const [ - { tableState, states, incidents, incidentsCount, isLoading, error }, + { tableState, states, alerts, alertsCount, isLoading, error }, { - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, setIsLoading, }, - ] = useIncidents(true); + ] = useAlerts(true); const [isModalOpened, setIsModalOpened] = React.useState(false); - const handleCreateNewIncidentClick = () => { + const handleCreateNewAlertClick = () => { setIsModalOpened(true); }; @@ -65,32 +65,32 @@ export const IncidentsPage = () => { return ( - + - This helps you to bring iLert into your developer portal. - ({ }, })); -export const IncidentsTable = ({ - incidents, - incidentsCount, +export const AlertsTable = ({ + alerts, + alertsCount, tableState, states, isLoading, - onIncidentChanged, + onAlertChanged, setIsLoading, - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, compact, }: { - incidents: Incident[]; - incidentsCount: number; + alerts: Alert[]; + alertsCount: number; tableState: TableState; - states: IncidentStatus[]; + states: AlertStatus[]; isLoading: boolean; - onIncidentChanged: (incident: Incident) => void; + onAlertChanged: (alert: Alert) => void; setIsLoading: (isLoading: boolean) => void; - onIncidentStatesChange: (states: IncidentStatus[]) => void; + onAlertStatesChange: (states: AlertStatus[]) => void; onChangePage: (page: number) => void; onChangeRowsPerPage: (pageSize: number) => void; compact?: boolean; @@ -92,14 +92,14 @@ export const IncidentsTable = ({ highlight: true, cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; const summaryColumn: TableColumn = { title: 'Summary', field: 'summary', cellStyle: !compact ? xlColumnStyle : undefined, headerStyle: !compact ? xlColumnStyle : undefined, - render: rowData => {(rowData as Incident).summary}, + render: rowData => {(rowData as Alert).summary}, }; const sourceColumn: TableColumn = { title: 'Source', @@ -107,7 +107,7 @@ export const IncidentsTable = ({ cellStyle: mdColumnStyle, headerStyle: mdColumnStyle, render: rowData => ( - + ), }; const durationColumn: TableColumn = { @@ -118,10 +118,10 @@ export const IncidentsTable = ({ headerStyle: smColumnStyle, render: rowData => ( - {(rowData as Incident).status !== 'RESOLVED' + {(rowData as Alert).status !== 'RESOLVED' ? humanizeDuration( Interval.fromDateTimes( - dt.fromISO((rowData as Incident).reportTime), + dt.fromISO((rowData as Alert).reportTime), dt.now(), ) .toDuration() @@ -130,8 +130,8 @@ export const IncidentsTable = ({ ) : humanizeDuration( Interval.fromDateTimes( - dt.fromISO((rowData as Incident).reportTime), - dt.fromISO((rowData as Incident).resolvedOn), + dt.fromISO((rowData as Alert).reportTime), + dt.fromISO((rowData as Alert).resolvedOn), ) .toDuration() .valueOf(), @@ -147,7 +147,7 @@ export const IncidentsTable = ({ headerStyle: !compact ? mdColumnStyle : lgColumnStyle, render: rowData => ( - {ilertApi.getUserInitials((rowData as Incident).assignedTo)} + {ilertApi.getUserInitials((rowData as Alert).assignedTo)} ), }; @@ -158,7 +158,7 @@ export const IncidentsTable = ({ headerStyle: smColumnStyle, render: rowData => ( - {(rowData as Incident).priority === 'HIGH' ? 'High' : 'Low'} + {(rowData as Alert).priority === 'HIGH' ? 'High' : 'Low'} ), }; @@ -167,7 +167,7 @@ export const IncidentsTable = ({ field: 'status', cellStyle: xsColumnStyle, headerStyle: xsColumnStyle, - render: rowData => , + render: rowData => , }; const actionsColumn: TableColumn = { title: '', @@ -175,9 +175,9 @@ export const IncidentsTable = ({ cellStyle: xsColumnStyle, headerStyle: xsColumnStyle, render: rowData => ( - ), @@ -236,14 +236,14 @@ export const IncidentsTable = ({ }} emptyContent={ - No incidents right now + No alerts right now } title={ !compact ? ( ) : ( @@ -252,12 +252,12 @@ export const IncidentsTable = ({ ) } page={tableState.page} - totalCount={incidentsCount} + totalCount={alertsCount} onPageChange={onChangePage} onRowsPerPageChange={onChangeRowsPerPage} // localization={{ header: { actions: undefined } }} columns={columns} - data={incidents} + data={alerts} isLoading={isLoading} /> ); diff --git a/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx b/plugins/ilert/src/components/AlertsPage/StatusChip.tsx similarity index 82% rename from plugins/ilert/src/components/IncidentsPage/StatusChip.tsx rename to plugins/ilert/src/components/AlertsPage/StatusChip.tsx index 98521234de..833e903b32 100644 --- a/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx +++ b/plugins/ilert/src/components/AlertsPage/StatusChip.tsx @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { Chip, withStyles } from '@material-ui/core'; -import { Incident, PENDING, ACCEPTED, RESOLVED } from '../../types'; -import { incidentStatusLabels } from '../Incident/IncidentStatus'; +import React from 'react'; +import { ACCEPTED, Alert, PENDING, RESOLVED } from '../../types'; +import { alertStatusLabels } from '../Alert/AlertStatus'; const ResolvedChip = withStyles({ root: { @@ -41,10 +41,10 @@ const PendingChip = withStyles({ }, })(Chip); -export const StatusChip = ({ incident }: { incident: Incident }) => { - const label = `${incidentStatusLabels[incident.status]}`; +export const StatusChip = ({ alert }: { alert: Alert }) => { + const label = `${alertStatusLabels[alert.status]}`; - switch (incident.status) { + switch (alert.status) { case RESOLVED: return ; case ACCEPTED: diff --git a/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx similarity index 76% rename from plugins/ilert/src/components/IncidentsPage/TableTitle.tsx rename to plugins/ilert/src/components/AlertsPage/TableTitle.tsx index 8b8fde87f7..c610e91d94 100644 --- a/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx +++ b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { PENDING, ACCEPTED, RESOLVED, IncidentStatus } from '../../types'; -import { incidentStatusLabels } from '../Incident/IncidentStatus'; +import Checkbox from '@material-ui/core/Checkbox'; import FormControl from '@material-ui/core/FormControl'; import ListItemText from '@material-ui/core/ListItemText'; -import Select from '@material-ui/core/Select'; -import Typography from '@material-ui/core/Typography'; import MenuItem from '@material-ui/core/MenuItem'; -import Checkbox from '@material-ui/core/Checkbox'; +import Select from '@material-ui/core/Select'; import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { ACCEPTED, AlertStatus, PENDING, RESOLVED } from '../../types'; +import { alertStatusLabels } from '../Alert/AlertStatus'; const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; @@ -53,15 +53,15 @@ const useStyles = makeStyles({ }); export const TableTitle = ({ - incidentStates, - onIncidentStatesChange, + alertStates, + onAlertStatesChange, }: { - incidentStates: IncidentStatus[]; - onIncidentStatesChange: (states: IncidentStatus[]) => void; + alertStates: AlertStatus[]; + onAlertStatesChange: (states: AlertStatus[]) => void; }) => { const classes = useStyles(); - const handleIncidentStatusSelectChange = (event: any) => { - onIncidentStatesChange(event.target.value); + const handleAlertStatusSelectChange = (event: any) => { + onAlertStatesChange(event.target.value); }; return ( @@ -75,19 +75,19 @@ export const TableTitle = ({ size="small" > diff --git a/plugins/ilert/src/components/Incident/index.ts b/plugins/ilert/src/components/AlertsPage/index.ts similarity index 89% rename from plugins/ilert/src/components/Incident/index.ts rename to plugins/ilert/src/components/AlertsPage/index.ts index 5065cb1de9..c42d558dca 100644 --- a/plugins/ilert/src/components/Incident/index.ts +++ b/plugins/ilert/src/components/AlertsPage/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './IncidentActionsMenu'; -export * from './IncidentStatus'; +export * from './AlertsPage'; +export * from './AlertsTable'; diff --git a/plugins/ilert/src/components/ILertCard/ILertCard.tsx b/plugins/ilert/src/components/ILertCard/ILertCard.tsx index 6330189a01..d124751b2b 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCard.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCard.tsx @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { ResponseErrorPanel } from '@backstage/core-components'; import { AuthenticationError } from '@backstage/errors'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import CardHeader from '@material-ui/core/CardHeader'; import Divider from '@material-ui/core/Divider'; import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; import { ILERT_INTEGRATION_KEY_ANNOTATION } from '../../constants'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useIncidents } from '../../hooks/useIncidents'; -import { IncidentsTable } from '../IncidentsPage'; -import { IncidentNewModal } from '../Incident/IncidentNewModal'; -import { ILertCardActionsHeader } from './ILertCardActionsHeader'; -import { useAlertSource } from '../../hooks/useAlertSource'; import { useILertEntity } from '../../hooks'; +import { useAlerts } from '../../hooks/useAlerts'; +import { useAlertSource } from '../../hooks/useAlertSource'; +import { AlertNewModal } from '../Alert/AlertNewModal'; +import { AlertsTable } from '../AlertsPage'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { ILertCardActionsHeader } from './ILertCardActionsHeader'; +import { ILertCardEmptyState } from './ILertCardEmptyState'; import { ILertCardHeaderStatus } from './ILertCardHeaderStatus'; import { ILertCardMaintenanceModal } from './ILertCardMaintenanceModal'; -import { ILertCardEmptyState } from './ILertCardEmptyState'; import { ILertCardOnCall } from './ILertCardOnCall'; -import { ResponseErrorPanel } from '@backstage/core-components'; /** @public */ export const isPluginApplicableToEntity = (entity: Entity) => @@ -60,18 +60,18 @@ export const ILertCard = () => { { setAlertSource, refetchAlertSource }, ] = useAlertSource(integrationKey); const [ - { tableState, states, incidents, incidentsCount, isLoading, error }, + { tableState, states, alerts, alertsCount, isLoading, error }, { - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, setIsLoading, }, - ] = useIncidents(false, true, alertSource); + ] = useAlerts(false, true, alertSource); - const [isNewIncidentModalOpened, setIsNewIncidentModalOpened] = + const [isNewAlertModalOpened, setIsNewAlertModalOpened] = React.useState(false); const [isMaintenanceModalOpened, setIsMaintenanceModalOpened] = React.useState(false); @@ -97,7 +97,7 @@ export const ILertCard = () => { @@ -107,13 +107,13 @@ export const ILertCard = () => { - { /> - diff --git a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx index 4e0b562369..49f2c789c4 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import Alert from '@material-ui/lab/Alert'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import Typography from '@material-ui/core/Typography'; import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import BuildIcon from '@material-ui/icons/Build'; import PauseIcon from '@material-ui/icons/Pause'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; import TimelineIcon from '@material-ui/icons/Timeline'; import WebIcon from '@material-ui/icons/Web'; -import Typography from '@material-ui/core/Typography'; +import Alert from '@material-ui/lab/Alert'; +import React from 'react'; import { ilertApiRef } from '../../api'; import { AlertSource, UptimeMonitor } from '../../types'; @@ -34,18 +34,18 @@ import { HeaderIconLinkRow, IconLinkVerticalProps, } from '@backstage/core-components'; -import { useApi, alertApiRef } from '@backstage/core-plugin-api'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; export const ILertCardActionsHeader = ({ alertSource, setAlertSource, - setIsNewIncidentModalOpened, + setIsNewAlertModalOpened, setIsMaintenanceModalOpened, uptimeMonitor, }: { alertSource: AlertSource | null; setAlertSource: (alertSource: AlertSource) => void; - setIsNewIncidentModalOpened: (isOpen: boolean) => void; + setIsNewAlertModalOpened: (isOpen: boolean) => void; setIsMaintenanceModalOpened: (isOpen: boolean) => void; uptimeMonitor: UptimeMonitor | null; }) => { @@ -54,8 +54,8 @@ export const ILertCardActionsHeader = ({ const [isLoading, setIsLoading] = React.useState(false); const [isDisableModalOpened, setIsDisableModalOpened] = React.useState(false); - const handleCreateNewIncident = () => { - setIsNewIncidentModalOpened(true); + const handleCreateNewAlert = () => { + setIsNewAlertModalOpened(true); }; const handleEnableAlertSource = async () => { @@ -108,9 +108,9 @@ export const ILertCardActionsHeader = ({ icon: , }; - const createIncidentLink: IconLinkVerticalProps = { - label: 'Create Incident', - onClick: handleCreateNewIncident, + const createAlertLink: IconLinkVerticalProps = { + label: 'Create Alert', + onClick: handleCreateNewAlert, icon: , color: 'secondary', disabled: @@ -149,7 +149,7 @@ export const ILertCardActionsHeader = ({ const links: IconLinkVerticalProps[] = [ alertSourceLink, - createIncidentLink, + createAlertLink, alertSource && alertSource.active ? disableAlertSourceLink : enableAlertSourceLink, @@ -178,7 +178,7 @@ export const ILertCardActionsHeader = ({ Do you really want to disable this alert source? A disabled alert - source cannot create new incidents. + source cannot create new alerts.
diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx index 89dae215e4..8dfed11690 100644 --- a/plugins/ilert/src/components/ILertPage/ILertPage.tsx +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -13,24 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { IncidentsPage } from '../IncidentsPage'; -import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; -import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; import { - Page, - Header, - HeaderTabs, - HeaderLabel, Content, + Header, + HeaderLabel, + HeaderTabs, + Page, } from '@backstage/core-components'; +import React from 'react'; +import { AlertsPage } from '../AlertsPage'; +import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; +import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; /** @public */ export const ILertPage = () => { const [selectedTab, setSelectedTab] = React.useState(0); const tabs = [ { label: 'Who is on call?' }, - { label: 'Incidents' }, + { label: 'Alerts' }, { label: 'Uptime Monitors' }, ]; const renderTab = () => { @@ -38,7 +38,7 @@ export const ILertPage = () => { case 0: return ; case 1: - return ; + return ; case 2: return ; default: diff --git a/plugins/ilert/src/hooks/useIncidentActions.ts b/plugins/ilert/src/hooks/useAlertActions.ts similarity index 69% rename from plugins/ilert/src/hooks/useIncidentActions.ts rename to plugins/ilert/src/hooks/useAlertActions.ts index 75621f67c3..875a8099f3 100644 --- a/plugins/ilert/src/hooks/useIncidentActions.ts +++ b/plugins/ilert/src/hooks/useAlertActions.ts @@ -13,48 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Incident, IncidentAction } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { Alert, AlertAction } from '../types'; -export const useIncidentActions = ( - incident: Incident | null, - open: boolean, -) => { +export const useAlertActions = (alert: Alert | null, open: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); - const [incidentActionsList, setIncidentActionsList] = React.useState< - IncidentAction[] - >([]); + const [alertActionsList, setAlertActionsList] = React.useState( + [], + ); const [isLoading, setIsLoading] = React.useState(false); const { error, retry } = useAsyncRetry(async () => { try { - if (!incident || !open) { + if (!alert || !open) { return; } - const data = await ilertApi.fetchIncidentActions(incident); - setIncidentActionsList(data); + const data = await ilertApi.fetchAlertActions(alert); + setAlertActionsList(data); } catch (e) { if (!(e instanceof AuthenticationError)) { errorApi.post(e); } throw e; } - }, [incident, open]); + }, [alert, open]); return [ { - incidentActions: incidentActionsList, + alertActions: alertActionsList, error, isLoading, }, { - setIncidentActionsList, + setAlertActionsList, setIsLoading, retry, }, diff --git a/plugins/ilert/src/hooks/useAlertSource.ts b/plugins/ilert/src/hooks/useAlertSource.ts index 279150a724..c80b3d59eb 100644 --- a/plugins/ilert/src/hooks/useAlertSource.ts +++ b/plugins/ilert/src/hooks/useAlertSource.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { ilertApiRef } from '../api'; import { AlertSource, UptimeMonitor } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; export const useAlertSource = (integrationKey: string) => { const ilertApi = useApi(ilertApiRef); diff --git a/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts index 5a804e37fd..2884e890db 100644 --- a/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts +++ b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { ilertApiRef } from '../api'; import { AlertSource, OnCall } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; export const useAlertSourceOnCalls = (alertSource?: AlertSource | null) => { const ilertApi = useApi(ilertApiRef); diff --git a/plugins/ilert/src/hooks/useIncidents.ts b/plugins/ilert/src/hooks/useAlerts.ts similarity index 57% rename from plugins/ilert/src/hooks/useIncidents.ts rename to plugins/ilert/src/hooks/useAlerts.ts index 45f28937c5..8acabe0086 100644 --- a/plugins/ilert/src/hooks/useIncidents.ts +++ b/plugins/ilert/src/hooks/useAlerts.ts @@ -13,20 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { GetIncidentsOpts, ilertApiRef, TableState } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { - ACCEPTED, - PENDING, - Incident, - IncidentStatus, - AlertSource, -} from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { GetAlertsOpts, ilertApiRef, TableState } from '../api'; +import { ACCEPTED, Alert, AlertSource, AlertStatus, PENDING } from '../types'; -export const useIncidents = ( +export const useAlerts = ( paging: boolean, singleSource?: boolean, alertSource?: AlertSource | null, @@ -38,21 +32,21 @@ export const useIncidents = ( page: 0, pageSize: 10, }); - const [states, setStates] = React.useState([ + const [states, setStates] = React.useState([ ACCEPTED, PENDING, ]); - const [incidentsList, setIncidentsList] = React.useState([]); - const [incidentsCount, setIncidentsCount] = React.useState(0); + const [alertsList, setAlertsList] = React.useState([]); + const [alertsCount, setAlertsCount] = React.useState(0); const [isLoading, setIsLoading] = React.useState(false); - const fetchIncidentsCall = async () => { + const fetchAlertsCall = async () => { try { if (singleSource && !alertSource) { return; } setIsLoading(true); - const opts: GetIncidentsOpts = { + const opts: GetAlertsOpts = { states, alertSources: alertSource ? [alertSource.id] : [], }; @@ -60,8 +54,8 @@ export const useIncidents = ( opts.maxResults = tableState.pageSize; opts.startIndex = tableState.page * tableState.pageSize; } - const data = await ilertApi.fetchIncidents(opts); - setIncidentsList(data || []); + const data = await ilertApi.fetchAlerts(opts); + setAlertsList(data || []); setIsLoading(false); } catch (e) { if (!(e instanceof AuthenticationError)) { @@ -72,10 +66,10 @@ export const useIncidents = ( } }; - const fetchIncidentsCountCall = async () => { + const fetchAlertsCountCall = async () => { try { - const count = await ilertApi.fetchIncidentsCount({ states }); - setIncidentsCount(count || 0); + const count = await ilertApi.fetchAlertsCount({ states }); + setAlertsCount(count || 0); } catch (e) { if (!(e instanceof AuthenticationError)) { errorApi.post(e); @@ -83,44 +77,44 @@ export const useIncidents = ( throw e; } }; - const fetchIncidents = useAsyncRetry(fetchIncidentsCall, [ + const fetchAlerts = useAsyncRetry(fetchAlertsCall, [ tableState, states, singleSource, alertSource, ]); - const refetchIncidents = () => { + const refetchAlerts = () => { setTableState({ ...tableState, page: 0 }); - Promise.all([fetchIncidentsCall(), fetchIncidentsCountCall()]); + Promise.all([fetchAlertsCall(), fetchAlertsCountCall()]); }; - const fetchIncidentsCount = useAsyncRetry(fetchIncidentsCountCall, [states]); + const fetchAlertsCount = useAsyncRetry(fetchAlertsCountCall, [states]); - const error = fetchIncidents.error || fetchIncidentsCount.error; + const error = fetchAlerts.error || fetchAlertsCount.error; const retry = () => { - fetchIncidents.retry(); - fetchIncidentsCount.retry(); + fetchAlerts.retry(); + fetchAlertsCount.retry(); }; - const onIncidentChanged = (newIncident: Incident) => { - let shouldRefetchIncidents = false; - setIncidentsList( - incidentsList.reduce((acc: Incident[], incident: Incident) => { - if (newIncident.id === incident.id) { - if (states.includes(newIncident.status)) { - acc.push(newIncident); + const onAlertChanged = (newAlert: Alert) => { + let shouldRefetchAlerts = false; + setAlertsList( + alertsList.reduce((acc: Alert[], alert: Alert) => { + if (newAlert.id === alert.id) { + if (states.includes(newAlert.status)) { + acc.push(newAlert); } else { - shouldRefetchIncidents = true; + shouldRefetchAlerts = true; } return acc; } - acc.push(incident); + acc.push(alert); return acc; }, []), ); - if (shouldRefetchIncidents) { - refetchIncidents(); + if (shouldRefetchAlerts) { + refetchAlerts(); } }; @@ -130,7 +124,7 @@ export const useIncidents = ( const onChangeRowsPerPage = (p: number) => { setTableState({ ...tableState, pageSize: p }); }; - const onIncidentStatesChange = (s: IncidentStatus[]) => { + const onAlertStatesChange = (s: AlertStatus[]) => { setStates(s); }; @@ -138,22 +132,22 @@ export const useIncidents = ( { tableState, states, - incidents: incidentsList, - incidentsCount, + alerts: alertsList, + alertsCount, error, isLoading, }, { setTableState, setStates, - setIncidentsList, + setAlertsList, setIsLoading, retry, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, onChangePage, onChangeRowsPerPage, - onIncidentStatesChange, + onAlertStatesChange, }, ] as const; }; diff --git a/plugins/ilert/src/hooks/useAssignIncident.ts b/plugins/ilert/src/hooks/useAssignAlert.ts similarity index 68% rename from plugins/ilert/src/hooks/useAssignIncident.ts rename to plugins/ilert/src/hooks/useAssignAlert.ts index b760f31e9c..f81eae724f 100644 --- a/plugins/ilert/src/hooks/useAssignIncident.ts +++ b/plugins/ilert/src/hooks/useAssignAlert.ts @@ -13,30 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Incident, IncidentResponder } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { Alert, AlertResponder } from '../types'; -export const useAssignIncident = (incident: Incident | null, open: boolean) => { +export const useAssignAlert = (alert: Alert | null, open: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); - const [incidentRespondersList, setIncidentRespondersList] = React.useState< - IncidentResponder[] + const [alertRespondersList, setAlertRespondersList] = React.useState< + AlertResponder[] >([]); - const [incidentResponder, setIncidentResponder] = - React.useState(null); + const [alertResponder, setAlertResponder] = + React.useState(null); const [isLoading, setIsLoading] = React.useState(false); const { error, retry } = useAsyncRetry(async () => { try { - if (!incident || !open) { + if (!alert || !open) { return; } - const data = await ilertApi.fetchIncidentResponders(incident); + const data = await ilertApi.fetchAlertResponders(alert); if (data && Array.isArray(data)) { const groups = [ 'SUGGESTED', @@ -45,7 +45,7 @@ export const useAssignIncident = (incident: Incident | null, open: boolean) => { 'ON_CALL_SCHEDULE', ]; data.sort((a, b) => groups.indexOf(a.group) - groups.indexOf(b.group)); - setIncidentRespondersList(data); + setAlertRespondersList(data); } } catch (e) { if (!(e instanceof AuthenticationError)) { @@ -53,18 +53,18 @@ export const useAssignIncident = (incident: Incident | null, open: boolean) => { } throw e; } - }, [incident, open]); + }, [alert, open]); return [ { - incidentRespondersList, - incidentResponder, + alertRespondersList, + alertResponder, error, isLoading, }, { - setIncidentRespondersList, - setIncidentResponder, + setAlertRespondersList, + setAlertResponder, setIsLoading, retry, }, diff --git a/plugins/ilert/src/hooks/useNewIncident.ts b/plugins/ilert/src/hooks/useNewAlert.ts similarity index 95% rename from plugins/ilert/src/hooks/useNewIncident.ts rename to plugins/ilert/src/hooks/useNewAlert.ts index f2bb3a8cd3..a560c1b035 100644 --- a/plugins/ilert/src/hooks/useNewIncident.ts +++ b/plugins/ilert/src/hooks/useNewAlert.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { ilertApiRef } from '../api'; import { AlertSource } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; -export const useNewIncident = ( +export const useNewAlert = ( open: boolean, initialAlertSource?: AlertSource | null, ) => { diff --git a/plugins/ilert/src/types.ts b/plugins/ilert/src/types.ts index 3d1531329a..3e5ecf94c4 100644 --- a/plugins/ilert/src/types.ts +++ b/plugins/ilert/src/types.ts @@ -15,15 +15,15 @@ */ /** @public */ -export interface Incident { +export interface Alert { id: number; summary: string; details: string; reportTime: string; resolvedOn: string; - status: IncidentStatus; - priority: IncidentPriority; - incidentKey: string; + status: AlertStatus; + priority: AlertPriority; + alertKey: string; alertSource: AlertSource | null; assignedTo: User | null; logEntries: LogEntry[]; @@ -42,10 +42,10 @@ export const ACCEPTED = 'ACCEPTED'; export const RESOLVED = 'RESOLVED'; /** @public */ -export type IncidentStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; +export type AlertStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; /** @public */ -export type IncidentPriority = 'HIGH' | 'LOW'; +export type AlertPriority = 'HIGH' | 'LOW'; /** @public */ export interface Link { @@ -76,7 +76,7 @@ export interface LogEntry { timestamp: string; logEntryType: string; text: string; - incidentId?: number; + alertId?: number; iconName?: string; iconClass?: string; filterTypes?: string[]; @@ -125,8 +125,8 @@ export interface AlertSource { iconUrl?: string; lightIconUrl?: string; darkIconUrl?: string; - incidentCreation?: AlertSourceIncidentCreation; - incidentPriorityRule?: AlertSourceIncidentPriorityRule; + alertCreation?: AlertSourceAlertCreation; + alertPriorityRule?: AlertSourceAlertPriorityRule; emailFiltered?: boolean; emailResolveFiltered?: boolean; active?: boolean; @@ -209,16 +209,16 @@ export type AlertSourceIntegrationType = | 'CORTEXXSOAR' | string; /** @public */ -export type AlertSourceIncidentCreation = - | 'ONE_INCIDENT_PER_EMAIL' - | 'ONE_INCIDENT_PER_EMAIL_SUBJECT' - | 'ONE_PENDING_INCIDENT_ALLOWED' - | 'ONE_OPEN_INCIDENT_ALLOWED' +export type AlertSourceAlertCreation = + | 'ONE_ALERT_PER_EMAIL' + | 'ONE_ALERT_PER_EMAIL_SUBJECT' + | 'ONE_PENDING_ALERT_ALLOWED' + | 'ONE_OPEN_ALERT_ALLOWED' | 'OPEN_RESOLVE_ON_EXTRACTION'; /** @public */ export type AlertSourceFilterOperator = 'AND' | 'OR'; /** @public */ -export type AlertSourceIncidentPriorityRule = +export type AlertSourceAlertPriorityRule = | 'HIGH' | 'LOW' | 'HIGH_DURING_SUPPORT_HOURS' @@ -251,7 +251,7 @@ export interface AlertSourceSupportDay { /** @public */ export interface AlertSourceSupportHours { timezone: AlertSourceTimeZone; - autoRaiseIncidents: boolean; + autoRaiseAlerts: boolean; supportDays: { MONDAY: AlertSourceSupportDay; TUESDAY: AlertSourceSupportDay; @@ -324,7 +324,7 @@ export interface UptimeMonitor { checkParams: UptimeMonitorCheckParams; intervalSec: number; timeoutMs: number; - createIncidentAfterFailedChecks: number; + createAlertAfterFailedChecks: number; paused: boolean; embedUrl: string; shareUrl: string; @@ -342,7 +342,7 @@ export interface UptimeMonitorCheckParams { } /** @public */ -export interface IncidentResponder { +export interface AlertResponder { group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; id: number; name: string; @@ -350,19 +350,19 @@ export interface IncidentResponder { } /** @public */ -export interface IncidentAction { +export interface AlertAction { name: string; type: string; webhookId: string; extensionId?: string; - history?: IncidentActionHistory[]; + history?: AlertActionHistory[]; } /** @public */ -export interface IncidentActionHistory { +export interface AlertActionHistory { id: string; webhookId: string; - incidentId: number; + alertId: number; actor: User; success: boolean; } From f41d2d69a6ae886a0ff19c51f0b878bace42a3e3 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Thu, 29 Sep 2022 12:30:33 +0200 Subject: [PATCH 032/221] remove username from user display Signed-off-by: Marko Simon --- plugins/ilert/src/api/client.ts | 2 +- .../components/OnCallSchedulesPage/OnCallShiftItem.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts index 2fa6f38c6e..9b8a9f636e 100644 --- a/plugins/ilert/src/api/client.ts +++ b/plugins/ilert/src/api/client.ts @@ -521,7 +521,7 @@ export class ILertClient implements ILertApi { if (!user.firstName && !user.lastName) { return user.username; } - return `${user.firstName} ${user.lastName} (${user.username})`; + return `${user.firstName} ${user.lastName}`; } private async apiUrl() { diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx index e0fa82fe85..f9c28430d8 100644 --- a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx +++ b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx @@ -13,14 +13,14 @@ * 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 from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import RepeatIcon from '@material-ui/icons/Repeat'; -import { Shift } from '../../types'; import { DateTime as dt } from 'luxon'; -import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { Shift } from '../../types'; import { ShiftOverrideModal } from '../Shift/ShiftOverrideModal'; const useStyles = makeStyles({ @@ -71,7 +71,7 @@ export const OnCallShiftItem = ({ {shift && shift.user ? ( - {`${shift.user.firstName} ${shift.user.lastName} (${shift.user.username})`} + {`${shift.user.firstName} ${shift.user.lastName}`} ) : null} From bfd8e9282dc439cf405cc76b4abad48b5660dcf7 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Mon, 3 Oct 2022 13:26:30 +0200 Subject: [PATCH 033/221] add services Signed-off-by: Marko Simon --- plugins/ilert/src/api/client.ts | 50 ++++- plugins/ilert/src/api/index.ts | 3 +- plugins/ilert/src/api/types.ts | 16 ++ .../src/components/AlertsPage/AlertsTable.tsx | 21 ++- .../src/components/ILertPage/ILertPage.tsx | 4 + .../components/Service/ServiceActionsMenu.tsx | 68 +++++++ .../src/components/Service/ServiceLink.tsx | 43 +++++ .../components/Service/ServiceNewModal.tsx | 136 ++++++++++++++ .../src/components/Service/ServiceStatus.tsx | 57 ++++++ plugins/ilert/src/components/Service/index.ts | 17 ++ .../components/ServicesPage/ServicesPage.tsx | 90 +++++++++ .../components/ServicesPage/ServicesTable.tsx | 172 ++++++++++++++++++ .../components/ServicesPage/StatusChip.tsx | 82 +++++++++ .../components/ServicesPage/TableTitle.tsx | 97 ++++++++++ .../src/components/ServicesPage/index.ts | 17 ++ plugins/ilert/src/hooks/useNewService.ts | 32 ++++ plugins/ilert/src/hooks/useServices.ts | 91 +++++++++ plugins/ilert/src/types.ts | 46 +++++ 18 files changed, 1031 insertions(+), 11 deletions(-) create mode 100644 plugins/ilert/src/components/Service/ServiceActionsMenu.tsx create mode 100644 plugins/ilert/src/components/Service/ServiceLink.tsx create mode 100644 plugins/ilert/src/components/Service/ServiceNewModal.tsx create mode 100644 plugins/ilert/src/components/Service/ServiceStatus.tsx create mode 100644 plugins/ilert/src/components/Service/index.ts create mode 100644 plugins/ilert/src/components/ServicesPage/ServicesPage.tsx create mode 100644 plugins/ilert/src/components/ServicesPage/ServicesTable.tsx create mode 100644 plugins/ilert/src/components/ServicesPage/StatusChip.tsx create mode 100644 plugins/ilert/src/components/ServicesPage/TableTitle.tsx create mode 100644 plugins/ilert/src/components/ServicesPage/index.ts create mode 100644 plugins/ilert/src/hooks/useNewService.ts create mode 100644 plugins/ilert/src/hooks/useServices.ts diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts index 9b8a9f636e..4269ef583d 100644 --- a/plugins/ilert/src/api/client.ts +++ b/plugins/ilert/src/api/client.ts @@ -28,6 +28,7 @@ import { EscalationPolicy, OnCall, Schedule, + Service, UptimeMonitor, User, } from '../types'; @@ -35,7 +36,9 @@ import { EventRequest, GetAlertsCountOpts, GetAlertsOpts, + GetServicesOpts, ILertApi, + ServiceRequest, } from './types'; /** @public */ @@ -127,7 +130,6 @@ export class ILertClient implements ILertApi { }); } const response = await this.fetch(`/api/alerts?${query.toString()}`, init); - return response; } @@ -444,7 +446,10 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/schedules', init); + const response = await this.fetch( + '/api/schedules?include=currentShift&include=nextShift', + init, + ); return response; } @@ -479,6 +484,41 @@ export class ILertClient implements ILertApi { return response; } + async fetchServices(opts?: GetServicesOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts?.maxResults !== undefined) { + query.append('max-results', String(opts.maxResults)); + } + if (opts?.startIndex !== undefined) { + query.append('start-index', String(opts.startIndex)); + } + + query.append('include', 'uptime'); + + const response = await this.fetch( + `/api/services?${query.toString()}`, + init, + ); + return response; + } + + async createService(serviceRequest: ServiceRequest): Promise { + const init = { + method: 'POST', + headers: JSON_HEADERS, + body: JSON.stringify({ + // apiKey: eventRequest.integrationKey, + name: serviceRequest.name, + }), + }; + + const response = await this.fetch('/api/services', init); + return response; + } + getAlertDetailsURL(alert: Alert): string { return `${this.baseUrl}/alert/view.jsf?id=${encodeURIComponent(alert.id)}`; } @@ -510,6 +550,12 @@ export class ILertClient implements ILertApi { )}`; } + getServiceDetailsURL(service: Service): string { + return `${this.baseUrl}/service/view.jsf?id=${encodeURIComponent( + service.id, + )}`; + } + getUserPhoneNumber(user: User | null) { return user?.mobile?.number || user?.landline?.number || ''; } diff --git a/plugins/ilert/src/api/index.ts b/plugins/ilert/src/api/index.ts index b33a702a5c..f0688e0f63 100644 --- a/plugins/ilert/src/api/index.ts +++ b/plugins/ilert/src/api/index.ts @@ -16,9 +16,10 @@ export { ilertApiRef, ILertClient } from './client'; export type { - EventRequest, + AlertEventRequest as EventRequest, GetAlertsCountOpts, GetAlertsOpts, + GetServicesOpts, ILertApi, TableState, } from './types'; diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index b7af3d845d..39eb373ae2 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -23,6 +23,7 @@ import { EscalationPolicy, OnCall, Schedule, + Service, UptimeMonitor, User, } from '../types'; @@ -46,6 +47,12 @@ export type GetAlertsCountOpts = { states?: AlertStatus[]; }; +/** @public */ +export type GetServicesOpts = { + maxResults?: number; + startIndex?: number; +}; + /** @public */ export type EventRequest = { integrationKey: string; @@ -55,6 +62,11 @@ export type EventRequest = { source: string; }; +/** @public */ +export type ServiceRequest = { + name: string; +}; + /** @public */ export interface ILertApi { fetchAlerts(opts?: GetAlertsOpts): Promise; @@ -94,11 +106,15 @@ export interface ILertApi { end: string, ): Promise; + fetchServices(opts?: GetServicesOpts): Promise; + createService(eventRequest: ServiceRequest): Promise; + getAlertDetailsURL(alert: Alert): string; getAlertSourceDetailsURL(alertSource: AlertSource | null): string; getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; getScheduleDetailsURL(schedule: Schedule): string; + getServiceDetailsURL(service: Service): string; getUserPhoneNumber(user: User | null): string; getUserInitials(user: User | null): string; } diff --git a/plugins/ilert/src/components/AlertsPage/AlertsTable.tsx b/plugins/ilert/src/components/AlertsPage/AlertsTable.tsx index cb3a6b886a..38ef4f07b1 100644 --- a/plugins/ilert/src/components/AlertsPage/AlertsTable.tsx +++ b/plugins/ilert/src/components/AlertsPage/AlertsTable.tsx @@ -140,14 +140,19 @@ export const AlertsTable = ({ ), }; - const assignedToColumn: TableColumn = { - title: 'Assigned to', - field: 'assignedTo', + const respondersColumn: TableColumn = { + title: 'Responders', + field: 'responders', cellStyle: !compact ? mdColumnStyle : lgColumnStyle, headerStyle: !compact ? mdColumnStyle : lgColumnStyle, render: rowData => ( - - {ilertApi.getUserInitials((rowData as Alert).assignedTo)} + + {(rowData as Alert).responders.map((value, i, arr) => { + return ( + ilertApi.getUserInitials(value.user) + + (arr.length - 1 !== i ? ', ' : '') + ); + })} ), }; @@ -187,7 +192,7 @@ export const AlertsTable = ({ ? [ summaryColumn, durationColumn, - assignedToColumn, + respondersColumn, statusColumn, actionsColumn, ] @@ -196,7 +201,7 @@ export const AlertsTable = ({ summaryColumn, sourceColumn, durationColumn, - assignedToColumn, + respondersColumn, priorityColumn, statusColumn, actionsColumn, @@ -247,7 +252,7 @@ export const AlertsTable = ({ /> ) : ( - INCIDENTS + ALERTS ) } diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx index 8dfed11690..cc7527c316 100644 --- a/plugins/ilert/src/components/ILertPage/ILertPage.tsx +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -23,6 +23,7 @@ import { import React from 'react'; import { AlertsPage } from '../AlertsPage'; import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; +import { ServicesPage } from '../ServicesPage'; import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; /** @public */ @@ -32,6 +33,7 @@ export const ILertPage = () => { { label: 'Who is on call?' }, { label: 'Alerts' }, { label: 'Uptime Monitors' }, + { label: 'Services' }, ]; const renderTab = () => { switch (selectedTab) { @@ -41,6 +43,8 @@ export const ILertPage = () => { return ; case 2: return ; + case 3: + return ; default: return null; } diff --git a/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx b/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx new file mode 100644 index 0000000000..096cc391ee --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Service } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const ServiceActionsMenu = ({ service }: { service: Service }) => { + const ilertApi = useApi(ilertApiRef); + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + View in iLert + + + + + + ); +}; diff --git a/plugins/ilert/src/components/Service/ServiceLink.tsx b/plugins/ilert/src/components/Service/ServiceLink.tsx new file mode 100644 index 0000000000..f8b1e88531 --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceLink.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Service } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +const useStyles = makeStyles({ + link: { + lineHeight: '22px', + }, +}); + +export const ServiceLink = ({ service }: { service: Service | null }) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (!service) { + return null; + } + + return ( + + #{service.id} + + ); +}; diff --git a/plugins/ilert/src/components/Service/ServiceNewModal.tsx b/plugins/ilert/src/components/Service/ServiceNewModal.tsx new file mode 100644 index 0000000000..64823eaf46 --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceNewModal.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { useNewService } from '../../hooks/useNewService'; + +const useStyles = makeStyles(() => ({ + container: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + minWidth: 120, + width: '100%', + }, + option: { + fontSize: 15, + '& > span': { + marginRight: 10, + fontSize: 18, + }, + }, + optionWrapper: { + display: 'flex', + width: '100%', + }, + sourceImage: { + height: 22, + paddingRight: 4, + }, +})); + +export const ServiceNewModal = ({ + isModalOpened, + setIsModalOpened, + refetchServices, +}: { + isModalOpened: boolean; + setIsModalOpened: (open: boolean) => void; + refetchServices: () => void; +}) => { + const [{ name, isLoading }, { setName, setIsLoading }] = useNewService(); + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const classes = useStyles(); + + const handleClose = () => { + setIsModalOpened(false); + }; + + const handleCreate = () => { + setIsLoading(true); + setTimeout(async () => { + try { + await ilertApi.createService({ + name, + }); + alertApi.post({ message: 'Service created.' }); + refetchServices(); + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + setIsModalOpened(false); + }, 250); + }; + + const canCreate = !!name; + + return ( + + 'New alert' + + {/* + + Please describe the problem you want to report. Be as descriptive as + possible. Your signed in user and a reference to the current page + will automatically be amended to the alarm so that the receiver can + reach out to you if necessary. + + */} + { + setName(event.target.value); + }} + /> + + + + + + + ); +}; diff --git a/plugins/ilert/src/components/Service/ServiceStatus.tsx b/plugins/ilert/src/components/Service/ServiceStatus.tsx new file mode 100644 index 0000000000..05923058aa --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceStatus.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { StatusError, StatusOK } from '@backstage/core-components'; +import { makeStyles } from '@material-ui/core/styles'; +import Tooltip from '@material-ui/core/Tooltip'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + Service, + UNDER_MAINTENANCE, +} from '../../types'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, +}); + +export const serviceStatusLabels = { + [OPERATIONAL]: 'Operational', + [UNDER_MAINTENANCE]: 'Under maintenance', + [DEGRADED]: 'Degraded', + [PARTIAL_OUTAGE]: 'Partial outage', + [MAJOR_OUTAGE]: 'Major outage', +} as Record; + +export const ServiceStatus = ({ service }: { service: Service }) => { + const classes = useStyles(); + + return ( + +
+ {service.status === 'OPERATIONAL' ? : } +
+
+ ); +}; diff --git a/plugins/ilert/src/components/Service/index.ts b/plugins/ilert/src/components/Service/index.ts new file mode 100644 index 0000000000..5227f44573 --- /dev/null +++ b/plugins/ilert/src/components/Service/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './ServiceActionsMenu'; +export * from './ServiceStatus'; diff --git a/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx new file mode 100644 index 0000000000..73b2d92f3c --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx @@ -0,0 +1,90 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Content, + ContentHeader, + ResponseErrorPanel, + SupportButton, +} from '@backstage/core-components'; +import { AuthenticationError } from '@backstage/errors'; +import Button from '@material-ui/core/Button'; +import AddIcon from '@material-ui/icons/Add'; +import React from 'react'; +import { useServices } from '../../hooks/useServices'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { ServiceNewModal } from '../Service/ServiceNewModal'; +import { ServicesTable } from './ServicesTable'; + +export const ServicesPage = () => { + const [ + { tableState, services, isLoading, error }, + { onChangePage, onChangeRowsPerPage, refetchServices, setIsLoading }, + ] = useServices(true); + + const [isModalOpened, setIsModalOpened] = React.useState(false); + + const handleCreateNewServiceClick = () => { + setIsModalOpened(true); + }; + + if (error) { + if (error instanceof AuthenticationError) { + return ( + + + + ); + } + + return ( + + + + ); + } + + return ( + + + + + + This helps you to bring iLert into your developer portal. + + + + + ); +}; diff --git a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx new file mode 100644 index 0000000000..bfb20acd71 --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx @@ -0,0 +1,172 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { ilertApiRef, TableState } from '../../api'; +import { Service } from '../../types'; +import { StatusChip } from './StatusChip'; + +import { Table, TableColumn } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; +import { ServiceActionsMenu } from '../Service/ServiceActionsMenu'; +import { ServiceLink } from '../Service/ServiceLink'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const ServicesTable = ({ + services, + tableState, + isLoading, + onChangePage, + onChangeRowsPerPage, + compact, +}: { + services: Service[]; + tableState: TableState; + isLoading: boolean; + setIsLoading: (isLoading: boolean) => void; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + compact?: boolean; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + const xsColumnStyle = { + width: '5%', + maxWidth: '5%', + }; + const smColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + const mdColumnStyle = { + width: '15%', + maxWidth: '15%', + }; + const lgColumnStyle = { + width: '20%', + maxWidth: '20%', + }; + const xlColumnStyle = { + width: '30%', + maxWidth: '30%', + }; + + const idColumn: TableColumn = { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const nameColumn: TableColumn = { + title: 'Name', + field: 'name', + cellStyle: !compact ? xlColumnStyle : undefined, + headerStyle: !compact ? xlColumnStyle : undefined, + render: rowData => {(rowData as Service).name}, + }; + const statusColumn: TableColumn = { + title: 'Status', + field: 'status', + cellStyle: xsColumnStyle, + headerStyle: xsColumnStyle, + render: rowData => , + }; + const uptimeColumn: TableColumn = { + title: 'Uptime in the last 90 days', + field: 'uptimePercentage', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + + {(rowData as Service).uptime.uptimePercentage.p90} + + ), + }; + const actionsColumn: TableColumn = { + title: '', + field: '', + cellStyle: xsColumnStyle, + headerStyle: xsColumnStyle, + render: rowData => , + }; + + const columns: TableColumn[] = compact + ? [nameColumn, statusColumn, uptimeColumn, actionsColumn] + : [idColumn, nameColumn, statusColumn, uptimeColumn, actionsColumn]; + let tableStyle: React.CSSProperties = {}; + if (compact) { + tableStyle = { + width: '100%', + maxWidth: '100%', + minWidth: '0', + height: 'calc(100% - 10px)', + boxShadow: 'none !important', + borderRadius: 'none !important', + }; + } else { + tableStyle = { + width: '100%', + maxWidth: '100%', + }; + } + + return ( + + No services + + } + title={ + + SERVICES + + } + page={tableState.page} + onPageChange={onChangePage} + onRowsPerPageChange={onChangeRowsPerPage} + // localization={{ header: { actions: undefined } }} + columns={columns} + data={services} + isLoading={isLoading} + /> + ); +}; diff --git a/plugins/ilert/src/components/ServicesPage/StatusChip.tsx b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx new file mode 100644 index 0000000000..5eb31b8a94 --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + Service, + UNDER_MAINTENANCE, +} from '../../types'; +import { serviceStatusLabels } from '../Service/ServiceStatus'; + +const OperationalChip = withStyles({ + root: { + backgroundColor: '#4caf50', + color: 'white', + margin: 0, + }, +})(Chip); + +const UnderMaintenanceChip = withStyles({ + root: { + backgroundColor: '#ffb74d', + color: 'white', + margin: 0, + }, +})(Chip); +const DegradedChip = withStyles({ + root: { + backgroundColor: '#d32f2f', + color: 'white', + margin: 0, + }, +})(Chip); +const PartialOutageChip = withStyles({ + root: { + backgroundColor: '#d4a5bb', + color: 'white', + margin: 0, + }, +})(Chip); +const MajorOutageChip = withStyles({ + root: { + backgroundColor: '#28c548', + color: 'white', + margin: 0, + }, +})(Chip); + +export const StatusChip = ({ service }: { service: Service }) => { + const label = `${serviceStatusLabels[service.status]}`; + + switch (service.status) { + case OPERATIONAL: + return ; + case UNDER_MAINTENANCE: + return ; + case DEGRADED: + return ; + case PARTIAL_OUTAGE: + return ; + case MAJOR_OUTAGE: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/ServicesPage/TableTitle.tsx b/plugins/ilert/src/components/ServicesPage/TableTitle.tsx new file mode 100644 index 0000000000..c610e91d94 --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/TableTitle.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Checkbox from '@material-ui/core/Checkbox'; +import FormControl from '@material-ui/core/FormControl'; +import ListItemText from '@material-ui/core/ListItemText'; +import MenuItem from '@material-ui/core/MenuItem'; +import Select from '@material-ui/core/Select'; +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { ACCEPTED, AlertStatus, PENDING, RESOLVED } from '../../types'; +import { alertStatusLabels } from '../Alert/AlertStatus'; + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; +const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250, + }, + }, +}; + +const useStyles = makeStyles({ + root: { + display: 'flex', + }, + label: { + marginTop: 8, + marginRight: 4, + }, + formControl: { + minWidth: 120, + maxWidth: 300, + }, + grow: { + flexGrow: 1, + }, +}); + +export const TableTitle = ({ + alertStates, + onAlertStatesChange, +}: { + alertStates: AlertStatus[]; + onAlertStatesChange: (states: AlertStatus[]) => void; +}) => { + const classes = useStyles(); + const handleAlertStatusSelectChange = (event: any) => { + onAlertStatesChange(event.target.value); + }; + + return ( +
+ + Status: + + + + +
+ ); +}; diff --git a/plugins/ilert/src/components/ServicesPage/index.ts b/plugins/ilert/src/components/ServicesPage/index.ts new file mode 100644 index 0000000000..6c4eef8100 --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './ServicesPage'; +export * from './ServicesTable'; diff --git a/plugins/ilert/src/hooks/useNewService.ts b/plugins/ilert/src/hooks/useNewService.ts new file mode 100644 index 0000000000..7d50d29a39 --- /dev/null +++ b/plugins/ilert/src/hooks/useNewService.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; + +export const useNewService = () => { + const [name, setName] = React.useState(''); + const [isLoading, setIsLoading] = React.useState(false); + + return [ + { + name, + isLoading, + }, + { + setName, + setIsLoading, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useServices.ts b/plugins/ilert/src/hooks/useServices.ts new file mode 100644 index 0000000000..b6ea120f80 --- /dev/null +++ b/plugins/ilert/src/hooks/useServices.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { GetServicesOpts, ilertApiRef, TableState } from '../api'; +import { Service } from '../types'; + +export const useServices = (paging: boolean) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [tableState, setTableState] = React.useState({ + page: 0, + pageSize: 10, + }); + + const [servicesList, setServicesList] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchServicesCall = async () => { + try { + setIsLoading(true); + const opts: GetServicesOpts = {}; + if (paging) { + opts.maxResults = tableState.pageSize; + opts.startIndex = tableState.page * tableState.pageSize; + } + const data = await ilertApi.fetchServices(opts); + setServicesList(data || []); + setIsLoading(false); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + setIsLoading(false); + throw e; + } + }; + + const fetchServices = useAsyncRetry(fetchServicesCall, [tableState]); + + const refetchServices = () => { + setTableState({ ...tableState, page: 0 }); + Promise.all([fetchServicesCall()]); + }; + + const error = fetchServices.error; + const retry = () => { + fetchServices.retry(); + }; + + const onChangePage = (page: number) => { + setTableState({ ...tableState, page }); + }; + const onChangeRowsPerPage = (p: number) => { + setTableState({ ...tableState, pageSize: p }); + }; + + return [ + { + tableState, + services: servicesList, + isLoading, + error, + }, + { + setTableState, + setServicesList, + setIsLoading, + retry, + refetchServices, + onChangePage, + onChangeRowsPerPage, + }, + ] as const; +}; diff --git a/plugins/ilert/src/types.ts b/plugins/ilert/src/types.ts index 3e5ecf94c4..ebe5c1b39d 100644 --- a/plugins/ilert/src/types.ts +++ b/plugins/ilert/src/types.ts @@ -26,6 +26,7 @@ export interface Alert { alertKey: string; alertSource: AlertSource | null; assignedTo: User | null; + responders: Responder[]; logEntries: LogEntry[]; links: Link[]; images: Image[]; @@ -99,6 +100,13 @@ export interface User { department: string; } +/** @public */ +export interface Responder { + acceptedAt?: string; + status: string; + user: User; +} + /** @public */ export type UserRole = | 'USER' @@ -223,6 +231,26 @@ export type AlertSourceAlertPriorityRule = | 'LOW' | 'HIGH_DURING_SUPPORT_HOURS' | 'LOW_DURING_SUPPORT_HOURS'; + +/** @public */ +export const OPERATIONAL = 'OPERATIONAL'; +/** @public */ +export const UNDER_MAINTENANCE = 'UNDER_MAINTENANCE'; +/** @public */ +export const DEGRADED = 'DEGRADED'; +/** @public */ +export const PARTIAL_OUTAGE = 'PARTIAL_OUTAGE'; +/** @public */ +export const MAJOR_OUTAGE = 'MAJOR_OUTAGE'; + +/** @public */ +export type ServiceStatus = + | typeof OPERATIONAL + | typeof UNDER_MAINTENANCE + | typeof DEGRADED + | typeof PARTIAL_OUTAGE + | typeof MAJOR_OUTAGE; + /** @public */ export interface AlertSourceEmailPredicate { field: 'EMAIL_FROM' | 'EMAIL_SUBJECT' | 'EMAIL_BODY'; @@ -376,3 +404,21 @@ export interface OnCall { end: string; escalationLevel: number; } + +/** @public */ +export interface Service { + id: number; + name: string; + status: ServiceStatus; + uptime: Uptime; +} + +/** @public */ +export interface Uptime { + uptimePercentage: UptimePercentage; +} + +/** @public */ +export interface UptimePercentage { + p90: number; +} From 3ce7fb36d488b6263b65173cbf31634bb6e95ea5 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Mon, 3 Oct 2022 13:34:05 +0200 Subject: [PATCH 034/221] remove create service Signed-off-by: Marko Simon --- .../components/Service/ServiceNewModal.tsx | 136 ------------------ .../components/ServicesPage/ServicesPage.tsx | 25 +--- .../components/ServicesPage/ServicesTable.tsx | 36 +++-- 3 files changed, 18 insertions(+), 179 deletions(-) delete mode 100644 plugins/ilert/src/components/Service/ServiceNewModal.tsx diff --git a/plugins/ilert/src/components/Service/ServiceNewModal.tsx b/plugins/ilert/src/components/Service/ServiceNewModal.tsx deleted file mode 100644 index 64823eaf46..0000000000 --- a/plugins/ilert/src/components/Service/ServiceNewModal.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import { makeStyles } from '@material-ui/core/styles'; -import TextField from '@material-ui/core/TextField'; -import React from 'react'; -import { ilertApiRef } from '../../api'; -import { useNewService } from '../../hooks/useNewService'; - -const useStyles = makeStyles(() => ({ - container: { - display: 'flex', - flexWrap: 'wrap', - }, - formControl: { - minWidth: 120, - width: '100%', - }, - option: { - fontSize: 15, - '& > span': { - marginRight: 10, - fontSize: 18, - }, - }, - optionWrapper: { - display: 'flex', - width: '100%', - }, - sourceImage: { - height: 22, - paddingRight: 4, - }, -})); - -export const ServiceNewModal = ({ - isModalOpened, - setIsModalOpened, - refetchServices, -}: { - isModalOpened: boolean; - setIsModalOpened: (open: boolean) => void; - refetchServices: () => void; -}) => { - const [{ name, isLoading }, { setName, setIsLoading }] = useNewService(); - const ilertApi = useApi(ilertApiRef); - const alertApi = useApi(alertApiRef); - const classes = useStyles(); - - const handleClose = () => { - setIsModalOpened(false); - }; - - const handleCreate = () => { - setIsLoading(true); - setTimeout(async () => { - try { - await ilertApi.createService({ - name, - }); - alertApi.post({ message: 'Service created.' }); - refetchServices(); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - setIsModalOpened(false); - }, 250); - }; - - const canCreate = !!name; - - return ( - - 'New alert' - - {/* - - Please describe the problem you want to report. Be as descriptive as - possible. Your signed in user and a reference to the current page - will automatically be amended to the alarm so that the receiver can - reach out to you if necessary. - - */} - { - setName(event.target.value); - }} - /> - - - - - - - ); -}; diff --git a/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx index 73b2d92f3c..e3afd4cc1a 100644 --- a/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx +++ b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx @@ -20,26 +20,17 @@ import { SupportButton, } from '@backstage/core-components'; import { AuthenticationError } from '@backstage/errors'; -import Button from '@material-ui/core/Button'; -import AddIcon from '@material-ui/icons/Add'; import React from 'react'; import { useServices } from '../../hooks/useServices'; import { MissingAuthorizationHeaderError } from '../Errors'; -import { ServiceNewModal } from '../Service/ServiceNewModal'; import { ServicesTable } from './ServicesTable'; export const ServicesPage = () => { const [ { tableState, services, isLoading, error }, - { onChangePage, onChangeRowsPerPage, refetchServices, setIsLoading }, + { onChangePage, onChangeRowsPerPage, setIsLoading }, ] = useServices(true); - const [isModalOpened, setIsModalOpened] = React.useState(false); - - const handleCreateNewServiceClick = () => { - setIsModalOpened(true); - }; - if (error) { if (error instanceof AuthenticationError) { return ( @@ -59,20 +50,6 @@ export const ServicesPage = () => { return ( - - This helps you to bring iLert into your developer portal. diff --git a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx index bfb20acd71..4ed1d581ae 100644 --- a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx +++ b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx @@ -16,12 +16,11 @@ import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import React from 'react'; -import { ilertApiRef, TableState } from '../../api'; +import { TableState } from '../../api'; import { Service } from '../../types'; import { StatusChip } from './StatusChip'; import { Table, TableColumn } from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; import { ServiceActionsMenu } from '../Service/ServiceActionsMenu'; import { ServiceLink } from '../Service/ServiceLink'; @@ -49,25 +48,24 @@ export const ServicesTable = ({ onChangeRowsPerPage: (pageSize: number) => void; compact?: boolean; }) => { - const ilertApi = useApi(ilertApiRef); const classes = useStyles(); - const xsColumnStyle = { - width: '5%', - maxWidth: '5%', - }; + // const xsColumnStyle = { + // width: '5%', + // maxWidth: '5%', + // }; const smColumnStyle = { width: '10%', maxWidth: '10%', }; - const mdColumnStyle = { - width: '15%', - maxWidth: '15%', - }; - const lgColumnStyle = { - width: '20%', - maxWidth: '20%', - }; + // const mdColumnStyle = { + // width: '15%', + // maxWidth: '15%', + // }; + // const lgColumnStyle = { + // width: '20%', + // maxWidth: '20%', + // }; const xlColumnStyle = { width: '30%', maxWidth: '30%', @@ -91,8 +89,8 @@ export const ServicesTable = ({ const statusColumn: TableColumn = { title: 'Status', field: 'status', - cellStyle: xsColumnStyle, - headerStyle: xsColumnStyle, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, render: rowData => , }; const uptimeColumn: TableColumn = { @@ -109,8 +107,8 @@ export const ServicesTable = ({ const actionsColumn: TableColumn = { title: '', field: '', - cellStyle: xsColumnStyle, - headerStyle: xsColumnStyle, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, render: rowData => , }; From 21d83eb2cc24b7595f7b561cba20fa963d2eca8c Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Tue, 4 Oct 2022 11:42:39 +0200 Subject: [PATCH 035/221] add status pages Signed-off-by: Marko Simon --- plugins/ilert/src/api/client.ts | 33 ++++ plugins/ilert/src/api/index.ts | 3 +- plugins/ilert/src/api/types.ts | 11 ++ .../src/components/ILertPage/ILertPage.tsx | 4 + .../components/ServicesPage/StatusChip.tsx | 10 +- .../components/ServicesPage/TableTitle.tsx | 97 --------- .../StatusPage/StatusPageActionsMenu.tsx | 79 ++++++++ .../components/StatusPage/StatusPageLink.tsx | 50 +++++ .../StatusPage/StatusPageStatus.tsx | 61 ++++++ .../components/StatusPage/StatusPageURL.tsx | 49 +++++ .../StatusPage/StatusPageVisibility.tsx | 51 +++++ .../StatusPage/index.ts} | 19 +- .../components/StatusPagePage/StatusChip.tsx | 82 ++++++++ .../StatusPagePage/StatusPagesPage.tsx | 67 +++++++ .../StatusPagePage/StatusPagesTable.tsx | 184 ++++++++++++++++++ .../StatusPagePage/VisibilityChip.tsx | 48 +++++ .../src/components/StatusPagePage/index.ts | 17 ++ plugins/ilert/src/hooks/useStatusPages.ts | 93 +++++++++ plugins/ilert/src/types.ts | 18 ++ 19 files changed, 856 insertions(+), 120 deletions(-) delete mode 100644 plugins/ilert/src/components/ServicesPage/TableTitle.tsx create mode 100644 plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx create mode 100644 plugins/ilert/src/components/StatusPage/StatusPageLink.tsx create mode 100644 plugins/ilert/src/components/StatusPage/StatusPageStatus.tsx create mode 100644 plugins/ilert/src/components/StatusPage/StatusPageURL.tsx create mode 100644 plugins/ilert/src/components/StatusPage/StatusPageVisibility.tsx rename plugins/ilert/src/{hooks/useNewService.ts => components/StatusPage/index.ts} (67%) create mode 100644 plugins/ilert/src/components/StatusPagePage/StatusChip.tsx create mode 100644 plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx create mode 100644 plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx create mode 100644 plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx create mode 100644 plugins/ilert/src/components/StatusPagePage/index.ts create mode 100644 plugins/ilert/src/hooks/useStatusPages.ts diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts index 4269ef583d..69b0a171f9 100644 --- a/plugins/ilert/src/api/client.ts +++ b/plugins/ilert/src/api/client.ts @@ -29,6 +29,7 @@ import { OnCall, Schedule, Service, + StatusPage, UptimeMonitor, User, } from '../types'; @@ -37,6 +38,7 @@ import { GetAlertsCountOpts, GetAlertsOpts, GetServicesOpts, + GetStatusPagesOpts, ILertApi, ServiceRequest, } from './types'; @@ -519,6 +521,27 @@ export class ILertClient implements ILertApi { return response; } + async fetchStatusPages(opts?: GetStatusPagesOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts?.maxResults !== undefined) { + query.append('max-results', String(opts.maxResults)); + } + if (opts?.startIndex !== undefined) { + query.append('start-index', String(opts.startIndex)); + } + + query.append('include', 'subscribed'); + + const response = await this.fetch( + `/api/status-pages?${query.toString()}`, + init, + ); + return response; + } + getAlertDetailsURL(alert: Alert): string { return `${this.baseUrl}/alert/view.jsf?id=${encodeURIComponent(alert.id)}`; } @@ -556,6 +579,16 @@ export class ILertClient implements ILertApi { )}`; } + getStatusPageDetailsURL(statusPage: StatusPage): string { + return `${this.baseUrl}/status-page/view.jsf?id=${encodeURIComponent( + statusPage.id, + )}`; + } + + getStatusPageURL(statusPage: StatusPage): string { + return statusPage.domain ? statusPage.domain : statusPage.subdomain; + } + getUserPhoneNumber(user: User | null) { return user?.mobile?.number || user?.landline?.number || ''; } diff --git a/plugins/ilert/src/api/index.ts b/plugins/ilert/src/api/index.ts index f0688e0f63..30120225a4 100644 --- a/plugins/ilert/src/api/index.ts +++ b/plugins/ilert/src/api/index.ts @@ -16,10 +16,11 @@ export { ilertApiRef, ILertClient } from './client'; export type { - AlertEventRequest as EventRequest, + EventRequest as EventRequest, GetAlertsCountOpts, GetAlertsOpts, GetServicesOpts, + GetStatusPagesOpts, ILertApi, TableState, } from './types'; diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index 39eb373ae2..4e6e56dfd5 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -24,6 +24,7 @@ import { OnCall, Schedule, Service, + StatusPage, UptimeMonitor, User, } from '../types'; @@ -53,6 +54,12 @@ export type GetServicesOpts = { startIndex?: number; }; +/** @public */ +export type GetStatusPagesOpts = { + maxResults?: number; + startIndex?: number; +}; + /** @public */ export type EventRequest = { integrationKey: string; @@ -109,12 +116,16 @@ export interface ILertApi { fetchServices(opts?: GetServicesOpts): Promise; createService(eventRequest: ServiceRequest): Promise; + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; + getAlertDetailsURL(alert: Alert): string; getAlertSourceDetailsURL(alertSource: AlertSource | null): string; getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; getScheduleDetailsURL(schedule: Schedule): string; getServiceDetailsURL(service: Service): string; + getStatusPageDetailsURL(statusPage: StatusPage): string; + getStatusPageURL(statusPage: StatusPage): string; getUserPhoneNumber(user: User | null): string; getUserInitials(user: User | null): string; } diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx index cc7527c316..45bd219d62 100644 --- a/plugins/ilert/src/components/ILertPage/ILertPage.tsx +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -24,6 +24,7 @@ import React from 'react'; import { AlertsPage } from '../AlertsPage'; import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; import { ServicesPage } from '../ServicesPage'; +import { StatusPagesPage } from '../StatusPagePage'; import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; /** @public */ @@ -34,6 +35,7 @@ export const ILertPage = () => { { label: 'Alerts' }, { label: 'Uptime Monitors' }, { label: 'Services' }, + { label: 'Status pages' }, ]; const renderTab = () => { switch (selectedTab) { @@ -45,6 +47,8 @@ export const ILertPage = () => { return ; case 3: return ; + case 4: + return ; default: return null; } diff --git a/plugins/ilert/src/components/ServicesPage/StatusChip.tsx b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx index 5eb31b8a94..f23c64450b 100644 --- a/plugins/ilert/src/components/ServicesPage/StatusChip.tsx +++ b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx @@ -27,7 +27,7 @@ import { serviceStatusLabels } from '../Service/ServiceStatus'; const OperationalChip = withStyles({ root: { - backgroundColor: '#4caf50', + backgroundColor: '#388E3D', color: 'white', margin: 0, }, @@ -35,28 +35,28 @@ const OperationalChip = withStyles({ const UnderMaintenanceChip = withStyles({ root: { - backgroundColor: '#ffb74d', + backgroundColor: '#616161', color: 'white', margin: 0, }, })(Chip); const DegradedChip = withStyles({ root: { - backgroundColor: '#d32f2f', + backgroundColor: '#FBC02D', color: 'white', margin: 0, }, })(Chip); const PartialOutageChip = withStyles({ root: { - backgroundColor: '#d4a5bb', + backgroundColor: '#F57C02', color: 'white', margin: 0, }, })(Chip); const MajorOutageChip = withStyles({ root: { - backgroundColor: '#28c548', + backgroundColor: '#D22F2E', color: 'white', margin: 0, }, diff --git a/plugins/ilert/src/components/ServicesPage/TableTitle.tsx b/plugins/ilert/src/components/ServicesPage/TableTitle.tsx deleted file mode 100644 index c610e91d94..0000000000 --- a/plugins/ilert/src/components/ServicesPage/TableTitle.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import Checkbox from '@material-ui/core/Checkbox'; -import FormControl from '@material-ui/core/FormControl'; -import ListItemText from '@material-ui/core/ListItemText'; -import MenuItem from '@material-ui/core/MenuItem'; -import Select from '@material-ui/core/Select'; -import { makeStyles } from '@material-ui/core/styles'; -import Typography from '@material-ui/core/Typography'; -import React from 'react'; -import { ACCEPTED, AlertStatus, PENDING, RESOLVED } from '../../types'; -import { alertStatusLabels } from '../Alert/AlertStatus'; - -const ITEM_HEIGHT = 48; -const ITEM_PADDING_TOP = 8; -const MenuProps = { - PaperProps: { - style: { - maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, - width: 250, - }, - }, -}; - -const useStyles = makeStyles({ - root: { - display: 'flex', - }, - label: { - marginTop: 8, - marginRight: 4, - }, - formControl: { - minWidth: 120, - maxWidth: 300, - }, - grow: { - flexGrow: 1, - }, -}); - -export const TableTitle = ({ - alertStates, - onAlertStatesChange, -}: { - alertStates: AlertStatus[]; - onAlertStatesChange: (states: AlertStatus[]) => void; -}) => { - const classes = useStyles(); - const handleAlertStatusSelectChange = (event: any) => { - onAlertStatesChange(event.target.value); - }; - - return ( -
- - Status: - - - - -
- ); -}; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx b/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx new file mode 100644 index 0000000000..8fe94eb2e1 --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const StatusPageActionsMenu = ({ + statusPage, +}: { + statusPage: StatusPage; +}) => { + const ilertApi = useApi(ilertApiRef); + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + View in iLert + + + + + + + View status page + + + + + + ); +}; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx new file mode 100644 index 0000000000..60daddf9df --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +const useStyles = makeStyles({ + link: { + lineHeight: '22px', + }, +}); + +export const StatusPageLink = ({ + statusPage, +}: { + statusPage: StatusPage | null; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (!statusPage) { + return null; + } + + return ( + + #{statusPage.id} + + ); +}; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageStatus.tsx b/plugins/ilert/src/components/StatusPage/StatusPageStatus.tsx new file mode 100644 index 0000000000..b40db5d8f3 --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/StatusPageStatus.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { StatusError, StatusOK } from '@backstage/core-components'; +import { makeStyles } from '@material-ui/core/styles'; +import Tooltip from '@material-ui/core/Tooltip'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + StatusPage, + UNDER_MAINTENANCE, +} from '../../types'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, +}); + +export const statusPageStatusLabels = { + [OPERATIONAL]: 'Operational', + [UNDER_MAINTENANCE]: 'Under maintenance', + [DEGRADED]: 'Degraded', + [PARTIAL_OUTAGE]: 'Partial outage', + [MAJOR_OUTAGE]: 'Major outage', +} as Record; + +export const StatusPageStatus = ({ + statusPage, +}: { + statusPage: StatusPage; +}) => { + const classes = useStyles(); + + return ( + +
+ {statusPage.status === 'OPERATIONAL' ? : } +
+
+ ); +}; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx new file mode 100644 index 0000000000..b94903333a --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +const useStyles = makeStyles({ + link: { + lineHeight: '22px', + }, +}); + +export const StatusPageURL = ({ + statusPage, +}: { + statusPage: StatusPage | null; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (!statusPage) { + return null; + } + + const url = ilertApi.getStatusPageURL(statusPage); + + return ( + + {url} + + ); +}; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageVisibility.tsx b/plugins/ilert/src/components/StatusPage/StatusPageVisibility.tsx new file mode 100644 index 0000000000..7c8f63dd35 --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/StatusPageVisibility.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { StatusError, StatusOK } from '@backstage/core-components'; +import { makeStyles } from '@material-ui/core/styles'; +import Tooltip from '@material-ui/core/Tooltip'; +import React from 'react'; +import { PRIVATE, PUBLIC, StatusPage } from '../../types'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, +}); + +export const statusPageVisibilityLabels = { + [PUBLIC]: 'Public', + [PRIVATE]: 'Private', +} as Record; + +export const StatusPageVisibility = ({ + statusPage, +}: { + statusPage: StatusPage; +}) => { + const classes = useStyles(); + + return ( + +
+ {statusPage.visibility === 'PUBLIC' ? : } +
+
+ ); +}; diff --git a/plugins/ilert/src/hooks/useNewService.ts b/plugins/ilert/src/components/StatusPage/index.ts similarity index 67% rename from plugins/ilert/src/hooks/useNewService.ts rename to plugins/ilert/src/components/StatusPage/index.ts index 7d50d29a39..93ea9deaba 100644 --- a/plugins/ilert/src/hooks/useNewService.ts +++ b/plugins/ilert/src/components/StatusPage/index.ts @@ -13,20 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; - -export const useNewService = () => { - const [name, setName] = React.useState(''); - const [isLoading, setIsLoading] = React.useState(false); - - return [ - { - name, - isLoading, - }, - { - setName, - setIsLoading, - }, - ] as const; -}; +export * from './StatusPageActionsMenu'; +export * from './StatusPageStatus'; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx new file mode 100644 index 0000000000..50a64de212 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + StatusPage, + UNDER_MAINTENANCE, +} from '../../types'; +import { statusPageStatusLabels } from '../StatusPage/StatusPageStatus'; + +const OperationalChip = withStyles({ + root: { + backgroundColor: '#388E3D', + color: 'white', + margin: 0, + }, +})(Chip); + +const UnderMaintenanceChip = withStyles({ + root: { + backgroundColor: '#616161', + color: 'white', + margin: 0, + }, +})(Chip); +const DegradedChip = withStyles({ + root: { + backgroundColor: '#FBC02D', + color: 'white', + margin: 0, + }, +})(Chip); +const PartialOutageChip = withStyles({ + root: { + backgroundColor: '#F57C02', + color: 'white', + margin: 0, + }, +})(Chip); +const MajorOutageChip = withStyles({ + root: { + backgroundColor: '#D22F2E', + color: 'white', + margin: 0, + }, +})(Chip); + +export const StatusChip = ({ statusPage }: { statusPage: StatusPage }) => { + const label = `${statusPageStatusLabels[statusPage.status]}`; + + switch (statusPage.status) { + case OPERATIONAL: + return ; + case UNDER_MAINTENANCE: + return ; + case DEGRADED: + return ; + case PARTIAL_OUTAGE: + return ; + case MAJOR_OUTAGE: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx new file mode 100644 index 0000000000..6e4290c7c9 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + Content, + ContentHeader, + ResponseErrorPanel, + SupportButton, +} from '@backstage/core-components'; +import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; +import { useStatusPages } from '../../hooks/useStatusPages'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { StatusPagesTable } from './StatusPagesTable'; + +export const StatusPagesPage = () => { + const [ + { tableState, statusPages, isLoading, error }, + { onChangePage, onChangeRowsPerPage, setIsLoading }, + ] = useStatusPages(true); + + if (error) { + if (error instanceof AuthenticationError) { + return ( + + + + ); + } + + return ( + + + + ); + } + + return ( + + + + This helps you to bring iLert into your developer portal. + + + + + ); +}; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx new file mode 100644 index 0000000000..8b013fdb35 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx @@ -0,0 +1,184 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { TableState } from '../../api'; +import { StatusPage } from '../../types'; +import { VisibilityChip } from './VisibilityChip'; + +import { Table, TableColumn } from '@backstage/core-components'; +import { StatusPageActionsMenu } from '../StatusPage/StatusPageActionsMenu'; +import { StatusPageLink } from '../StatusPage/StatusPageLink'; +import { StatusPageURL } from '../StatusPage/StatusPageURL'; +import { StatusChip } from './StatusChip'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const StatusPagesTable = ({ + statusPages, + tableState, + isLoading, + onChangePage, + onChangeRowsPerPage, + compact, +}: { + statusPages: StatusPage[]; + tableState: TableState; + isLoading: boolean; + setIsLoading: (isLoading: boolean) => void; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + compact?: boolean; +}) => { + const classes = useStyles(); + + // const xsColumnStyle = { + // width: '5%', + // maxWidth: '5%', + // }; + const smColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + // const mdColumnStyle = { + // width: '15%', + // maxWidth: '15%', + // }; + // const lgColumnStyle = { + // width: '20%', + // maxWidth: '20%', + // }; + const xlColumnStyle = { + width: '30%', + maxWidth: '30%', + }; + + const idColumn: TableColumn = { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const nameColumn: TableColumn = { + title: 'Name', + field: 'name', + cellStyle: !compact ? xlColumnStyle : undefined, + headerStyle: !compact ? xlColumnStyle : undefined, + render: rowData => {(rowData as StatusPage).name}, + }; + const urlColumn: TableColumn = { + title: 'URL', + field: 'url', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const visibilityColumn: TableColumn = { + title: 'Visibility', + field: 'visibility', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const statusColumn: TableColumn = { + title: 'Status', + field: 'status', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const actionsColumn: TableColumn = { + title: '', + field: '', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + + ), + }; + + const columns: TableColumn[] = compact + ? [nameColumn, statusColumn, urlColumn, actionsColumn] + : [ + idColumn, + nameColumn, + statusColumn, + urlColumn, + visibilityColumn, + actionsColumn, + ]; + let tableStyle: React.CSSProperties = {}; + if (compact) { + tableStyle = { + width: '100%', + maxWidth: '100%', + minWidth: '0', + height: 'calc(100% - 10px)', + boxShadow: 'none !important', + borderRadius: 'none !important', + }; + } else { + tableStyle = { + width: '100%', + maxWidth: '100%', + }; + } + + return ( +
+ No status pages + + } + title={ + + STATUS PAGES + + } + page={tableState.page} + onPageChange={onChangePage} + onRowsPerPageChange={onChangeRowsPerPage} + // localization={{ header: { actions: undefined } }} + columns={columns} + data={statusPages} + isLoading={isLoading} + /> + ); +}; diff --git a/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx b/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx new file mode 100644 index 0000000000..fb72819216 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { PRIVATE, PUBLIC, StatusPage } from '../../types'; +import { statusPageVisibilityLabels } from '../StatusPage/StatusPageVisibility'; + +const PrivateChip = withStyles({ + root: { + backgroundColor: '#4caf50', + color: 'white', + margin: 0, + }, +})(Chip); + +const PublicChip = withStyles({ + root: { + backgroundColor: '#ffb74d', + color: 'white', + margin: 0, + }, +})(Chip); + +export const VisibilityChip = ({ statusPage }: { statusPage: StatusPage }) => { + const label = `${statusPageVisibilityLabels[statusPage.visibility]}`; + + switch (statusPage.visibility) { + case PRIVATE: + return ; + case PUBLIC: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/StatusPagePage/index.ts b/plugins/ilert/src/components/StatusPagePage/index.ts new file mode 100644 index 0000000000..8bc923ffe1 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './StatusPagesPage'; +export * from './StatusPagesTable'; diff --git a/plugins/ilert/src/hooks/useStatusPages.ts b/plugins/ilert/src/hooks/useStatusPages.ts new file mode 100644 index 0000000000..740e0f515f --- /dev/null +++ b/plugins/ilert/src/hooks/useStatusPages.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { GetStatusPagesOpts, ilertApiRef, TableState } from '../api'; +import { StatusPage } from '../types'; + +export const useStatusPages = (paging: boolean) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [tableState, setTableState] = React.useState({ + page: 0, + pageSize: 10, + }); + + const [statusPagesList, setStatusPagesList] = React.useState( + [], + ); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchStatusPagesCall = async () => { + try { + setIsLoading(true); + const opts: GetStatusPagesOpts = {}; + if (paging) { + opts.maxResults = tableState.pageSize; + opts.startIndex = tableState.page * tableState.pageSize; + } + const data = await ilertApi.fetchStatusPages(opts); + setStatusPagesList(data || []); + setIsLoading(false); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + setIsLoading(false); + throw e; + } + }; + + const fetchStatusPages = useAsyncRetry(fetchStatusPagesCall, [tableState]); + + const refetchStatusPages = () => { + setTableState({ ...tableState, page: 0 }); + Promise.all([fetchStatusPagesCall()]); + }; + + const error = fetchStatusPages.error; + const retry = () => { + fetchStatusPages.retry(); + }; + + const onChangePage = (page: number) => { + setTableState({ ...tableState, page }); + }; + const onChangeRowsPerPage = (p: number) => { + setTableState({ ...tableState, pageSize: p }); + }; + + return [ + { + tableState, + statusPages: statusPagesList, + isLoading, + error, + }, + { + setTableState, + setStatusPagesList, + setIsLoading, + retry, + refetchStatusPages, + onChangePage, + onChangeRowsPerPage, + }, + ] as const; +}; diff --git a/plugins/ilert/src/types.ts b/plugins/ilert/src/types.ts index ebe5c1b39d..28b8b7ca8a 100644 --- a/plugins/ilert/src/types.ts +++ b/plugins/ilert/src/types.ts @@ -251,6 +251,14 @@ export type ServiceStatus = | typeof PARTIAL_OUTAGE | typeof MAJOR_OUTAGE; +/** @public */ +export const PRIVATE = 'PRIVATE'; +/** @public */ +export const PUBLIC = 'PUBLIC'; + +/** @public */ +export type StatusPageVisibility = typeof PRIVATE | typeof PUBLIC; + /** @public */ export interface AlertSourceEmailPredicate { field: 'EMAIL_FROM' | 'EMAIL_SUBJECT' | 'EMAIL_BODY'; @@ -422,3 +430,13 @@ export interface Uptime { export interface UptimePercentage { p90: number; } + +/** @public */ +export interface StatusPage { + id: number; + name: string; + domain: string; + subdomain: string; + visibility: StatusPageVisibility; + status: ServiceStatus; +} From 56793b0db3ca4ead067a0fced26e372a486366a3 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Wed, 12 Oct 2022 16:02:25 +0200 Subject: [PATCH 036/221] fix tab order Signed-off-by: Marko Simon --- plugins/ilert/src/components/ILertPage/ILertPage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx index 45bd219d62..51512f6d39 100644 --- a/plugins/ilert/src/components/ILertPage/ILertPage.tsx +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -33,9 +33,9 @@ export const ILertPage = () => { const tabs = [ { label: 'Who is on call?' }, { label: 'Alerts' }, - { label: 'Uptime Monitors' }, { label: 'Services' }, { label: 'Status pages' }, + { label: 'Uptime Monitors' }, ]; const renderTab = () => { switch (selectedTab) { @@ -44,11 +44,11 @@ export const ILertPage = () => { case 1: return ; case 2: - return ; - case 3: return ; - case 4: + case 3: return ; + case 4: + return ; default: return null; } From f67f7e97e1d607e263adfbaae682f1d5cc7ffbaa Mon Sep 17 00:00:00 2001 From: spencerrichardhenry <46569542+spencerrichardhenry@users.noreply.github.com> Date: Wed, 12 Oct 2022 09:13:19 -0600 Subject: [PATCH 037/221] Update .changeset/flat-items-perform.md Co-authored-by: Patrik Oldsberg Signed-off-by: spencerrichardhenry <46569542+spencerrichardhenry@users.noreply.github.com> --- .changeset/flat-items-perform.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/flat-items-perform.md b/.changeset/flat-items-perform.md index 46626ac667..4bc846ba7e 100644 --- a/.changeset/flat-items-perform.md +++ b/.changeset/flat-items-perform.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-backend': minor --- Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: From 383dc6e2fa714d89f41c9b204ff526216611e9ab Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Wed, 12 Oct 2022 09:17:21 -0600 Subject: [PATCH 038/221] address mr comments Signed-off-by: Spencer Henry --- contrib/docs/tutorials/prometheus-metrics.md | 6 ++--- .../tasks/NunjucksWorkflowRunner.ts | 25 +++++++------------ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/contrib/docs/tutorials/prometheus-metrics.md b/contrib/docs/tutorials/prometheus-metrics.md index a46a2286cd..56989e709c 100644 --- a/contrib/docs/tutorials/prometheus-metrics.md +++ b/contrib/docs/tutorials/prometheus-metrics.md @@ -106,9 +106,7 @@ There are some custom metrics that have been added to Backstage will be output f - `catalog_processing_duration_seconds`: Time spent executing the full processing flow - `catalog_processors_duration_seconds`: Time spent executing catalog processors - `catalog_processing_queue_delay_seconds`: The amount of delay between being scheduled for processing, and the start of actually being processed -- `scaffolder_task_success_count`: Tracks successful task runs. -- `scaffolder_task_error_count`: a count that track how many task runs error out +- `scaffolder_task_count`: Tracks successful task runs. - `scaffolder_task_duration`: a histogram which tracks the duration of a task run -- `scaffolder_step_success_count`: a count that tracks each step run -- `scaffolder_step_error_count`: a count that tracks how many steps error out +- `scaffolder_step_count`: a count that tracks each step run - `scaffolder_step_duration`: a histogram which tracks the duration of each step run diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index f651be4830..fa0e9d8402 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -230,7 +230,6 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const { taskLogger, streamLogger } = createStepLogger({ task, step }); if (task.isDryRun) { - await taskTrack.skipDryRun(step, action); const redactedSecrets = Object.fromEntries( Object.entries(task.secrets ?? {}).map(secret => [ secret[0], @@ -258,13 +257,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { )}`, ); if (!action.supportsDryRun) { - task.emitLog( - `Skipping because ${action.id} does not support dry-run`, - { - stepId: step.id, - status: 'skipped', - }, - ); + await taskTrack.skipDryRun(step, action); const outputSchema = action.schema?.output; if (outputSchema) { context.steps[step.id] = { @@ -336,13 +329,13 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { await stepTrack.markSuccessful(); } catch (err) { await taskTrack.markFailed(step, err); - stepTrack.markFailed(); + await stepTrack.markFailed(); throw err; } } const output = this.render(task.spec.output, context, renderTemplate); - taskTrack.markSuccessful(); + await taskTrack.markSuccessful(); return { output }; } finally { @@ -355,8 +348,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { function scaffoldingTracker() { const taskCount = createCounterMetric({ - name: 'scaffolder_task_success_count', - help: 'Count of succesful task runs', + name: 'scaffolder_task_count', + help: 'Count of task runs', labelNames: ['template', 'user', 'result'], }); const taskDuration = createHistogramMetric({ @@ -365,8 +358,8 @@ function scaffoldingTracker() { labelNames: ['template', 'result'], }); const stepCount = createCounterMetric({ - name: 'scaffolder_step_success_count', - help: 'Count of successful step runs', + name: 'scaffolder_step_count', + help: 'Count of step runs', labelNames: ['template', 'step', 'result'], }); const stepDuration = createHistogramMetric({ @@ -394,7 +387,7 @@ function scaffoldingTracker() { }); } - function markSuccessful() { + async function markSuccessful() { taskCount.inc({ template, user, @@ -448,7 +441,7 @@ function scaffoldingTracker() { stepTimer({ result: 'ok' }); } - function markFailed() { + async function markFailed() { stepCount.inc({ template, step: step.name, From 0697af30da75218b82244af8909308cf777ba943 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Wed, 12 Oct 2022 16:59:33 +0200 Subject: [PATCH 039/221] add changeset Signed-off-by: Marko Simon --- .changeset/popular-mails-wave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/popular-mails-wave.md diff --git a/.changeset/popular-mails-wave.md b/.changeset/popular-mails-wave.md new file mode 100644 index 0000000000..23517f0608 --- /dev/null +++ b/.changeset/popular-mails-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-ilert': minor +--- + +Added support for multiple responders in alert list, added new tab with list to support iLert resource 'service', added new tab with list to support iLert resource 'status page' From 2499dcb7b123b15c64c4129270737d3582351ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 22 Sep 2022 14:09:19 +0200 Subject: [PATCH 040/221] no longer transpile tsx files in backend packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/swift-phones-cheat.md | 12 ++++++++++++ packages/cli/src/lib/bundler/config.ts | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/swift-phones-cheat.md diff --git a/.changeset/swift-phones-cheat.md b/.changeset/swift-phones-cheat.md new file mode 100644 index 0000000000..97697ad916 --- /dev/null +++ b/.changeset/swift-phones-cheat.md @@ -0,0 +1,12 @@ +--- +'@backstage/cli': minor +--- + +Removed `tsx` and `jsx` as supported extensions in backend packages. For most +repos, this will not have any effect. But if you inadvertently had added some +`tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for +this is that the offending files get ignored during transpilation. Hence, the +importing file can no longer find anything to import. + +The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, +or `.jsx` to `.js`. diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 0d4649fa3f..1974153f02 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -267,7 +267,7 @@ export async function createBackendConfig( paths.targetRunFile ? paths.targetRunFile : paths.targetEntry, ], resolve: { - extensions: ['.ts', '.tsx', '.mjs', '.js', '.jsx', '.json'], + extensions: ['.ts', '.mjs', '.js', '.json'], mainFields: ['main'], modules: [paths.rootNodeModules, ...moduleDirs], plugins: [ From 19ff4836a5af9c64d900330c8f2a7c0e623acb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 22 Sep 2022 17:03:20 +0200 Subject: [PATCH 041/221] nerf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/swift-phones-cheat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/swift-phones-cheat.md b/.changeset/swift-phones-cheat.md index 97697ad916..6d1653caa3 100644 --- a/.changeset/swift-phones-cheat.md +++ b/.changeset/swift-phones-cheat.md @@ -1,5 +1,5 @@ --- -'@backstage/cli': minor +'@backstage/cli': patch --- Removed `tsx` and `jsx` as supported extensions in backend packages. For most From c735a18c0db0127dd67ba6a1b781fe43773e64b0 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Thu, 29 Sep 2022 09:15:38 -0600 Subject: [PATCH 042/221] kick off E2E tests Signed-off-by: Spencer Henry From a889314692a285f2569fec0d16b57632b42a0e9e Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 13 Oct 2022 14:27:50 +0200 Subject: [PATCH 043/221] Introduce an entityRef context key Signed-off-by: Eric Peterson --- .changeset/analyze-software-exploration.md | 5 ++ docs/plugins/analytics.md | 10 +-- .../src/hooks/useEntity.test.tsx | 64 ++++++++++++++++++- plugins/catalog-react/src/hooks/useEntity.tsx | 11 +++- 4 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 .changeset/analyze-software-exploration.md diff --git a/.changeset/analyze-software-exploration.md b/.changeset/analyze-software-exploration.md new file mode 100644 index 0000000000..f535f60127 --- /dev/null +++ b/.changeset/analyze-software-exploration.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Both `EntityProvider` and `AsyncEntityProvider` contexts now wrap all children with an `AnalyticsContext` containing the corresponding `entityRef`; this opens up the possibility for all events underneath these contexts to be associated with and aggregated by the corresponding entity. diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index ccb4e5c5a7..0c2173de5c 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -301,11 +301,11 @@ it's important to keep each of these levels of detail disaggregated. automatically as part of the `extension` in which the `filter` event was captured). -- On the flip side, when adding `attributes` to an event, look at existing - events and see if the data you are capturing matches the intention, type, or - even the content of _their_ `attributes`. For instance, it may be common for - events that involve the Catalog to add details like entity `name`, `kind`, - and/or `namespace` as `attributes`. Using the same keys in your event will +- On the flip side, when adding `attributes` to or `context` around an event, + look at existing events and see if the data you are capturing matches the + intention, type, or even the content of _their_ `attributes` or `context`. + For instance, it's common for events that involve the Catalog to include an + `entityRef` contextual key. Using the same keys and values in your event will ensure that events instrumented across plugins can easily be aggregated. ### Unit Testing Event Capture diff --git a/plugins/catalog-react/src/hooks/useEntity.test.tsx b/plugins/catalog-react/src/hooks/useEntity.test.tsx index 5e86a94180..3a1e5010d3 100644 --- a/plugins/catalog-react/src/hooks/useEntity.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.test.tsx @@ -23,6 +23,11 @@ import { AsyncEntityProvider, } from './useEntity'; import { Entity } from '@backstage/catalog-model'; +import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api'; +import { MockAnalyticsApi, TestApiRegistry } from '@backstage/test-utils'; +import { ApiProvider } from '@backstage/core-app-api'; + +const entity = { metadata: { name: 'my-entity' }, kind: 'MyKind' } as Entity; describe('useEntity', () => { it('should throw if no entity is provided', async () => { @@ -34,7 +39,6 @@ describe('useEntity', () => { }); it('should provide an entity', async () => { - const entity = { kind: 'MyEntity' } as Entity; const { result } = renderHook(() => useEntity(), { wrapper: ({ children }) => ( @@ -43,6 +47,24 @@ describe('useEntity', () => { expect(result.current.entity).toBe(entity); }); + + it('should provide entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + context: { entityRef: 'mykind:default/my-entity' }, + }); + }); }); describe('useAsyncEntity', () => { @@ -60,7 +82,6 @@ describe('useAsyncEntity', () => { }); it('should provide an entity', async () => { - const entity = { kind: 'MyEntity' } as Entity; const refresh = () => {}; const { result } = renderHook(() => useAsyncEntity(), { wrapper: ({ children }) => ( @@ -96,4 +117,43 @@ describe('useAsyncEntity', () => { expect(result.current.error).toBe(error); expect(result.current.refresh).toBe(undefined); }); + + it('should provide entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + {}} + children={children} + /> + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + context: { entityRef: 'mykind:default/my-entity' }, + }); + }); + + it('should omit entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0].context).not.toHaveProperty('entityRef'); + }); }); diff --git a/plugins/catalog-react/src/hooks/useEntity.tsx b/plugins/catalog-react/src/hooks/useEntity.tsx index f654cff777..84b7883c8a 100644 --- a/plugins/catalog-react/src/hooks/useEntity.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { AnalyticsContext } from '@backstage/core-plugin-api'; import { createVersionedContext, createVersionedValueMap, @@ -66,7 +67,13 @@ export const AsyncEntityProvider = ({ // consumers might be doing things like `useContext(EntityContext)` return ( - {children} + + {children} + ); }; From 4830a3569f792a726c6853a17a75a2a773c094e1 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 13 Oct 2022 14:30:34 +0200 Subject: [PATCH 044/221] Introduce basic scaffolder instrumentation Signed-off-by: Eric Peterson --- .changeset/analyze-software-creation.md | 8 ++ docs/plugins/analytics.md | 13 ++-- .../MultistepJsonForm/MultistepJsonForm.tsx | 10 ++- .../TemplatePage/TemplatePage.test.tsx | 70 ++++++++++++++++- .../components/TemplatePage/TemplatePage.tsx | 75 ++++++++++--------- 5 files changed, 132 insertions(+), 44 deletions(-) create mode 100644 .changeset/analyze-software-creation.md diff --git a/.changeset/analyze-software-creation.md b/.changeset/analyze-software-creation.md new file mode 100644 index 0000000000..312987d3d1 --- /dev/null +++ b/.changeset/analyze-software-creation.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Basic analytics instrumentation is now in place: + +- As users make their way through template steps, a `click` event is fired, including the step number. +- After a user clicks "Create" a `create` event is fired, including the name of the software that was just created. The template used at creation is set on the `entityRef` context key. diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 0c2173de5c..46e1d354df 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -52,12 +52,13 @@ learn how to contribute the integration yourself! The following table summarizes events that, depending on the plugins you have installed, may be captured. -| Action | Subject | Other Notes | -| ---------- | --------------------------------------------------- | ----------------------------------------------------------------- | -| `navigate` | The URL of the page that was navigated to | | -| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | -| `search` | The search term entered in any search bar component | The `searchTypes` attribute holds `types` constraining the search | -| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | +| Action | Subject | Other Notes | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | +| `navigate` | The URL of the page that was navigated to | | +| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | +| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`) | +| `search` | The search term entered in any search bar component | The context holds `searchTypes`, representing `types` constraining the search | +| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | If there is an event you'd like to see captured, please [open an issue][add-event] describing the event you want to see and the questions it diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 8c71df67b6..7afbeb5a97 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -28,6 +28,8 @@ import { errorApiRef, useApi, featureFlagsApiRef, + useAnalytics, + useRouteRefParams, } from '@backstage/core-plugin-api'; import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; @@ -37,6 +39,7 @@ import { Content, StructuredMetadataTable } from '@backstage/core-components'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; +import { selectedTemplateRouteRef } from '../../routes'; const Form = withTheme(MuiTheme); type Step = { @@ -123,6 +126,8 @@ export const MultistepJsonForm = (props: Props) => { finishButtonLabel, layouts, } = props; + const { templateName } = useRouteRefParams(selectedTemplateRouteRef); + const analytics = useAnalytics(); const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); @@ -171,7 +176,9 @@ export const MultistepJsonForm = (props: Props) => { onReset(); }; const handleNext = () => { - setActiveStep(Math.min(activeStep + 1, steps.length)); + const stepNum = Math.min(activeStep + 1, steps.length); + setActiveStep(stepNum); + analytics.captureEvent('click', `Next Step (${stepNum})`); }; const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); const handleCreate = async () => { @@ -182,6 +189,7 @@ export const MultistepJsonForm = (props: Props) => { setDisableButtons(true); try { await onFinish(); + analytics.captureEvent('create', formData.name || `new ${templateName}`); } catch (err) { errorApi.post(err); } finally { diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index d35b561f2b..a48644e72d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; +import { + MockAnalyticsApi, + renderInTestApp, + TestApiRegistry, +} from '@backstage/test-utils'; import { act, fireEvent, within } from '@testing-library/react'; import React from 'react'; import { Route, Routes } from 'react-router'; @@ -24,6 +28,7 @@ import { TemplatePage } from './TemplatePage'; import { featureFlagsApiRef, FeatureFlagsApi, + analyticsApiRef, } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; @@ -57,6 +62,8 @@ const featureFlagsApiMock: jest.Mocked = { const errorApiMock = { post: jest.fn(), error$: jest.fn() }; +const analyticsMock = new MockAnalyticsApi(); + const schemaMockValue = { title: 'my-schema', steps: [ @@ -105,6 +112,7 @@ const apis = TestApiRegistry.from( [scaffolderApiRef, scaffolderApiMock], [errorApiRef, errorApiMock], [featureFlagsApiRef, featureFlagsApiMock], + [analyticsApiRef, analyticsMock], ); describe('TemplatePage', () => { @@ -158,6 +166,66 @@ describe('TemplatePage', () => { }); }); + it('captures expected analytics events', async () => { + scaffolderApiMock.scaffold.mockResolvedValue({ taskId: 'xyz' }); + scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({ + title: 'schema-4-analytics', + steps: [ + { + title: 'Fill in some steps', + schema: { + properties: { + name: { + title: 'Name', + type: 'string', + }, + }, + required: ['name'], + }, + }, + ], + }); + const { findByLabelText, findByText } = await renderInTestApp( + + + , + { + mountedRoutes: { + '/create': rootRouteRef, + }, + }, + ); + + // Fill out the name field + expect(await findByText('Fill in some steps')).toBeInTheDocument(); + fireEvent.change(await findByLabelText('Name', { exact: false }), { + target: { value: 'expected-name' }, + }); + + // Go to the final page + fireEvent.click(await findByText('Next step')); + expect(await findByText('Reset')).toBeInTheDocument(); + + // Create the software + await act(async () => { + fireEvent.click(await findByText('Create')); + }); + + // The "Next Step" button should have fired an event + expect(analyticsMock.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'Next Step (1)', + context: { entityRef: 'template:default/test' }, + }); + + // And the "Create" button should have fired an event + expect(analyticsMock.getEvents()[1]).toMatchObject({ + action: 'create', + subject: 'expected-name', + context: { entityRef: 'template:default/test' }, + }); + }); + it('navigates away if no template was loaded', async () => { scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue( undefined as any, diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 9eba61b53b..87678d2a83 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -32,6 +32,7 @@ import { createValidator } from './createValidator'; import { Content, Header, InfoCard, Page } from '@backstage/core-components'; import { + AnalyticsContext, errorApiRef, useApi, useApiHolder, @@ -129,41 +130,43 @@ export const TemplatePage = ({ ); return ( - -
- - {loading && } - {schema && ( - - { - return { - ...step, - validate: createValidator( - step.schema, - customFieldValidators, - { apiHolder }, - ), - }; - })} - /> - - )} - - + + +
+ + {loading && } + {schema && ( + + { + return { + ...step, + validate: createValidator( + step.schema, + customFieldValidators, + { apiHolder }, + ), + }; + })} + /> + + )} + + + ); }; From d79f104234830d13f843c38342fee738d83bc806 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 13 Oct 2022 15:17:27 +0200 Subject: [PATCH 045/221] Fix entity mocks in tests Signed-off-by: Eric Peterson --- .../components/EntityBadgesDialog.test.tsx | 5 ++++- .../EntityLinksCard/EntityLinksCard.test.tsx | 2 ++ .../EntitySwitch/EntitySwitch.test.tsx | 20 ++++++++++++------- .../GoCdBuildsComponent.test.tsx | 4 +++- .../src/components/TodoList/TodoList.test.tsx | 5 ++++- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/plugins/badges/src/components/EntityBadgesDialog.test.tsx b/plugins/badges/src/components/EntityBadgesDialog.test.tsx index ea44eb20fc..5d0e7fe849 100644 --- a/plugins/badges/src/components/EntityBadgesDialog.test.tsx +++ b/plugins/badges/src/components/EntityBadgesDialog.test.tsx @@ -38,7 +38,10 @@ describe('EntityBadgesDialog', () => { }, ]), }; - const mockEntity = { metadata: { name: 'mock' } } as Entity; + const mockEntity = { + metadata: { name: 'mock' }, + kind: 'MockKind', + } as Entity; const rendered = await renderWithEffects( { const createEntity = (links: EntityLink[] = []): Entity => ({ metadata: { + name: 'mock', links, }, + kind: 'MockKind', } as Entity); const createLink = ({ diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx index 9588327c3b..4ee9d9c820 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx @@ -46,7 +46,9 @@ describe('EntitySwitch', () => { const rendered = render( - + {content} , @@ -58,7 +60,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} , @@ -70,7 +74,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} , @@ -94,7 +100,7 @@ describe('EntitySwitch', () => { }); it('should switch child when filters switch', () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const rendered = render( @@ -126,7 +132,7 @@ describe('EntitySwitch', () => { }); it('should switch with async condition that is true', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.resolve(true); const rendered = render( @@ -145,7 +151,7 @@ describe('EntitySwitch', () => { }); it('should switch with sync condition that is false', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.resolve(false); const rendered = render( @@ -164,7 +170,7 @@ describe('EntitySwitch', () => { }); it('should switch with sync condition that throws', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.reject(); const rendered = render( diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx index 7f16f40a6a..ebc3502901 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -36,7 +36,9 @@ describe('GoCdArtifactsComponent', () => { baseUrl: 'gocd.baseurl.com', }, }); - const entityValue = { entity: { metadata: {} } as Entity }; + const entityValue = { + entity: { metadata: { name: 'mock' }, kind: 'MockKind' } as Entity, + }; const renderComponent = () => renderWithEffects( diff --git a/plugins/todo/src/components/TodoList/TodoList.test.tsx b/plugins/todo/src/components/TodoList/TodoList.test.tsx index 2759fe40f8..15d4334610 100644 --- a/plugins/todo/src/components/TodoList/TodoList.test.tsx +++ b/plugins/todo/src/components/TodoList/TodoList.test.tsx @@ -38,7 +38,10 @@ describe('TodoList', () => { offset: 0, }), }; - const mockEntity = { metadata: { name: 'mock' } } as Entity; + const mockEntity = { + metadata: { name: 'mock' }, + kind: 'MockKind', + } as Entity; const rendered = await renderWithEffects( From 3ec10f407ea01b7f47f274dba3208c7d3f0e7bcb Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 11:47:22 +0200 Subject: [PATCH 046/221] added header options to scaffolder page props Signed-off-by: Alex Rybchenko --- plugins/scaffolder/src/components/Router.tsx | 6 ++++++ .../src/components/ScaffolderPage/ScaffolderPage.tsx | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 021d73f5e9..097eddf6cd 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -63,6 +63,11 @@ export type RouterProps = { filter: (entity: Entity) => boolean; }>; defaultPreviewTemplate?: string; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; /** * Options for the context menu on the scaffolder page. */ @@ -143,6 +148,7 @@ export const Router = (props: RouterProps) => { groups={groups} TemplateCardComponent={TemplateCardComponent} contextMenu={props.contextMenu} + headerOptions={props.headerOptions} /> } /> diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 60a761d171..9c13db72b1 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -54,12 +54,18 @@ export type ScaffolderPageProps = { actions?: boolean; tasks?: boolean; }; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; }; export const ScaffolderPageContents = ({ TemplateCardComponent, groups, contextMenu, + headerOptions, }: ScaffolderPageProps) => { const registerComponentLink = useRouteRef(registerComponentRouteRef); const otherTemplatesGroup = { @@ -80,6 +86,7 @@ export const ScaffolderPageContents = ({ pageTitleOverride="Create a New Component" title="Create a New Component" subtitle="Create new software components using standard templates" + {...headerOptions} >
@@ -134,12 +141,14 @@ export const ScaffolderPage = ({ TemplateCardComponent, groups, contextMenu, + headerOptions, }: ScaffolderPageProps) => ( ); From a3f1340f3ccf43ffdc2010f971f1684db6163770 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 11:47:52 +0200 Subject: [PATCH 047/221] exported scaffolder routes Signed-off-by: Alex Rybchenko --- plugins/scaffolder/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index e68eece8e5..440c87c363 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -61,6 +61,7 @@ export { scaffolderPlugin, } from './plugin'; export * from './components'; +export * from './routes'; export type { TaskPageProps } from './components/TaskPage'; /** next exports */ From edae17309e7e0e0007dc2176bbb226ed7717db5c Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 11:52:36 +0200 Subject: [PATCH 048/221] added changeset Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sixty-islands-develop.md diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md new file mode 100644 index 0000000000..75af6bcc70 --- /dev/null +++ b/.changeset/sixty-islands-develop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added props to override default Scaffolder page title, subtitle and pageTitleOverride From 7d8734c35cb2bfcae40388bc7dbffb38da234cf4 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 28 Sep 2022 15:30:51 +0200 Subject: [PATCH 049/221] updated api report Signed-off-by: Alex Rybchenko --- plugins/scaffolder/api-report.md | 75 ++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index cf69736bd4..193497a849 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -24,10 +24,12 @@ import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; +import { PathParams } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { SubRouteRef } from '@backstage/core-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; @@ -42,6 +44,11 @@ export function createNextScaffolderFieldExtension< options: NextFieldExtensionOptions, ): Extension>; +// Warning: (ae-missing-release-tag) "actionsRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const actionsRouteRef: SubRouteRef; + // @public export function createScaffolderFieldExtension< TReturnValue = unknown, @@ -64,6 +71,11 @@ export type CustomFieldValidator = ( }, ) => void | Promise; +// Warning: (ae-missing-release-tag) "editRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const editRouteRef: SubRouteRef; + // @public export const EntityNamePickerFieldExtension: FieldExtensionComponent< string, @@ -144,6 +156,13 @@ export interface LayoutOptions

{ // @public export type LayoutTemplate = FormProps['ObjectFieldTemplate']; +// Warning: (ae-missing-release-tag) "legacySelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public @deprecated (undocumented) +export const legacySelectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:templateName'> +>; + // @public export type ListActionsResponse = Array<{ id: string; @@ -167,6 +186,11 @@ export type LogEvent = { taskId: string; }; +// Warning: (ae-missing-release-tag) "nextRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const nextRouteRef: RouteRef; + // @alpha export type NextCustomFieldValidator = ( data: TFieldReturnValue, @@ -215,6 +239,13 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; +// Warning: (ae-missing-release-tag) "nextSelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const nextSelectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:namespace/:templateName'> +>; + // @public export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< string, @@ -249,6 +280,11 @@ export interface OwnerPickerUiOptions { defaultNamespace?: string | false; } +// Warning: (ae-missing-release-tag) "registerComponentRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const registerComponentRouteRef: ExternalRouteRef; + // @public export const repoPickerValidation: ( value: string, @@ -287,6 +323,11 @@ export interface RepoUrlPickerUiOptions { }; } +// Warning: (ae-missing-release-tag) "rootRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const rootRouteRef: RouteRef; + // @public export type RouterProps = { components?: { @@ -302,6 +343,11 @@ export type RouterProps = { filter: (entity: Entity) => boolean; }>; defaultPreviewTemplate?: string; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; contextMenu?: { editor?: boolean; actions?: boolean; @@ -424,6 +470,11 @@ export interface ScaffolderGetIntegrationsListResponse { // @public export const ScaffolderLayouts: React.ComponentType; +// Warning: (ae-missing-release-tag) "scaffolderListTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const scaffolderListTaskRouteRef: SubRouteRef; + // @public (undocumented) export type ScaffolderOutputLink = { title?: string; @@ -494,6 +545,11 @@ export type ScaffolderTaskOutput = { [key: string]: unknown; }; +// Warning: (ae-missing-release-tag) "scaffolderTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const scaffolderTaskRouteRef: SubRouteRef>; + // @public export type ScaffolderTaskStatus = | 'open' @@ -508,6 +564,13 @@ export interface ScaffolderUseTemplateSecrets { setSecrets: (input: Record) => void; } +// Warning: (ae-missing-release-tag) "selectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const selectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:namespace/:templateName'> +>; + // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; @@ -538,4 +601,16 @@ export const TemplateTypePicker: () => JSX.Element | null; // @public export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; + +// Warning: (ae-missing-release-tag) "viewTechDocRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const viewTechDocRouteRef: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true +>; ``` From 36985448d9e87d51d33fa30258a69a5db7171ff3 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Thu, 29 Sep 2022 16:49:56 +0200 Subject: [PATCH 050/221] updated api docs Signed-off-by: Alex Rybchenko --- plugins/scaffolder/api-report.md | 24 +----------------------- plugins/scaffolder/src/routes.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 193497a849..5931e127f5 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -44,8 +44,6 @@ export function createNextScaffolderFieldExtension< options: NextFieldExtensionOptions, ): Extension>; -// Warning: (ae-missing-release-tag) "actionsRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const actionsRouteRef: SubRouteRef; @@ -71,8 +69,6 @@ export type CustomFieldValidator = ( }, ) => void | Promise; -// Warning: (ae-missing-release-tag) "editRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const editRouteRef: SubRouteRef; @@ -156,9 +152,7 @@ export interface LayoutOptions

{ // @public export type LayoutTemplate = FormProps['ObjectFieldTemplate']; -// Warning: (ae-missing-release-tag) "legacySelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public @deprecated (undocumented) +// @public (undocumented) export const legacySelectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:templateName'> >; @@ -186,8 +180,6 @@ export type LogEvent = { taskId: string; }; -// Warning: (ae-missing-release-tag) "nextRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const nextRouteRef: RouteRef; @@ -239,8 +231,6 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; -// Warning: (ae-missing-release-tag) "nextSelectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const nextSelectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:namespace/:templateName'> @@ -280,8 +270,6 @@ export interface OwnerPickerUiOptions { defaultNamespace?: string | false; } -// Warning: (ae-missing-release-tag) "registerComponentRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const registerComponentRouteRef: ExternalRouteRef; @@ -323,8 +311,6 @@ export interface RepoUrlPickerUiOptions { }; } -// Warning: (ae-missing-release-tag) "rootRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const rootRouteRef: RouteRef; @@ -470,8 +456,6 @@ export interface ScaffolderGetIntegrationsListResponse { // @public export const ScaffolderLayouts: React.ComponentType; -// Warning: (ae-missing-release-tag) "scaffolderListTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const scaffolderListTaskRouteRef: SubRouteRef; @@ -545,8 +529,6 @@ export type ScaffolderTaskOutput = { [key: string]: unknown; }; -// Warning: (ae-missing-release-tag) "scaffolderTaskRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const scaffolderTaskRouteRef: SubRouteRef>; @@ -564,8 +546,6 @@ export interface ScaffolderUseTemplateSecrets { setSecrets: (input: Record) => void; } -// Warning: (ae-missing-release-tag) "selectedTemplateRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const selectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:namespace/:templateName'> @@ -602,8 +582,6 @@ export const TemplateTypePicker: () => JSX.Element | null; // @public export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; -// Warning: (ae-missing-release-tag) "viewTechDocRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const viewTechDocRouteRef: ExternalRouteRef< { diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 5e2d191443..26e25b54a2 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -19,17 +19,20 @@ import { createSubRouteRef, } from '@backstage/core-plugin-api'; +/** @public */ export const registerComponentRouteRef = createExternalRouteRef({ id: 'register-component', optional: true, }); +/** @public */ export const viewTechDocRouteRef = createExternalRouteRef({ id: 'view-techdoc', optional: true, params: ['namespace', 'kind', 'name'], }); +/** @public */ export const rootRouteRef = createRouteRef({ id: 'scaffolder', }); @@ -37,28 +40,33 @@ export const rootRouteRef = createRouteRef({ /** * @deprecated This is the old template route, can be deleted before next major release */ +/** @public */ export const legacySelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/legacy/selected-template', parent: rootRouteRef, path: '/templates/:templateName', }); +/** @public */ export const nextRouteRef = createRouteRef({ id: 'scaffolder/next', }); +/** @public */ export const selectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/selected-template', parent: rootRouteRef, path: '/templates/:namespace/:templateName', }); +/** @public */ export const nextSelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/next/selected-template', parent: nextRouteRef, path: '/templates/:namespace/:templateName', }); +/** @public */ export const scaffolderTaskRouteRef = createSubRouteRef({ id: 'scaffolder/task', parent: rootRouteRef, @@ -71,18 +79,21 @@ export const nextScaffolderTaskRouteRef = createSubRouteRef({ path: '/tasks/:taskId', }); +/** @public */ export const scaffolderListTaskRouteRef = createSubRouteRef({ id: 'scaffolder/list-tasks', parent: rootRouteRef, path: '/tasks', }); +/** @public */ export const actionsRouteRef = createSubRouteRef({ id: 'scaffolder/actions', parent: rootRouteRef, path: '/actions', }); +/** @public */ export const editRouteRef = createSubRouteRef({ id: 'scaffolder/edit', parent: rootRouteRef, From b6640cd762aec813d537b51b23046e0a2342bb8a Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Fri, 30 Sep 2022 15:12:19 +0200 Subject: [PATCH 051/221] export selected routes only Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 2 +- plugins/scaffolder/api-report.md | 34 ++--------------------------- plugins/scaffolder/src/index.ts | 7 +++++- plugins/scaffolder/src/routes.ts | 11 ++-------- 4 files changed, 11 insertions(+), 43 deletions(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 75af6bcc70..1afcc5730a 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': patch --- -Added props to override default Scaffolder page title, subtitle and pageTitleOverride +Added props to override default Scaffolder page title, subtitle and pageTitleOverride. diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 5931e127f5..e759049233 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -44,9 +44,6 @@ export function createNextScaffolderFieldExtension< options: NextFieldExtensionOptions, ): Extension>; -// @public (undocumented) -export const actionsRouteRef: SubRouteRef; - // @public export function createScaffolderFieldExtension< TReturnValue = unknown, @@ -69,9 +66,6 @@ export type CustomFieldValidator = ( }, ) => void | Promise; -// @public (undocumented) -export const editRouteRef: SubRouteRef; - // @public export const EntityNamePickerFieldExtension: FieldExtensionComponent< string, @@ -152,11 +146,6 @@ export interface LayoutOptions

{ // @public export type LayoutTemplate = FormProps['ObjectFieldTemplate']; -// @public (undocumented) -export const legacySelectedTemplateRouteRef: SubRouteRef< - PathParams<'/templates/:templateName'> ->; - // @public export type ListActionsResponse = Array<{ id: string; @@ -180,7 +169,7 @@ export type LogEvent = { taskId: string; }; -// @public (undocumented) +// @alpha (undocumented) export const nextRouteRef: RouteRef; // @alpha @@ -231,7 +220,7 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; -// @public (undocumented) +// @alpha (undocumented) export const nextSelectedTemplateRouteRef: SubRouteRef< PathParams<'/templates/:namespace/:templateName'> >; @@ -270,9 +259,6 @@ export interface OwnerPickerUiOptions { defaultNamespace?: string | false; } -// @public (undocumented) -export const registerComponentRouteRef: ExternalRouteRef; - // @public export const repoPickerValidation: ( value: string, @@ -456,9 +442,6 @@ export interface ScaffolderGetIntegrationsListResponse { // @public export const ScaffolderLayouts: React.ComponentType; -// @public (undocumented) -export const scaffolderListTaskRouteRef: SubRouteRef; - // @public (undocumented) export type ScaffolderOutputLink = { title?: string; @@ -529,9 +512,6 @@ export type ScaffolderTaskOutput = { [key: string]: unknown; }; -// @public (undocumented) -export const scaffolderTaskRouteRef: SubRouteRef>; - // @public export type ScaffolderTaskStatus = | 'open' @@ -581,14 +561,4 @@ export const TemplateTypePicker: () => JSX.Element | null; // @public export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; - -// @public (undocumented) -export const viewTechDocRouteRef: ExternalRouteRef< - { - name: string; - kind: string; - namespace: string; - }, - true ->; ``` diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 440c87c363..7853667101 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -61,7 +61,12 @@ export { scaffolderPlugin, } from './plugin'; export * from './components'; -export * from './routes'; +export { + rootRouteRef, + nextRouteRef, + selectedTemplateRouteRef, + nextSelectedTemplateRouteRef, +} from './routes'; export type { TaskPageProps } from './components/TaskPage'; /** next exports */ diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 26e25b54a2..7451a3a943 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -19,13 +19,11 @@ import { createSubRouteRef, } from '@backstage/core-plugin-api'; -/** @public */ export const registerComponentRouteRef = createExternalRouteRef({ id: 'register-component', optional: true, }); -/** @public */ export const viewTechDocRouteRef = createExternalRouteRef({ id: 'view-techdoc', optional: true, @@ -40,14 +38,13 @@ export const rootRouteRef = createRouteRef({ /** * @deprecated This is the old template route, can be deleted before next major release */ -/** @public */ export const legacySelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/legacy/selected-template', parent: rootRouteRef, path: '/templates/:templateName', }); -/** @public */ +/** @alpha */ export const nextRouteRef = createRouteRef({ id: 'scaffolder/next', }); @@ -59,14 +56,13 @@ export const selectedTemplateRouteRef = createSubRouteRef({ path: '/templates/:namespace/:templateName', }); -/** @public */ +/** @alpha */ export const nextSelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/next/selected-template', parent: nextRouteRef, path: '/templates/:namespace/:templateName', }); -/** @public */ export const scaffolderTaskRouteRef = createSubRouteRef({ id: 'scaffolder/task', parent: rootRouteRef, @@ -79,21 +75,18 @@ export const nextScaffolderTaskRouteRef = createSubRouteRef({ path: '/tasks/:taskId', }); -/** @public */ export const scaffolderListTaskRouteRef = createSubRouteRef({ id: 'scaffolder/list-tasks', parent: rootRouteRef, path: '/tasks', }); -/** @public */ export const actionsRouteRef = createSubRouteRef({ id: 'scaffolder/actions', parent: rootRouteRef, path: '/actions', }); -/** @public */ export const editRouteRef = createSubRouteRef({ id: 'scaffolder/edit', parent: rootRouteRef, From dec322b87158e31cdb86f90d262663637e77cae0 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Tue, 4 Oct 2022 18:45:51 +0200 Subject: [PATCH 052/221] Update .changeset/sixty-islands-develop.md Co-authored-by: Ben Lambert Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 1afcc5730a..3bdcd344b1 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder': patch --- -Added props to override default Scaffolder page title, subtitle and pageTitleOverride. +Added props to override default Scaffolder page `title`, `subtitle` and `pageTitleOverride`. From ca0cbd5a022259e11a67b48ddda7f554dd6ed4f2 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 12 Oct 2022 15:16:33 +0200 Subject: [PATCH 053/221] updated changeset Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 3bdcd344b1..9fbe2b2edd 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -1,5 +1,11 @@ --- -'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder': minor --- +<<<<<<< Updated upstream Added props to override default Scaffolder page `title`, `subtitle` and `pageTitleOverride`. +======= +Added props to override default Scaffolder page title, subtitle and pageTitleOverride. +Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component) + +> > > > > > > Stashed changes From afb13576c9060d35a660923b8d0082314f187106 Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Thu, 13 Oct 2022 14:49:37 +0200 Subject: [PATCH 054/221] fixed changeset Signed-off-by: Alex Rybchenko --- .changeset/sixty-islands-develop.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md index 9fbe2b2edd..9eb36cc1a9 100644 --- a/.changeset/sixty-islands-develop.md +++ b/.changeset/sixty-islands-develop.md @@ -2,10 +2,5 @@ '@backstage/plugin-scaffolder': minor --- -<<<<<<< Updated upstream -Added props to override default Scaffolder page `title`, `subtitle` and `pageTitleOverride`. -======= Added props to override default Scaffolder page title, subtitle and pageTitleOverride. -Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component) - -> > > > > > > Stashed changes +Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component). From 5e2347ef682dabaecf1c7fd92fdf915c975a7e8d Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Thu, 13 Oct 2022 15:22:58 +0200 Subject: [PATCH 055/221] updated api report Signed-off-by: Alex Rybchenko --- plugins/scaffolder/api-report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index e759049233..158c75e5bd 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -169,9 +169,6 @@ export type LogEvent = { taskId: string; }; -// @alpha (undocumented) -export const nextRouteRef: RouteRef; - // @alpha export type NextCustomFieldValidator = ( data: TFieldReturnValue, @@ -204,6 +201,9 @@ export type NextFieldExtensionOptions< validation?: NextCustomFieldValidator; }; +// @alpha (undocumented) +export const nextRouteRef: RouteRef; + // @alpha export type NextRouterProps = { components?: { From 6aec3eb1b04a11c793c6d0ef620fc99b279ea7af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 14 Oct 2022 15:22:26 +0100 Subject: [PATCH 056/221] initial REVIEWING.md Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 REVIEWING.md diff --git a/REVIEWING.md b/REVIEWING.md new file mode 100644 index 0000000000..9b21b49d51 --- /dev/null +++ b/REVIEWING.md @@ -0,0 +1,107 @@ +# Introduction + +This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to the repository as well. + +## Code Style + +See [STYLE.md](./STYLE.md). + +## Secure Coding Practices + +Be sure to familiarize yourself with our [secure coding practices](./SECURITY.md#coding-practices). + +## Changesets + +We use changesets to track the changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. +An introduction to changesets can be found in our [contribution guidelines](./CONTRIBUTING.md#creating-changesets). + +When reviewing a changeset, the most important things to look for are the bump level, i.e. `major` / `minor` / `patch`, and whether the content is accurate and if it's written in a way that makes sense when reading it in the changelog for each package. + +### Reviewing Changeset Bump Levels + +### Reviewing Changeset Content + +Each changeset should be written in a way that describes the impact of the change for the end user of each package. The changesets end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. + +Some things that changeset should NOT contain are: + +- Internal architecture details - these are generally not interesting to end users, focus on the impact towards end users instead. +- Information related to a different package. +- A large amount of content, consider for example a separate migration guide instead, either in the package README or [./docs/](./docs/), and then link to that instead. +- Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). + +### When is a changeset needed? + +In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also + +Changes that do NOT need a new changeset: + +- Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. +- When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset. +- Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. +- Changes that do not end up having an effect on the published package can be skipped, such as whitespace fixes or code formatting changes. It is also alright to include a short changeset for these kind of changes too. + +### Changeset Examples + +**Example 1** + +A new `EntityList` component has been added to `plugins/catalog-react`. + +#### GOOD + +```md +--- +'@backstage/plugin-catalog-react': minor +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. +``` + +The Catalog React library has reached version `1.x`, which means that feature additions that aren't breaking should be a `minor` change. We don't bother with too much documentation, keeping it short and sweet. The main purpose is to inform users that this new component exists and to give them an idea of how they can use it. + +#### BAD + +```md +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog': minor +--- + +Added `EntityList` component. +Fixed a bug in the catalog index page. +``` + +This changeset is too short, it's best to give users an idea of how they can benefit from the new addition. + +It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changeset for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. + +```md +--- +'@backstage/plugin-catalog-react': major +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. The component looks like this: + +![EntityList screenshot](./screenshot.png) + +It accepts the following properties: + +- entities - The entities that should be listed. +- title - An optional formatting function for the list titles. +- dialog - An optional component that overrides the default details dialog. +``` + +This changeset is getting too detailed. It's not always bad to get this much into the weeds, but keep in mind that changesets are not easy to browse when search for information about specific APIs. It's better to document things like this separately and keep the changeset more lean. Also avoid linking to assets in changesets, keep them text-only. + +The change is also marked as a breaking `major` change. This should be changed to `minor` since adding new APIs is never a breaking change. + +## Review Checklist + +- [ ] API Reports + - [ ] Naming + - [ ] Breaking changes +- [ ] Changesets + - [ ] Content + - [ ] Bump level +- [ ] Have tests been added for new features bug fixes? +- [ ] Has documentation been added? From 3934e83101e6ef5df2afc6e76a3526e53e4a831e Mon Sep 17 00:00:00 2001 From: Chris Langhout Date: Mon, 17 Oct 2022 17:25:13 +0200 Subject: [PATCH 057/221] chore: update bol.com contacts Signed-off-by: Chris Langhout --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 574623cd14..b6efa67836 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -6,7 +6,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | Organization | Contact | Description of Use | | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [bol.com](https://www.bol.com) | [@acierto](https://github.com/acierto), [@clanghout](https://github.com/clanghout) | Initial work being done to unify platform tooling. | | [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | | [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | | [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | From 7f93a25ac7e0825e29d17a3f8c4b960287f2a021 Mon Sep 17 00:00:00 2001 From: Leon van Ginneken Date: Tue, 18 Oct 2022 13:46:40 +0200 Subject: [PATCH 058/221] Update .changeset/tame-ads-appear.md Co-authored-by: Johan Haals Signed-off-by: Leon van Ginneken --- .changeset/tame-ads-appear.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tame-ads-appear.md b/.changeset/tame-ads-appear.md index 00eb4a7dcd..3f7a564c71 100644 --- a/.changeset/tame-ads-appear.md +++ b/.changeset/tame-ads-appear.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-tech-insights-backend': minor +'@backstage/plugin-tech-insights-backend': patch --- Add a default delay to the fact retrievers to prevent cold-start errors From 114614a22c880d60fbfbdf122de1b83f92499114 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 11:58:27 +0000 Subject: [PATCH 059/221] Update dependency @changesets/cli to v2.25.0 Signed-off-by: Renovate Bot --- yarn.lock | 166 +++++++++++++++++++++++++++--------------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/yarn.lock b/yarn.lock index 47b6d4247a..d944768ad9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7543,15 +7543,15 @@ __metadata: languageName: node linkType: hard -"@changesets/apply-release-plan@npm:^6.1.0": - version: 6.1.0 - resolution: "@changesets/apply-release-plan@npm:6.1.0" +"@changesets/apply-release-plan@npm:^6.1.1": + version: 6.1.1 + resolution: "@changesets/apply-release-plan@npm:6.1.1" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/config": ^2.1.1 + "@changesets/config": ^2.2.0 "@changesets/get-version-range-type": ^0.3.2 - "@changesets/git": ^1.4.1 - "@changesets/types": ^5.1.0 + "@changesets/git": ^1.5.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 detect-indent: ^6.0.0 fs-extra: ^7.0.1 @@ -7560,51 +7560,51 @@ __metadata: prettier: ^2.7.1 resolve-from: ^5.0.0 semver: ^5.4.1 - checksum: dbbe5301ba6b3651c788c25e9ae73a765ce8332afb013b417b91004b63be608ee898cbf1916a1e40d9d4ef11bfbe46eefe3e39524f42ef54d8a6eb615fffb292 + checksum: 34da2d52eced00bc5f51c8e8ce16c0e487743219f057da2b3e6c6597bb2d50498f3996c779e4fc29b19d357c9b700d82310d723395ba09350b9a139c9e0e0d23 languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^5.2.1": - version: 5.2.1 - resolution: "@changesets/assemble-release-plan@npm:5.2.1" +"@changesets/assemble-release-plan@npm:^5.2.2": + version: 5.2.2 + resolution: "@changesets/assemble-release-plan@npm:5.2.2" dependencies: "@babel/runtime": ^7.10.4 "@changesets/errors": ^0.1.4 - "@changesets/get-dependents-graph": ^1.3.3 - "@changesets/types": ^5.1.0 + "@changesets/get-dependents-graph": ^1.3.4 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 semver: ^5.4.1 - checksum: 34e00f9ac98163cd81338962ce5cf27e7a8ecbd60a5040e245976fba87336aa7efdd6c54cc2112db0cda2099fefc3ad37bd24d29307a30e49a515a3279022d74 + checksum: 4f4a2108537ecbfbf9a554e441f4899f9c6d7e2b933deddd186d6f670ea1e52ebbbef3da01bc61b5cb735470536258b94d7bbac1035fe98dc277615d5017c61a languageName: node linkType: hard -"@changesets/changelog-git@npm:^0.1.12": - version: 0.1.12 - resolution: "@changesets/changelog-git@npm:0.1.12" +"@changesets/changelog-git@npm:^0.1.13": + version: 0.1.13 + resolution: "@changesets/changelog-git@npm:0.1.13" dependencies: - "@changesets/types": ^5.1.0 - checksum: 68ae2ccb7e18f4559c954bcc104cd2e311c6daf5a39e9339290c0e1db8db926a1432e54485c593d977083253736147d855e923053d50daaef1041aa486172538 + "@changesets/types": ^5.2.0 + checksum: c0e3b11a0a63794304e064ef01444503a864a1fbfc4c592bd3aec91ba6aa5c24fac91a47f7f0159f449cd6ce99c334290b465d84b1c98b6577937ff55974cc7e languageName: node linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.24.4 - resolution: "@changesets/cli@npm:2.24.4" + version: 2.25.0 + resolution: "@changesets/cli@npm:2.25.0" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/apply-release-plan": ^6.1.0 - "@changesets/assemble-release-plan": ^5.2.1 - "@changesets/changelog-git": ^0.1.12 - "@changesets/config": ^2.1.1 + "@changesets/apply-release-plan": ^6.1.1 + "@changesets/assemble-release-plan": ^5.2.2 + "@changesets/changelog-git": ^0.1.13 + "@changesets/config": ^2.2.0 "@changesets/errors": ^0.1.4 - "@changesets/get-dependents-graph": ^1.3.3 - "@changesets/get-release-plan": ^3.0.14 - "@changesets/git": ^1.4.1 + "@changesets/get-dependents-graph": ^1.3.4 + "@changesets/get-release-plan": ^3.0.15 + "@changesets/git": ^1.5.0 "@changesets/logger": ^0.0.5 - "@changesets/pre": ^1.0.12 - "@changesets/read": ^0.5.7 - "@changesets/types": ^5.1.0 - "@changesets/write": ^0.2.0 + "@changesets/pre": ^1.0.13 + "@changesets/read": ^0.5.8 + "@changesets/types": ^5.2.0 + "@changesets/write": ^0.2.1 "@manypkg/get-packages": ^1.1.3 "@types/is-ci": ^3.0.0 "@types/semver": ^6.0.0 @@ -7626,22 +7626,22 @@ __metadata: tty-table: ^4.1.5 bin: changeset: bin.js - checksum: 899e0f3b075e12d28f71ed05e385c642ce9d56de06f9f60da346c15004612c8629c4619bcfaca860c9a947cfb3f0df904ea6cc080ff4de5c60c814665771f4ab + checksum: 54439bdfa7ca115964482f6323e9475b5917d68c2e0752a272ed133b863fce51eeba86366d8740a275dc0b891af8e272602da69d04144f40e4bf3531f48fe8fb languageName: node linkType: hard -"@changesets/config@npm:^2.1.1": - version: 2.1.1 - resolution: "@changesets/config@npm:2.1.1" +"@changesets/config@npm:^2.2.0": + version: 2.2.0 + resolution: "@changesets/config@npm:2.2.0" dependencies: "@changesets/errors": ^0.1.4 - "@changesets/get-dependents-graph": ^1.3.3 + "@changesets/get-dependents-graph": ^1.3.4 "@changesets/logger": ^0.0.5 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 fs-extra: ^7.0.1 micromatch: ^4.0.2 - checksum: 12b47efd06f9ca19f00a170517dad0e2041c2f285eff308c37e955893d24e981addc941ae545e23f9507628674ef2b9321d7423fc495bc17d00ea09066d68fc7 + checksum: 18a6ae52150a7426bcaeb4018f7eb4e330dec69a448b093d604f1cd73d89b689eb791777cee5af57f9e572403ccbf196a572b33dbb702bae4bccfbbbd3be5ffc languageName: node linkType: hard @@ -7654,31 +7654,31 @@ __metadata: languageName: node linkType: hard -"@changesets/get-dependents-graph@npm:^1.3.3": - version: 1.3.3 - resolution: "@changesets/get-dependents-graph@npm:1.3.3" +"@changesets/get-dependents-graph@npm:^1.3.4": + version: 1.3.4 + resolution: "@changesets/get-dependents-graph@npm:1.3.4" dependencies: - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 chalk: ^2.1.0 fs-extra: ^7.0.1 semver: ^5.4.1 - checksum: 0ccde4d7e233fef875447e08219da45eb3cd9b073124b6ce051b35ed3fac89ce2cb185fce41eae1ed2a41aa741cd9f999f5384c5c76086c72c5b5e7609716fd9 + checksum: 584852e17fd102271dea520f851c85130605f232f7f52126d202b0041269af12d5681744bfa7fecf41048e5fd62a71da726be471207832060408d9cfb35ec3fb languageName: node linkType: hard -"@changesets/get-release-plan@npm:^3.0.14": - version: 3.0.14 - resolution: "@changesets/get-release-plan@npm:3.0.14" +"@changesets/get-release-plan@npm:^3.0.15": + version: 3.0.15 + resolution: "@changesets/get-release-plan@npm:3.0.15" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/assemble-release-plan": ^5.2.1 - "@changesets/config": ^2.1.1 - "@changesets/pre": ^1.0.12 - "@changesets/read": ^0.5.7 - "@changesets/types": ^5.1.0 + "@changesets/assemble-release-plan": ^5.2.2 + "@changesets/config": ^2.2.0 + "@changesets/pre": ^1.0.13 + "@changesets/read": ^0.5.8 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 - checksum: 32ea990b58b9c138ec0e335b0ee69e4c4e7ef802adcf28a5b843d25cb1155d68d7621063fbb18ea200bcc20112575df902722792b6428431b8ee30b8bb6b8b77 + checksum: f2d33982dfeabe12e0eba976a3cc68e05bdd834b4dd6283f42464d028852e46f90cb14b84f0b5425813c787494d0490dc12df5d25d63559d07f47f9a7cb87c12 languageName: node linkType: hard @@ -7689,17 +7689,17 @@ __metadata: languageName: node linkType: hard -"@changesets/git@npm:^1.4.1": - version: 1.4.1 - resolution: "@changesets/git@npm:1.4.1" +"@changesets/git@npm:^1.5.0": + version: 1.5.0 + resolution: "@changesets/git@npm:1.5.0" dependencies: "@babel/runtime": ^7.10.4 "@changesets/errors": ^0.1.4 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 is-subdir: ^1.1.1 spawndamnit: ^2.0.0 - checksum: 9b710d532a1816a2540fe4bc4212e13acb102f39f3b49d23396cad9b3fcae8bfe3f838902a9279a5b0b3e4533ca0965ffb169f0bebeb8b4933a48965679ed991 + checksum: 7208d5bff9c584aa752ca6f647ba5e97356213d00c88cce65c24c6bab1b3837c14f8977c2384a7fecb81e20a0586d4cc1fddb408a38e7dd818ef18377d01ee54 languageName: node linkType: hard @@ -7712,42 +7712,42 @@ __metadata: languageName: node linkType: hard -"@changesets/parse@npm:^0.3.14": - version: 0.3.14 - resolution: "@changesets/parse@npm:0.3.14" +"@changesets/parse@npm:^0.3.15": + version: 0.3.15 + resolution: "@changesets/parse@npm:0.3.15" dependencies: - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 js-yaml: ^3.13.1 - checksum: 4234e93ba3081fc30ec6ed3425cb2f4ac9e69fec0fc90d6d0438d850840c247f7bc9365b78a5295a6084c4cdba1c657c1bc4acd2f666dc0097741f0933111cf7 + checksum: 1e17f494954140d7885f4be76ac7708c19930f08ecf0b58bcee09160ad6e59223da8899df0709a78e5b5fa419f0d60eccbb34bf0c11d564e24f766c4654e3384 languageName: node linkType: hard -"@changesets/pre@npm:^1.0.12": - version: 1.0.12 - resolution: "@changesets/pre@npm:1.0.12" +"@changesets/pre@npm:^1.0.13": + version: 1.0.13 + resolution: "@changesets/pre@npm:1.0.13" dependencies: "@babel/runtime": ^7.10.4 "@changesets/errors": ^0.1.4 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 fs-extra: ^7.0.1 - checksum: 6a81027feaca4727b2809410ec246481a324b4230e0f06b6add52ac2f28f8d7cb9b72cea56b2cfb64a15eb016fb6923bad49c3f2e767ecd2b30b13357a75a0da + checksum: f1cc5721546c66977a2fc62428aae9d62f78a04aefac224ac7014172807afa9d07f6da1e326d0a14b1ff12840b0379a7ec24e7984839deb337cd203b63c24b1d languageName: node linkType: hard -"@changesets/read@npm:^0.5.7": - version: 0.5.7 - resolution: "@changesets/read@npm:0.5.7" +"@changesets/read@npm:^0.5.8": + version: 0.5.8 + resolution: "@changesets/read@npm:0.5.8" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/git": ^1.4.1 + "@changesets/git": ^1.5.0 "@changesets/logger": ^0.0.5 - "@changesets/parse": ^0.3.14 - "@changesets/types": ^5.1.0 + "@changesets/parse": ^0.3.15 + "@changesets/types": ^5.2.0 chalk: ^2.1.0 fs-extra: ^7.0.1 p-filter: ^2.1.0 - checksum: 5c9ea7f4ad1391e971ba7eea4e64fd2c1ad2b00db2884a35ec262fa356fc9c72f813ea1c077c7ce904cd4ce2e97b3599d21116df1c0452f7d240c0af71a34f84 + checksum: cc32c5a3366be58c5b00988940eb6677898814de7ce60a3c4785d064e0df7563627f8dcfa0620f3ef9cea6f328ce538b2ba7d430c35c883f212b71a94026b2d8 languageName: node linkType: hard @@ -7758,23 +7758,23 @@ __metadata: languageName: node linkType: hard -"@changesets/types@npm:^5.1.0": - version: 5.1.0 - resolution: "@changesets/types@npm:5.1.0" - checksum: fa903b53d5cc0ce1bd6054fd6c0053af54f604ade2c6436e735a7a66e4f31f7757ec324ceed3355ccd7f0653174fe8b51e1b3850a0b0d80315d13d92a45185b2 +"@changesets/types@npm:^5.2.0": + version: 5.2.0 + resolution: "@changesets/types@npm:5.2.0" + checksum: 579cf8bd2d3a03f293871976d8641531667527f248dc29310a70928d6400cef5df3d09e75beeb2ccf5d384fa1f294f0a2db243c6ebf53913d1f67e283e826f91 languageName: node linkType: hard -"@changesets/write@npm:^0.2.0": - version: 0.2.0 - resolution: "@changesets/write@npm:0.2.0" +"@changesets/write@npm:^0.2.1": + version: 0.2.1 + resolution: "@changesets/write@npm:0.2.1" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 fs-extra: ^7.0.1 human-id: ^1.0.2 prettier: ^2.7.1 - checksum: 0cc09ddc0669752f6856d3fcc1d609ad7dcd5f464c94501be7b406f2c00013bcec486a422cd09006a2eb0c2bdfe78dd1e44b37202fc7925dea9b855fead8c88b + checksum: 98b4d9c12fe13177860407557979d475361076a596103895440f52b2724f7004d6c98af39105fda0eaa52ceca8c0dc0ec9c8ab10eec7a0cb9bf83301f2ca48b3 languageName: node linkType: hard From fc464e3546f99161a3cdba1dec0fa89c63917175 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Oct 2022 14:24:59 +0000 Subject: [PATCH 060/221] Update dependency @codemirror/view to v6.4.0 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 47b6d4247a..4b0fa71f6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7872,13 +7872,13 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0": - version: 6.3.0 - resolution: "@codemirror/view@npm:6.3.0" + version: 6.4.0 + resolution: "@codemirror/view@npm:6.4.0" dependencies: "@codemirror/state": ^6.0.0 style-mod: ^4.0.0 w3c-keyname: ^2.2.4 - checksum: 8fe39a1c47799b9bca8b815d7fd5c33bcde06cc8b98c26c87729ccca6d5afbc005671c8b00d8678209bcfc079f6894f4e61f26618d8b8133a79befa0828c789f + checksum: 57ed7d9d51907f1ea549a2a158872fb7affa6cdff72e29e214f8437b7142ea2a2431ac3c780cbc0187dfff7cb5fd1055a45a887ea9b8c2a09d8430c60ebe9083 languageName: node linkType: hard From 646426303efad4de265a73ca1363bfff204e5650 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Tue, 18 Oct 2022 16:38:37 +0200 Subject: [PATCH 061/221] update api-report.md Signed-off-by: Marko Simon --- plugins/ilert/api-report.md | 420 +++++++++++++++++++++++------------- 1 file changed, 265 insertions(+), 155 deletions(-) diff --git a/plugins/ilert/api-report.md b/plugins/ilert/api-report.md index 96576a8358..86ea698921 100644 --- a/plugins/ilert/api-report.md +++ b/plugins/ilert/api-report.md @@ -16,11 +16,96 @@ import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) export const ACCEPTED = 'ACCEPTED'; +// @public (undocumented) +export interface Alert { + // (undocumented) + alertKey: string; + // (undocumented) + alertSource: AlertSource | null; + // (undocumented) + assignedTo: User | null; + // (undocumented) + commentPublishToSubscribers: boolean; + // (undocumented) + commentText: string; + // (undocumented) + details: string; + // (undocumented) + id: number; + // (undocumented) + images: Image_2[]; + // (undocumented) + links: Link[]; + // (undocumented) + logEntries: LogEntry[]; + // (undocumented) + priority: AlertPriority; + // (undocumented) + reportTime: string; + // (undocumented) + resolvedOn: string; + // (undocumented) + responders: Responder[]; + // (undocumented) + status: AlertStatus; + // (undocumented) + subscribers: Subscriber[]; + // (undocumented) + summary: string; +} + +// @public (undocumented) +export interface AlertAction { + // (undocumented) + extensionId?: string; + // (undocumented) + history?: AlertActionHistory[]; + // (undocumented) + name: string; + // (undocumented) + type: string; + // (undocumented) + webhookId: string; +} + +// @public (undocumented) +export interface AlertActionHistory { + // (undocumented) + actor: User; + // (undocumented) + alertId: number; + // (undocumented) + id: string; + // (undocumented) + success: boolean; + // (undocumented) + webhookId: string; +} + +// @public (undocumented) +export type AlertPriority = 'HIGH' | 'LOW'; + +// @public (undocumented) +export interface AlertResponder { + // (undocumented) + disabled: boolean; + // (undocumented) + group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; + // (undocumented) + id: number; + // (undocumented) + name: string; +} + // @public (undocumented) export interface AlertSource { // (undocumented) active?: boolean; // (undocumented) + alertCreation?: AlertSourceAlertCreation; + // (undocumented) + alertPriorityRule?: AlertSourceAlertPriorityRule; + // (undocumented) autoResolutionTimeout?: string; // (undocumented) autotaskMetadata?: AlertSourceAutotaskMetadata; @@ -45,10 +130,6 @@ export interface AlertSource { // (undocumented) id: number; // (undocumented) - incidentCreation?: AlertSourceIncidentCreation; - // (undocumented) - incidentPriorityRule?: AlertSourceIncidentPriorityRule; - // (undocumented) integrationKey?: string; // (undocumented) integrationType: AlertSourceIntegrationType; @@ -66,6 +147,21 @@ export interface AlertSource { teams: TeamShort[]; } +// @public (undocumented) +export type AlertSourceAlertCreation = + | 'ONE_ALERT_PER_EMAIL' + | 'ONE_ALERT_PER_EMAIL_SUBJECT' + | 'ONE_PENDING_ALERT_ALLOWED' + | 'ONE_OPEN_ALERT_ALLOWED' + | 'OPEN_RESOLVE_ON_EXTRACTION'; + +// @public (undocumented) +export type AlertSourceAlertPriorityRule = + | 'HIGH' + | 'LOW' + | 'HIGH_DURING_SUPPORT_HOURS' + | 'LOW_DURING_SUPPORT_HOURS'; + // @public (undocumented) export interface AlertSourceAutotaskMetadata { // (undocumented) @@ -109,21 +205,6 @@ export interface AlertSourceHeartbeat { summary: string; } -// @public (undocumented) -export type AlertSourceIncidentCreation = - | 'ONE_INCIDENT_PER_EMAIL' - | 'ONE_INCIDENT_PER_EMAIL_SUBJECT' - | 'ONE_PENDING_INCIDENT_ALLOWED' - | 'ONE_OPEN_INCIDENT_ALLOWED' - | 'OPEN_RESOLVE_ON_EXTRACTION'; - -// @public (undocumented) -export type AlertSourceIncidentPriorityRule = - | 'HIGH' - | 'LOW' - | 'HIGH_DURING_SUPPORT_HOURS' - | 'LOW_DURING_SUPPORT_HOURS'; - // @public (undocumented) export type AlertSourceIntegrationType = | 'NAGIOS' @@ -192,7 +273,7 @@ export interface AlertSourceSupportDay { // @public (undocumented) export interface AlertSourceSupportHours { // (undocumented) - autoRaiseIncidents: boolean; + autoRaiseAlerts: boolean; // (undocumented) supportDays: { MONDAY: AlertSourceSupportDay; @@ -214,6 +295,12 @@ export type AlertSourceTimeZone = | 'America/Los_Angeles' | 'Asia/Istanbul'; +// @public (undocumented) +export type AlertStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; + +// @public (undocumented) +export const DEGRADED = 'DEGRADED'; + // @public (undocumented) export const EntityILertCard: () => JSX.Element; @@ -255,71 +342,94 @@ export type EventRequest = { }; // @public (undocumented) -export type GetIncidentsCountOpts = { - states?: IncidentStatus[]; +export type GetAlertsCountOpts = { + states?: AlertStatus[]; }; // @public (undocumented) -export type GetIncidentsOpts = { +export type GetAlertsOpts = { maxResults?: number; startIndex?: number; - states?: IncidentStatus[]; + states?: AlertStatus[]; alertSources?: number[]; }; +// @public (undocumented) +export type GetServicesOpts = { + maxResults?: number; + startIndex?: number; +}; + +// @public (undocumented) +export type GetStatusPagesOpts = { + maxResults?: number; + startIndex?: number; +}; + // @public (undocumented) export interface ILertApi { // (undocumented) - acceptIncident(incident: Incident, userName: string): Promise; + acceptAlert(alert: Alert, userName: string): Promise; // (undocumented) addImmediateMaintenance( alertSourceId: number, minutes: number, ): Promise; // (undocumented) - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; // (undocumented) - createIncident(eventRequest: EventRequest): Promise; + createAlert(eventRequest: EventRequest): Promise; + // Warning: (ae-forgotten-export) The symbol "ServiceRequest" needs to be exported by the entry point index.d.ts + // + // (undocumented) + createService(eventRequest: ServiceRequest): Promise; // (undocumented) disableAlertSource(alertSource: AlertSource): Promise; // (undocumented) enableAlertSource(alertSource: AlertSource): Promise; // (undocumented) + fetchAlert(id: number): Promise; + // (undocumented) + fetchAlertActions(alert: Alert): Promise; + // (undocumented) + fetchAlertResponders(alert: Alert): Promise; + // (undocumented) + fetchAlerts(opts?: GetAlertsOpts): Promise; + // (undocumented) + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + // (undocumented) fetchAlertSource(idOrIntegrationKey: number | string): Promise; // (undocumented) fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; // (undocumented) fetchAlertSources(): Promise; // (undocumented) - fetchIncident(id: number): Promise; - // (undocumented) - fetchIncidentActions(incident: Incident): Promise; - // (undocumented) - fetchIncidentResponders(incident: Incident): Promise; - // (undocumented) - fetchIncidents(opts?: GetIncidentsOpts): Promise; - // (undocumented) - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - // (undocumented) fetchOnCallSchedules(): Promise; // (undocumented) + fetchServices(opts?: GetServicesOpts): Promise; + // (undocumented) + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; + // (undocumented) fetchUptimeMonitor(id: number): Promise; // (undocumented) fetchUptimeMonitors(): Promise; // (undocumented) fetchUsers(): Promise; // (undocumented) + getAlertDetailsURL(alert: Alert): string; + // (undocumented) getAlertSourceDetailsURL(alertSource: AlertSource | null): string; // (undocumented) getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; // (undocumented) - getIncidentDetailsURL(incident: Incident): string; - // (undocumented) getScheduleDetailsURL(schedule: Schedule): string; // (undocumented) + getServiceDetailsURL(service: Service): string; + // (undocumented) + getStatusPageDetailsURL(statusPage: StatusPage): string; + // (undocumented) + getStatusPageURL(statusPage: StatusPage): string; + // (undocumented) getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; // (undocumented) getUserInitials(user: User | null): string; @@ -335,14 +445,11 @@ export interface ILertApi { // (undocumented) pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; // (undocumented) - resolveIncident(incident: Incident, userName: string): Promise; + resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; // (undocumented) - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; } // @public (undocumented) @@ -359,42 +466,45 @@ export class ILertClient implements ILertApi { proxyPath: string; }); // (undocumented) - acceptIncident(incident: Incident, userName: string): Promise; + acceptAlert(alert: Alert, userName: string): Promise; // (undocumented) addImmediateMaintenance( alertSourceId: number, minutes: number, ): Promise; // (undocumented) - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; // (undocumented) - createIncident(eventRequest: EventRequest): Promise; + createAlert(eventRequest: EventRequest): Promise; + // (undocumented) + createService(serviceRequest: ServiceRequest): Promise; // (undocumented) disableAlertSource(alertSource: AlertSource): Promise; // (undocumented) enableAlertSource(alertSource: AlertSource): Promise; // (undocumented) + fetchAlert(id: number): Promise; + // (undocumented) + fetchAlertActions(alert: Alert): Promise; + // (undocumented) + fetchAlertResponders(alert: Alert): Promise; + // (undocumented) + fetchAlerts(opts?: GetAlertsOpts): Promise; + // (undocumented) + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + // (undocumented) fetchAlertSource(idOrIntegrationKey: number | string): Promise; // (undocumented) fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; // (undocumented) fetchAlertSources(): Promise; // (undocumented) - fetchIncident(id: number): Promise; - // (undocumented) - fetchIncidentActions(incident: Incident): Promise; - // (undocumented) - fetchIncidentResponders(incident: Incident): Promise; - // (undocumented) - fetchIncidents(opts?: GetIncidentsOpts): Promise; - // (undocumented) - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - // (undocumented) fetchOnCallSchedules(): Promise; // (undocumented) + fetchServices(opts?: GetServicesOpts): Promise; + // (undocumented) + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; + // (undocumented) fetchUptimeMonitor(id: number): Promise; // (undocumented) fetchUptimeMonitors(): Promise; @@ -406,14 +516,20 @@ export class ILertClient implements ILertApi { discoveryApi: DiscoveryApi, ): ILertClient; // (undocumented) + getAlertDetailsURL(alert: Alert): string; + // (undocumented) getAlertSourceDetailsURL(alertSource: AlertSource | null): string; // (undocumented) getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; // (undocumented) - getIncidentDetailsURL(incident: Incident): string; - // (undocumented) getScheduleDetailsURL(schedule: Schedule): string; // (undocumented) + getServiceDetailsURL(service: Service): string; + // (undocumented) + getStatusPageDetailsURL(statusPage: StatusPage): string; + // (undocumented) + getStatusPageURL(statusPage: StatusPage): string; + // (undocumented) getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; // (undocumented) getUserInitials(user: User | null): string; @@ -429,14 +545,11 @@ export class ILertClient implements ILertApi { // (undocumented) pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; // (undocumented) - resolveIncident(incident: Incident, userName: string): Promise; + resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; // (undocumented) - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; } // @public (undocumented) @@ -470,88 +583,6 @@ interface Image_2 { } export { Image_2 as Image }; -// @public (undocumented) -export interface Incident { - // (undocumented) - alertSource: AlertSource | null; - // (undocumented) - assignedTo: User | null; - // (undocumented) - commentPublishToSubscribers: boolean; - // (undocumented) - commentText: string; - // (undocumented) - details: string; - // (undocumented) - id: number; - // (undocumented) - images: Image_2[]; - // (undocumented) - incidentKey: string; - // (undocumented) - links: Link[]; - // (undocumented) - logEntries: LogEntry[]; - // (undocumented) - priority: IncidentPriority; - // (undocumented) - reportTime: string; - // (undocumented) - resolvedOn: string; - // (undocumented) - status: IncidentStatus; - // (undocumented) - subscribers: Subscriber[]; - // (undocumented) - summary: string; -} - -// @public (undocumented) -export interface IncidentAction { - // (undocumented) - extensionId?: string; - // (undocumented) - history?: IncidentActionHistory[]; - // (undocumented) - name: string; - // (undocumented) - type: string; - // (undocumented) - webhookId: string; -} - -// @public (undocumented) -export interface IncidentActionHistory { - // (undocumented) - actor: User; - // (undocumented) - id: string; - // (undocumented) - incidentId: number; - // (undocumented) - success: boolean; - // (undocumented) - webhookId: string; -} - -// @public (undocumented) -export type IncidentPriority = 'HIGH' | 'LOW'; - -// @public (undocumented) -export interface IncidentResponder { - // (undocumented) - disabled: boolean; - // (undocumented) - group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; - // (undocumented) - id: number; - // (undocumented) - name: string; -} - -// @public (undocumented) -export type IncidentStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; - // @public (undocumented) const isPluginApplicableToEntity: (entity: Entity) => boolean; export { isPluginApplicableToEntity as isILertAvailable }; @@ -570,6 +601,8 @@ export interface Link { // @public (undocumented) export interface LogEntry { + // (undocumented) + alertId?: number; // (undocumented) filterTypes?: string[]; // (undocumented) @@ -579,8 +612,6 @@ export interface LogEntry { // (undocumented) id: number; // (undocumented) - incidentId?: number; - // (undocumented) logEntryType: string; // (undocumented) text: string; @@ -588,6 +619,9 @@ export interface LogEntry { timestamp: string; } +// @public (undocumented) +export const MAJOR_OUTAGE = 'MAJOR_OUTAGE'; + // @public (undocumented) export interface OnCall { // (undocumented) @@ -604,6 +638,12 @@ export interface OnCall { user: User; } +// @public (undocumented) +export const OPERATIONAL = 'OPERATIONAL'; + +// @public (undocumented) +export const PARTIAL_OUTAGE = 'PARTIAL_OUTAGE'; + // @public (undocumented) export const PENDING = 'PENDING'; @@ -615,9 +655,25 @@ export interface Phone { regionCode: string; } +// @public (undocumented) +export const PRIVATE = 'PRIVATE'; + +// @public (undocumented) +export const PUBLIC = 'PUBLIC'; + // @public (undocumented) export const RESOLVED = 'RESOLVED'; +// @public (undocumented) +export interface Responder { + // (undocumented) + acceptedAt?: string; + // (undocumented) + status: string; + // (undocumented) + user: User; +} + // @public (undocumented) export const Router: () => JSX.Element; @@ -643,6 +699,26 @@ export interface Schedule { timezone: string; } +// @public (undocumented) +export interface Service { + // (undocumented) + id: number; + // (undocumented) + name: string; + // (undocumented) + status: ServiceStatus; + // (undocumented) + uptime: Uptime; +} + +// @public (undocumented) +export type ServiceStatus = + | typeof OPERATIONAL + | typeof UNDER_MAINTENANCE + | typeof DEGRADED + | typeof PARTIAL_OUTAGE + | typeof MAJOR_OUTAGE; + // @public (undocumented) export interface Shift { // (undocumented) @@ -653,6 +729,25 @@ export interface Shift { user: User; } +// @public (undocumented) +export interface StatusPage { + // (undocumented) + domain: string; + // (undocumented) + id: number; + // (undocumented) + name: string; + // (undocumented) + status: ServiceStatus; + // (undocumented) + subdomain: string; + // (undocumented) + visibility: StatusPageVisibility; +} + +// @public (undocumented) +export type StatusPageVisibility = typeof PRIVATE | typeof PUBLIC; + // @public (undocumented) export interface Subscriber { // (undocumented) @@ -688,6 +783,15 @@ export interface TeamShort { name: string; } +// @public (undocumented) +export const UNDER_MAINTENANCE = 'UNDER_MAINTENANCE'; + +// @public (undocumented) +export interface Uptime { + // (undocumented) + uptimePercentage: UptimePercentage; +} + // @public (undocumented) export interface UptimeMonitor { // (undocumented) @@ -695,7 +799,7 @@ export interface UptimeMonitor { // (undocumented) checkType: 'http' | 'tcp' | 'udp' | 'ping'; // (undocumented) - createIncidentAfterFailedChecks: number; + createAlertAfterFailedChecks: number; // (undocumented) embedUrl: string; // (undocumented) @@ -732,6 +836,12 @@ export interface UptimeMonitorCheckParams { url?: string; } +// @public (undocumented) +export interface UptimePercentage { + // (undocumented) + p90: number; +} + // @public (undocumented) export interface User { // (undocumented) From aff603e4bdfbfc5f6aba60d28d02ab59ba2eb431 Mon Sep 17 00:00:00 2001 From: spencerrichardhenry <46569542+spencerrichardhenry@users.noreply.github.com> Date: Tue, 18 Oct 2022 09:22:43 -0600 Subject: [PATCH 062/221] Update plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts Co-authored-by: Patrik Oldsberg Signed-off-by: spencerrichardhenry <46569542+spencerrichardhenry@users.noreply.github.com> --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index fa0e9d8402..4f3ca032bc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -452,7 +452,7 @@ function scaffoldingTracker() { async function skipFalsy() { await task.emitLog( - `Skipping step ${step.id} because it's if condition was false`, + `Skipping step ${step.id} because its if condition was false`, { stepId: step.id, status: 'skipped' }, ); stepTimer({ result: 'skipped' }); From 51d891bcf7afec67d3e91cc02d6802253638f640 Mon Sep 17 00:00:00 2001 From: Spencer Henry Date: Tue, 18 Oct 2022 09:26:44 -0600 Subject: [PATCH 063/221] remove random commit items? Signed-off-by: Spencer Henry --- .changeset/swift-phones-cheat.md | 12 ------------ .../src/tasks/PluginTaskSchedulerImpl.test.ts | 2 +- packages/cli/src/lib/bundler/config.ts | 2 +- 3 files changed, 2 insertions(+), 14 deletions(-) delete mode 100644 .changeset/swift-phones-cheat.md diff --git a/.changeset/swift-phones-cheat.md b/.changeset/swift-phones-cheat.md deleted file mode 100644 index 6d1653caa3..0000000000 --- a/.changeset/swift-phones-cheat.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Removed `tsx` and `jsx` as supported extensions in backend packages. For most -repos, this will not have any effect. But if you inadvertently had added some -`tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for -this is that the offending files get ignored during transpilation. Hence, the -importing file can no longer find anything to import. - -The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, -or `.jsx` to `.js`. diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts index d61948a929..f6177b57f7 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -45,7 +45,7 @@ describe('PluginTaskManagerImpl', () => { ); jest.useFakeTimers(); - }, 60_000); + }, 30_000); async function init(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 1974153f02..7ce9499f45 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -267,7 +267,7 @@ export async function createBackendConfig( paths.targetRunFile ? paths.targetRunFile : paths.targetEntry, ], resolve: { - extensions: ['.ts', '.mjs', '.js', '.json'], + extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], mainFields: ['main'], modules: [paths.rootNodeModules, ...moduleDirs], plugins: [ From ebe4489a3730514be2214a1522e59b13161f2ed8 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Tue, 18 Oct 2022 17:30:30 +0200 Subject: [PATCH 064/221] refactoring, fix api-reports.md Signed-off-by: Marko Simon --- plugins/ilert/api-report.md | 9 ++++++--- plugins/ilert/src/api/index.ts | 1 + plugins/ilert/src/api/types.ts | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/ilert/api-report.md b/plugins/ilert/api-report.md index 86ea698921..c1de4f796b 100644 --- a/plugins/ilert/api-report.md +++ b/plugins/ilert/api-report.md @@ -379,10 +379,8 @@ export interface ILertApi { assignAlert(alert: Alert, responder: AlertResponder): Promise; // (undocumented) createAlert(eventRequest: EventRequest): Promise; - // Warning: (ae-forgotten-export) The symbol "ServiceRequest" needs to be exported by the entry point index.d.ts - // // (undocumented) - createService(eventRequest: ServiceRequest): Promise; + createService(serviceRequest: ServiceRequest): Promise; // (undocumented) disableAlertSource(alertSource: AlertSource): Promise; // (undocumented) @@ -711,6 +709,11 @@ export interface Service { uptime: Uptime; } +// @public (undocumented) +export type ServiceRequest = { + name: string; +}; + // @public (undocumented) export type ServiceStatus = | typeof OPERATIONAL diff --git a/plugins/ilert/src/api/index.ts b/plugins/ilert/src/api/index.ts index 30120225a4..15d2981c20 100644 --- a/plugins/ilert/src/api/index.ts +++ b/plugins/ilert/src/api/index.ts @@ -22,5 +22,6 @@ export type { GetServicesOpts, GetStatusPagesOpts, ILertApi, + ServiceRequest as ServiceRequest, TableState, } from './types'; diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index 4e6e56dfd5..6d38d48023 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -114,7 +114,7 @@ export interface ILertApi { ): Promise; fetchServices(opts?: GetServicesOpts): Promise; - createService(eventRequest: ServiceRequest): Promise; + createService(serviceRequest: ServiceRequest): Promise; fetchStatusPages(opts?: GetStatusPagesOpts): Promise; From d383afb3722011879550febccb2595ffaad82149 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 18 Oct 2022 19:50:43 +0200 Subject: [PATCH 065/221] REVIEWING: add typescript section Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/REVIEWING.md b/REVIEWING.md index 9b21b49d51..f0fdc3fbcd 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -105,3 +105,150 @@ The change is also marked as a breaking `major` change. This should be changed t - [ ] Bump level - [ ] Have tests been added for new features bug fixes? - [ ] Has documentation been added? + +## Breaking Changes + +Identifying breaking changes can be quite tricky. You need to look at the changes from both the point of view of consumers and producers of APIs, as well as behavioral changes. In this section we explore a couple of methods for identifying whether a change is breaking or not. + +### TypeScript + +Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. + +In this section we will be talking about changed "types", but that refers to any kind of exported symbol from a packages, such as TypeScript types and interfaces, functions, classes, constants, etc. + +An important distinction to make when looking at changes to an API Report is the direction of a changed type, that is whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type, and how it affects whether a change is breaking or not. + +#### Input Types + +A input type is one that users need to provide to the package by consumers. The most common form of input type are function, constructor and method parameters. + +The following is an example where `MyComponentProps` is an input type: + +```ts +type MyComponentProps = { + title: string; + size?: 'small' | 'medium' | 'large'; +}; + +function MyComponent(props: MyComponentProps): JSX.Element; +``` + +And from the consumer's point of view it would look something like this: + +```tsx + +``` + +When modifying an input type, any change that increases constraints are breaking. For example, if we made the `size` prop required, that would be a breaking change. Likewise, if we changed the type of `size` to `'small' | 'large'`, that would also be breaking. + +On the other hand, it's fine to relax constraints without it being a breaking change. For example, if we made the `title` prop optional, that would not be breaking. Likewise, if we changed the type of `size` to `'small' | 'medium' | 'large' | 'huge'`, that would not be breaking either. It is also possible to add new properties without it being a breaking change, as long as they are optional. + +There's an edge-case where completely removing a property is also considered a breaking change. That's because of TypeScript being strict and refusing unknown properties, rather than a runtime breaking change. It is typically and easy thing for consumers to fix though. + +Another way to think about the rules for evolving input types is that the old type must be assignable to the new type. In this case for example `_props: NewComponentProps = {} as OldComponentProps`. It's not a silver bullet though, because of edge-cases like the one mentioned above. + +#### Output Types + +An output type is one that the user receives from the packages. One of the most obvious examples here are the top-level exports from the package itself, but it also includes function return types. + +The following is an example where both `useSize` and `Size` are output types: + +```ts +type Box = { + title: string; + shape?: 'square' | 'rounded'; +}; + +function useBox(): Box; +``` + +And from the consumer's point of view it would look something like this: + +```ts +const { title, shape } = useBox(); +``` + +When modifying an output type, any change that reduces constraints are breaking. For example, if we made the `title` property optional, that would be a breaking change, or if we changed the type of `shape` to `'square' | 'rounded' | 'octagon'`. + +Adding new properties is not a breaking change, regardless of whether they are optional or not. Removing properties is on the other hand always breaking. + +It is generally fine to increase constraints without it being a breaking change. For example, if we made the `title` property required, that would not be breaking. + +There are some edge-cases though, for example if `shape` was changed to just `'square'`, that would be a breaking change because consumers might be checking for `box.shape === 'rounded'`, which would then be breaking. It's typically a quite easy thing for consumers to fix though. More generally, type unions and discriminated unions are quite troublesome in output types, as both adding and removing types from them are considered breaking changes. + +Another way to think about the rules for evolving output types is that the new type must be assignable to the old type. In this case for example `_box: OldBox = {} as NewBox`. It's not a silver bullet though, because of edge-cases like the one mentioned above. + +#### I/O Types + +Some types are considered both input and output types. For example, consider the following example: + +```ts +type Point = { + x: number; + y: number; +}; + +function trimCoords(point: Point): Point; +``` + +In this case `Point` is both an input and output type. This means that the only changes we can make to the type that aren't breaking are the intersection of allowed changes between input and output types. In practice this only allows for the addition of new optional properties. Because of this constraint it is generally best to avoid using I/O types, and keep the input separated from the output. + +There are some cases where I/O types favor either input or output when it comes to API stability. For example, all types used by Utility APIs are I/O types, but the stability of the output is a lot more important than the stability of the input. That is because it's a lot easier for the single producer of the input interface to adapt to changes compared to all consumers of the API that use it as an output type. + +#### Identifying the Direction + +The only way to identify the direction of a type is to look at the context in which it's being used. In particular this can be tricky when looking at individual type aliases and interfaces, as you need to look at the rest of the package exports to see how the type is being used. + +One important rule is that the context considered for any type is limited to only the package in which the type is declared. Just because a type is imported in a different package and used as an input type does not make it an input type. + +The following rules can be used to identify the direction of a type alias or interface: + +- If the type is used in an input context, for example function parameter, then it's an input type. +- If the type is used in an output context, for example function return type, then it's an output type. +- If the type is referenced by another type, then it inherits the direction of that type, except if referenced through a function callback, in which case the direction is reversed. +- If the type is used or inherits both input and output context, then it's an I/O type. +- If the type is not referenced anywhere else, then it's an I/O type. + +Below is an example of the public API of a package, with type directions assigned to each export: + +```ts +// I/O, used by getPoint as return type and referenced by BoxProps, an input type +interface Point { + x: number; + y: number; +} + +function getPoint(): Point; + +// Input, used by Box as parameter type +interface BoxProps { + point?: Point +} + +function Box(props: BoxProps): JSX.Element; + +// Output, used by createWidget as return type +interface Widget { + ... +} + +// Output, as it's referenced by WidgetOptions, which is an input +// type, but the render callback causes a direction reversal +interface WidgetProps { + ... +} + +// Input, just like WidgetProps this is due to the direction reversal +// caused by the render callback +type RenderedWidget = JSX.Element | null; + +// Input, used by createWidget parameter type +interface WidgetOptions { + render(props: WidgetProps): RenderedWidget; +} + +function createWidget(options: WidgetOptions): Widget; + +// I/O, since it's not referenced anywhere else +type LabelStyle = 'normal' | 'thin'; +``` From b3d522b0a8db57f205d7b0d6a7250f8f5b16454c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 10:42:40 +0200 Subject: [PATCH 066/221] REVIEWING: example tweaks talk about behavioral changes Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 49 ++++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/REVIEWING.md b/REVIEWING.md index f0fdc3fbcd..074f568638 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -4,7 +4,7 @@ This file provides pointers for reviewing pull requests. While the main audience ## Code Style -See [STYLE.md](./STYLE.md). +See [STYLE.md](./STYLE.md). In particular, make sure that naming follows established conventions within the project and/or package. ## Secure Coding Practices @@ -41,13 +41,15 @@ Changes that do NOT need a new changeset: - Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. - Changes that do not end up having an effect on the published package can be skipped, such as whitespace fixes or code formatting changes. It is also alright to include a short changeset for these kind of changes too. -### Changeset Examples +### Changeset Example -**Example 1** +Consider the following scenario for a changeset: A new `EntityList` component has been added to `plugins/catalog-react`. -#### GOOD +Below are examples of a good and two bad changesets for that change. + +**GOOD** ```md --- @@ -57,14 +59,14 @@ A new `EntityList` component has been added to `plugins/catalog-react`. Added a new `EntityList` component that can be used to display detailed information about a list of entities. ``` -The Catalog React library has reached version `1.x`, which means that feature additions that aren't breaking should be a `minor` change. We don't bother with too much documentation, keeping it short and sweet. The main purpose is to inform users that this new component exists and to give them an idea of how they can use it. +The `@backstage/plugin-catalog-react` package has reached version `1.x`, which means that feature additions that aren't breaking should be a `minor` change. We don't bother with too much documentation, keeping it short and sweet. The main purpose is to inform users that this new component exists and to give them an idea of how they can use it. -#### BAD +**BAD** ```md --- '@backstage/plugin-catalog-react': minor -'@backstage/plugin-catalog': minor +'@backstage/plugin-catalog': patch --- Added `EntityList` component. @@ -75,6 +77,10 @@ This changeset is too short, it's best to give users an idea of how they can ben It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changeset for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. +Lastly, the `@backstage/plugin-catalog-react` package has reached `1.x`, which means that new features should be introduced through a `minor` bump. We'd only use `patch` bumps for minor changes or fixes that do not affect the public API. + +**BAD** + ```md --- '@backstage/plugin-catalog-react': major @@ -95,22 +101,21 @@ This changeset is getting too detailed. It's not always bad to get this much int The change is also marked as a breaking `major` change. This should be changed to `minor` since adding new APIs is never a breaking change. -## Review Checklist - -- [ ] API Reports - - [ ] Naming - - [ ] Breaking changes -- [ ] Changesets - - [ ] Content - - [ ] Bump level -- [ ] Have tests been added for new features bug fixes? -- [ ] Has documentation been added? - ## Breaking Changes Identifying breaking changes can be quite tricky. You need to look at the changes from both the point of view of consumers and producers of APIs, as well as behavioral changes. In this section we explore a couple of methods for identifying whether a change is breaking or not. -### TypeScript +### Behavioral Changes + +These are changes where the behavior of the code changes, but the public API remains the same. They can be anything from tiny tweaks, like adding a bit of padding to a visual element, to a complete redesign and refactor of an entire plugin. + +It's hard to set up exact rules for when a behavioral change is breaking or not. In some cases it's obvious, for example if you remove important functionality of a system, while in other cases it can be very hard to tell. In the end what's important is whether a significant number of users of the package will be negatively impacted by the change. One question that you can ask yourself here is "is it likely that there are users that don't want the new behavior, or will need to change their code to adapt to the new behavior?" If the answer is yes, then it's likely a breaking change. You do also want to keep [xkcd.com/1172](https://xkcd.com/1172/) in mind though. + +Note that even a bug fix can be considered a breaking change in some situations. One things to lean on in that case is what the _documented_ behavior is. If the current behavior does not match the documented behavior, then a change to match the documentation is generally not a breaking change. That is unless it is likely that there are a significant number of users that will be impacted by the change. + +For tricky behavioral changes you may simply need to let end users provide feedback. This can be done either by hiding the new behavior behind an experimental feature switch, or by releasing the change early on in the release cycle, preferably in the first or second next line release. Be ready to respond to feedback and potentially revert the change if needed. + +### Public API Changes Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. @@ -218,6 +223,7 @@ interface Point { y: number; } +// Output, since it's an exported function function getPoint(): Point; // Input, used by Box as parameter type @@ -225,6 +231,7 @@ interface BoxProps { point?: Point } +// Output, since it's an exported function function Box(props: BoxProps): JSX.Element; // Output, used by createWidget as return type @@ -247,8 +254,12 @@ interface WidgetOptions { render(props: WidgetProps): RenderedWidget; } +// Output, since it's an exported function function createWidget(options: WidgetOptions): Widget; // I/O, since it's not referenced anywhere else type LabelStyle = 'normal' | 'thin'; + +// Output, since it's an exported constant +const LABEL_SIZE: number; ``` From 8a4f4d1f307ebdf8fa216283bd80be9bff771358 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 13:19:00 +0200 Subject: [PATCH 067/221] REVIEWING: versioning policy and changeset bump sections Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/REVIEWING.md b/REVIEWING.md index 074f568638..d118eee367 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -10,6 +10,12 @@ See [STYLE.md](./STYLE.md). In particular, make sure that naming follows establi Be sure to familiarize yourself with our [secure coding practices](./SECURITY.md#coding-practices). +## Release & Versioning Policy + +When reviewing pull requests it's important to consider our [versioning policy and release cycle](https://backstage.io/docs/overview/versioning-policy). Generally the most important bit is our [package versioning policy](https://backstage.io/docs/overview/versioning-policy#package-versioning-policy), which describes when and how we can ship breaking changes. We'll dive into how to identify breaking changes in a later section section. + +One other thing to keep in mind, especially when merging pull requests, is where in the release cycle we're currently at. In particular you want to avoid merging any large or risky changes towards the end of each release cycle. If there is a change that is ready to be merged, but you want to hold off until the next main line release, then you can label it with the `merge-after-release` label. + ## Changesets We use changesets to track the changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. @@ -19,6 +25,19 @@ When reviewing a changeset, the most important things to look for are the bump l ### Reviewing Changeset Bump Levels +The following table provides a reference for what type of version bump is needed for each individual package. This applies to each individual package separately, it does not matter what the scope of a change is in any other broader scope. + +| Scope | Current Package Version | Bump Level | +| --------------- | ----------------------- | ---------- | +| Breaking Change | `1.0` and above | `major` | +| New Feature | `1.0` and above | `minor` | +| Fix | `1.0` and above | `patch` | +| Breaking Change | `0.x` | `minor` | +| New Feature | `0.x` | `patch` | +| Fix | `0.x` | `patch` | + +The only situation where a package that is currently at `0.x` can have a `major` bump is if all owners and stakeholders of the package agree that the package is ready to be released as `1.0`. + ### Reviewing Changeset Content Each changeset should be written in a way that describes the impact of the change for the end user of each package. The changesets end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. From 78577cb46e673a23eaf5ae87c8e0f1b6d60b55ad Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 13:22:41 +0200 Subject: [PATCH 068/221] REVIEWING: expand code style section Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index d118eee367..32c2a797ae 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -4,7 +4,11 @@ This file provides pointers for reviewing pull requests. While the main audience ## Code Style -See [STYLE.md](./STYLE.md). In particular, make sure that naming follows established conventions within the project and/or package. +See our code style documented at [STYLE.md](./STYLE.md). + +In particular when it comes to naming, make sure that naming follows established conventions within the project and/or package. + +When adding new dependencies to packages it is always preferred to use version ranges that are already in use by other packages in the repository. This helps minimize lockfile changes and reduce package duplication, both in our repository as well as other Backstage installations. ## Secure Coding Practices From 47ec8b88d5d837079a99a359ec09bc29cf67045c Mon Sep 17 00:00:00 2001 From: Alex Rybchenko Date: Wed, 19 Oct 2022 14:13:56 +0200 Subject: [PATCH 069/221] set application/json header Signed-off-by: Alex Rybchenko --- .changeset/poor-moons-impress.md | 5 +++++ plugins/tech-insights/src/api/TechInsightsClient.ts | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/poor-moons-impress.md diff --git a/.changeset/poor-moons-impress.md b/.changeset/poor-moons-impress.md new file mode 100644 index 0000000000..f441cba1f2 --- /dev/null +++ b/.changeset/poor-moons-impress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights': patch +--- + +Set content type to 'application/json' in TechInsightsClient diff --git a/plugins/tech-insights/src/api/TechInsightsClient.ts b/plugins/tech-insights/src/api/TechInsightsClient.ts index 6e85769b34..0f55a8f8e1 100644 --- a/plugins/tech-insights/src/api/TechInsightsClient.ts +++ b/plugins/tech-insights/src/api/TechInsightsClient.ts @@ -82,6 +82,9 @@ export class TechInsightsClient implements TechInsightsApi { { method: 'POST', body: JSON.stringify(requestBody), + headers: { + 'content-type': 'application/json', + }, }, ); } @@ -98,6 +101,9 @@ export class TechInsightsClient implements TechInsightsApi { return this.api('/checks/run', { method: 'POST', body: JSON.stringify(requestBody), + headers: { + 'content-type': 'application/json', + }, }); } From 1156ed81398abd69820d01c345d71bfbeb855828 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 14:22:46 +0200 Subject: [PATCH 070/221] REVIEWING: couple more sections and tweaks Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 83 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/REVIEWING.md b/REVIEWING.md index 32c2a797ae..6a54ae875e 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -1,6 +1,6 @@ # Introduction -This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to the repository as well. +This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to this repository. ## Code Style @@ -16,20 +16,20 @@ Be sure to familiarize yourself with our [secure coding practices](./SECURITY.md ## Release & Versioning Policy -When reviewing pull requests it's important to consider our [versioning policy and release cycle](https://backstage.io/docs/overview/versioning-policy). Generally the most important bit is our [package versioning policy](https://backstage.io/docs/overview/versioning-policy#package-versioning-policy), which describes when and how we can ship breaking changes. We'll dive into how to identify breaking changes in a later section section. +When reviewing pull requests it's important to consider our [versioning policy and release cycle](https://backstage.io/docs/overview/versioning-policy). Generally the most important bit is our [package versioning policy](https://backstage.io/docs/overview/versioning-policy#package-versioning-policy), which describes when and how we can ship breaking changes. We'll dive into how to identify breaking changes in a different section. One other thing to keep in mind, especially when merging pull requests, is where in the release cycle we're currently at. In particular you want to avoid merging any large or risky changes towards the end of each release cycle. If there is a change that is ready to be merged, but you want to hold off until the next main line release, then you can label it with the `merge-after-release` label. ## Changesets -We use changesets to track the changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. +We use changesets to track changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. An introduction to changesets can be found in our [contribution guidelines](./CONTRIBUTING.md#creating-changesets). -When reviewing a changeset, the most important things to look for are the bump level, i.e. `major` / `minor` / `patch`, and whether the content is accurate and if it's written in a way that makes sense when reading it in the changelog for each package. +When reviewing a changeset, the most important things to look for are the bump levels, i.e. `major` / `minor` / `patch`, as well as whether the content is accurate and if it's written in a way that makes sense when reading it in the changelog for each package. ### Reviewing Changeset Bump Levels -The following table provides a reference for what type of version bump is needed for each individual package. This applies to each individual package separately, it does not matter what the scope of a change is in any other broader scope. +The following table provides a reference for what type of version bump is needed for each package. This is applied separately to each individual package, it does not matter what the scope of a change is in any other broader context. | Scope | Current Package Version | Bump Level | | --------------- | ----------------------- | ---------- | @@ -44,25 +44,25 @@ The only situation where a package that is currently at `0.x` can have a `major` ### Reviewing Changeset Content -Each changeset should be written in a way that describes the impact of the change for the end user of each package. The changesets end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. +Each changeset should be written in a way that describes the impact of the change for the users of each package. The contents of the changesets will end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. Some things that changeset should NOT contain are: -- Internal architecture details - these are generally not interesting to end users, focus on the impact towards end users instead. +- Internal architecture details - these are generally not interesting to users, focus on the impact towards users of the package instead. - Information related to a different package. - A large amount of content, consider for example a separate migration guide instead, either in the package README or [./docs/](./docs/), and then link to that instead. - Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). ### When is a changeset needed? -In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also +In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also on the kind of change that has been made. Changes that do NOT need a new changeset: - Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. -- When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset. +- When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset instead. - Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. -- Changes that do not end up having an effect on the published package can be skipped, such as whitespace fixes or code formatting changes. It is also alright to include a short changeset for these kind of changes too. +- Changes that do not end up having an effect on the published package, such as whitespace fixes or code formatting changes. Although it's also fine to have a short changeset for these kind of changes too. ### Changeset Example @@ -70,7 +70,7 @@ Consider the following scenario for a changeset: A new `EntityList` component has been added to `plugins/catalog-react`. -Below are examples of a good and two bad changesets for that change. +Below are examples of a good and three bad changesets for that change. **GOOD** @@ -89,18 +89,17 @@ The `@backstage/plugin-catalog-react` package has reached version `1.x`, which m ```md --- '@backstage/plugin-catalog-react': minor -'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog': minor --- Added `EntityList` component. + Fixed a bug in the catalog index page. ``` This changeset is too short, it's best to give users an idea of how they can benefit from the new addition. -It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changeset for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. - -Lastly, the `@backstage/plugin-catalog-react` package has reached `1.x`, which means that new features should be introduced through a `minor` bump. We'd only use `patch` bumps for minor changes or fixes that do not affect the public API. +It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changesets for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. **BAD** @@ -120,17 +119,31 @@ It accepts the following properties: - dialog - An optional component that overrides the default details dialog. ``` -This changeset is getting too detailed. It's not always bad to get this much into the weeds, but keep in mind that changesets are not easy to browse when search for information about specific APIs. It's better to document things like this separately and keep the changeset more lean. Also avoid linking to assets in changesets, keep them text-only. +This changeset is getting too detailed. It's not always bad to get this much into the weeds, but keep in mind that changesets are not easy to browse when searching for information about specific APIs. It's better to document things like this separately and keep the changeset more lean. Also avoid linking to assets in changesets, keep them text-only. The change is also marked as a breaking `major` change. This should be changed to `minor` since adding new APIs is never a breaking change. +**BAD** + +```md +--- +'@backstage/plugin-catalog-react': patch +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. The `ListView` component was also refactored in order to make it possible to reuse it between the new `EntityList` and `KindList` components. +``` + +Assuming that the `ListView` component is not public API, this changeset goes into details that are not interesting to the user of the package. Internal changes do not need to be highlighted in changesets. If an internal refactor is the only change then it's alright to say something short like "Internal refactor to improve code reuse", but otherwise those details should be left out. + +The `@backstage/plugin-catalog-react` package has also reached `1.x`, which means that new features should be introduced through a `minor` bump. We'd only use `patch` bumps for minor changes or fixes that do not affect the public API. + ## Breaking Changes Identifying breaking changes can be quite tricky. You need to look at the changes from both the point of view of consumers and producers of APIs, as well as behavioral changes. In this section we explore a couple of methods for identifying whether a change is breaking or not. ### Behavioral Changes -These are changes where the behavior of the code changes, but the public API remains the same. They can be anything from tiny tweaks, like adding a bit of padding to a visual element, to a complete redesign and refactor of an entire plugin. +These are changes where the behavior of the code changes, but the public API is unchanged or doesn't have any breaking changes. They can be anything from tiny tweaks, like adding a bit of padding to a visual element, to a complete redesign and refactor of an entire plugin. It's hard to set up exact rules for when a behavioral change is breaking or not. In some cases it's obvious, for example if you remove important functionality of a system, while in other cases it can be very hard to tell. In the end what's important is whether a significant number of users of the package will be negatively impacted by the change. One question that you can ask yourself here is "is it likely that there are users that don't want the new behavior, or will need to change their code to adapt to the new behavior?" If the answer is yes, then it's likely a breaking change. You do also want to keep [xkcd.com/1172](https://xkcd.com/1172/) in mind though. @@ -140,15 +153,31 @@ For tricky behavioral changes you may simply need to let end users provide feedb ### Public API Changes -Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. +Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. If you determine that a change is breaking at the TypeScript level, then it is a breaking change. -In this section we will be talking about changed "types", but that refers to any kind of exported symbol from a packages, such as TypeScript types and interfaces, functions, classes, constants, etc. +In this section we will be talking about changed "types", but by that we mean any kind of exported symbol from a packages, such as TypeScript types aliases or interfaces, functions, classes, constants, etc. -An important distinction to make when looking at changes to an API Report is the direction of a changed type, that is whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type, and how it affects whether a change is breaking or not. +#### API Reports + +We generate API Reports using the [API Extractor](https://api-extractor.com/) tool. These reports are generated for most packages in the Backstage repository, and are stored in the `api-report.md` file of each package. For CLI package we use custom tooling, and instead store the result in `cli-report.md`. Whenever the public API of a package changes, the API Report needs to be updated to reflect the new state of the API. Our CI checks will fail if the API reports are not up to date in a pull request. + +Each API report contains a list of all the exported types of each package. As long as the API report does not have any warnings it will contain the full publicly facing API of the package, meaning you do not need to consider any other changes to the package from the point of view of TypeScript API stability. + +Exported types can be marked with either `@public`, `@alpha` or `@beta` release tags. It is only the `@public` exports that we consider to be part of the stable API. The `@alpha` and `@beta` exports are considered unstable and can be changed at any time without needing a breaking package versions bump. However, this **ONLY** applies if the package has been configured to use experimental type builds, which looks like this in `package.json`: + +```json + "build": "backstage-cli package build --experimental-type-build" +``` + +If a package does not have this configuration, then all exported types are considered stable, even if they are marked as `@alpha` or `@beta`. + +#### Type Contract Direction + +An important distinction to make when looking at changes to an API Report is the direction of the contract of a changed type, that is, whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type contract, and how it affects whether a change is breaking or not. #### Input Types -A input type is one that users need to provide to the package by consumers. The most common form of input type are function, constructor and method parameters. +A input type is one where a value needs to be provided by users of the package. The most common form of input type are function, constructor, and method parameters. The following is an example where `MyComponentProps` is an input type: @@ -161,7 +190,7 @@ type MyComponentProps = { function MyComponent(props: MyComponentProps): JSX.Element; ``` -And from the consumer's point of view it would look something like this: +And from the package user's point of view it would look something like this: ```tsx @@ -177,9 +206,9 @@ Another way to think about the rules for evolving input types is that the old ty #### Output Types -An output type is one that the user receives from the packages. One of the most obvious examples here are the top-level exports from the package itself, but it also includes function return types. +An output type is one that the user receives from the packages. One of the most obvious examples here are the top-level exports from the package itself, but it also includes for example function return types. -The following is an example where both `useSize` and `Size` are output types: +The following is an example where both `useBox` and `Box` are output types: ```ts type Box = { @@ -223,9 +252,9 @@ In this case `Point` is both an input and output type. This means that the only There are some cases where I/O types favor either input or output when it comes to API stability. For example, all types used by Utility APIs are I/O types, but the stability of the output is a lot more important than the stability of the input. That is because it's a lot easier for the single producer of the input interface to adapt to changes compared to all consumers of the API that use it as an output type. -#### Identifying the Direction +#### Identifying the Contract Direction -The only way to identify the direction of a type is to look at the context in which it's being used. In particular this can be tricky when looking at individual type aliases and interfaces, as you need to look at the rest of the package exports to see how the type is being used. +The only way to identify the contract direction of a type is to look at the context in which it's being used. In particular this can be tricky when looking at individual type aliases and interfaces, as you need to look at the rest of the package exports to see how the type is being used. One important rule is that the context considered for any type is limited to only the package in which the type is declared. Just because a type is imported in a different package and used as an input type does not make it an input type. @@ -234,7 +263,7 @@ The following rules can be used to identify the direction of a type alias or int - If the type is used in an input context, for example function parameter, then it's an input type. - If the type is used in an output context, for example function return type, then it's an output type. - If the type is referenced by another type, then it inherits the direction of that type, except if referenced through a function callback, in which case the direction is reversed. -- If the type is used or inherits both input and output context, then it's an I/O type. +- If the type is used or inherits both input and output contexts, then it's an I/O type. - If the type is not referenced anywhere else, then it's an I/O type. Below is an example of the public API of a package, with type directions assigned to each export: From c0098f1bdd968f518006603cf42d32325fd74441 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 15:20:26 +0200 Subject: [PATCH 071/221] add TSDoc to vocab Signed-off-by: Patrik Oldsberg --- .github/vale/Vocab/Backstage/accept.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index e40dfffda1..19205d0c8d 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -341,6 +341,7 @@ transpiled transpiler transpilers truthy +TSDoc typeahead ui unbreak From 9c767e8f454f8e5203aae2f6886828a3a35d2e35 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 14:02:09 +0000 Subject: [PATCH 072/221] Update SVGR monorepo packages to 6.5.x Signed-off-by: Renovate Bot --- .changeset/renovate-6fb5f1b.md | 8 ++ packages/cli/package.json | 8 +- yarn.lock | 164 +++++++++++++++++---------------- 3 files changed, 95 insertions(+), 85 deletions(-) create mode 100644 .changeset/renovate-6fb5f1b.md diff --git a/.changeset/renovate-6fb5f1b.md b/.changeset/renovate-6fb5f1b.md new file mode 100644 index 0000000000..f98cc64ea4 --- /dev/null +++ b/.changeset/renovate-6fb5f1b.md @@ -0,0 +1,8 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `@svgr/plugin-jsx` to `6.5.x`. +Updated dependency `@svgr/plugin-svgo` to `6.5.x`. +Updated dependency `@svgr/rollup` to `6.5.x`. +Updated dependency `@svgr/webpack` to `6.5.x`. diff --git a/packages/cli/package.json b/packages/cli/package.json index f460bfa4e0..a377bad909 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -48,10 +48,10 @@ "@spotify/eslint-config-typescript": "^14.0.0", "@sucrase/jest-plugin": "^2.1.1", "@sucrase/webpack-loader": "^2.0.0", - "@svgr/plugin-jsx": "6.3.x", - "@svgr/plugin-svgo": "6.3.x", - "@svgr/rollup": "6.3.x", - "@svgr/webpack": "6.3.x", + "@svgr/plugin-jsx": "6.5.x", + "@svgr/plugin-svgo": "6.5.x", + "@svgr/rollup": "6.5.x", + "@svgr/webpack": "6.5.x", "@swc/core": "^1.2.239", "@swc/helpers": "^0.4.7", "@swc/jest": "^0.2.22", diff --git a/yarn.lock b/yarn.lock index ef0c3fa984..b5614fe26a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3357,10 +3357,10 @@ __metadata: "@spotify/eslint-config-typescript": ^14.0.0 "@sucrase/jest-plugin": ^2.1.1 "@sucrase/webpack-loader": ^2.0.0 - "@svgr/plugin-jsx": 6.3.x - "@svgr/plugin-svgo": 6.3.x - "@svgr/rollup": 6.3.x - "@svgr/webpack": 6.3.x + "@svgr/plugin-jsx": 6.5.x + "@svgr/plugin-svgo": 6.5.x + "@svgr/rollup": 6.5.x + "@svgr/webpack": 6.5.x "@swc/core": ^1.2.239 "@swc/helpers": ^0.4.7 "@swc/jest": ^0.2.22 @@ -12333,147 +12333,149 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-add-jsx-attribute@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.3.1" +"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3a04515743af5f67c3c38cf414f225cb4c266db29fbf37f4bd970be0ab5b6a2c18e9e8c7de3303a70168909106077860b0fdfb9ee4de9c50d994181b4850e615 + checksum: f65ca26905240b685929a7766411618700bda233673cebd74eb9a8da45af8ce8e0536074a178b37762cd23db8868db494e15067e74b2d73e377b2d247895d054 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-attribute@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.3.1" +"@svgr/babel-plugin-remove-jsx-attribute@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ea78848a1d987a30320f84263399769d80064a593cf8af41bb5d4e1699869f9395d3ed18c7d35a06c85d4c46f93df3a9864981d6844296c7a26d19b6bfc39098 + checksum: 7a4dfc1345f5855b010684e9c5301731842bf91d72b82ce5cc4c82c80b94de1036e447a8a00fb306a6dd575cb4c640d8ce3cfee6607ddbb804796a77284c7f22 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.3.1" +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3975ee4ca649fde5acba30748f7766c1362b7b39b54d6164b8f27a13cee0b0f2b2cf05e8eda476a4c833be42697a1e0b47b0c8fae8a66563ba23ac9537fdd502 + checksum: 3e173f720d530f9f71f8506f3eb78583eec3d87d66e385efe1ef3b3ebfc4e3680ec30f36414726de6a163e99ca69f54886022967e49476dea522267e1986936e languageName: node linkType: hard -"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.3.1" +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8a65eb8aa99e3c3e4710aff34d20099a4f2a610d79a5ef705ce4050ff28a25c1f22d813c5021a6c9399725559aba28580674f68b4b5a202028754541e3243453 + checksum: e8e77e4026f2e2f910a3495be8bd283f413865449b6e2f639318d76edc05b373e18d86e1210c808038a5477ae273858d3b313b2a7df8e6929450ed902c1441bc languageName: node linkType: hard -"@svgr/babel-plugin-svg-dynamic-title@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.3.1" +"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 026f440d2e609532b1a40434dbd97cae54d0ed9090a6f4069d75523611f6d45ac9983a5c69c10cfd4a6ab76bc854c529c99c327e1a11fd8e65b6f59a930181b9 + checksum: 55f36f6e3ef986f2d0ba4cd9e2ebf6b17d68c5c2cf98821abc0dbd551d4fd7d92cf3cda83a91898d988ad7118a9768042ac5afe534ad594bdac024fe0009bae1 languageName: node linkType: hard -"@svgr/babel-plugin-svg-em-dimensions@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.3.1" +"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 02aa7fa0afd6def11af7f00401918926626faba861f9869b7359d532d524dcf5062810728bf5e8117275dd4c340dc34a24d55c8c705c7a6d678988db8619428b + checksum: af6508c042a7d256081c09520e79e2d3278ecf361a74707dcc1bc61713845ec7fd6eeb52bbc3a2e114ecbbb1df49b16674caf8b97345570a6c5f0a631118cb5e languageName: node linkType: hard -"@svgr/babel-plugin-transform-react-native-svg@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.3.1" +"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2cbe20f7016eab8de3515c2bf9887a6399e20d8078614b1316952794ec03c331ea0127c689a258c115b5ca29c279fafef972238c8b491841c49a86b84f408088 + checksum: 0e7f1d85a25ef0c49b2bfacdc9ae80520959a0925304e030edc739684c75d41a7d4173ac4a1cd6ec8dee8bc618e7465da49ae59dd5638b87e75dccee05498f4c languageName: node linkType: hard -"@svgr/babel-plugin-transform-svg-component@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.3.1" +"@svgr/babel-plugin-transform-svg-component@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 76113730f5cbcc58d42e2254168db98bc40201cd7e90d58cd3137f332fd8328ae113ce64a59c2a60e9ca92730030eb7e4ab8476fdbc31cf9ec0cc8221a7ffb96 + checksum: 8613ef673b7e881d661057188729419b9a9d0b3802247954283293698a9910d76414b4fe106b5255e06fb2329c41f9da147ac5e153149b3b82024e06ab87a2b3 languageName: node linkType: hard -"@svgr/babel-preset@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-preset@npm:6.3.1" +"@svgr/babel-preset@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-preset@npm:6.5.0" dependencies: - "@svgr/babel-plugin-add-jsx-attribute": ^6.3.1 - "@svgr/babel-plugin-remove-jsx-attribute": ^6.3.1 - "@svgr/babel-plugin-remove-jsx-empty-expression": ^6.3.1 - "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.3.1 - "@svgr/babel-plugin-svg-dynamic-title": ^6.3.1 - "@svgr/babel-plugin-svg-em-dimensions": ^6.3.1 - "@svgr/babel-plugin-transform-react-native-svg": ^6.3.1 - "@svgr/babel-plugin-transform-svg-component": ^6.3.1 + "@svgr/babel-plugin-add-jsx-attribute": ^6.5.0 + "@svgr/babel-plugin-remove-jsx-attribute": ^6.5.0 + "@svgr/babel-plugin-remove-jsx-empty-expression": ^6.5.0 + "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.5.0 + "@svgr/babel-plugin-svg-dynamic-title": ^6.5.0 + "@svgr/babel-plugin-svg-em-dimensions": ^6.5.0 + "@svgr/babel-plugin-transform-react-native-svg": ^6.5.0 + "@svgr/babel-plugin-transform-svg-component": ^6.5.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c9cdb0889d63d8fa178c90b016e36515e6803ba5c96e980f0233798f63cac29a1e871effa2c17a40e3eaffdb150ad8e98676cc14c97dd8f978f67541c594a05f + checksum: 987f6eafebc347b061bfab7d15f87b48601eecdf7cde9ff0e4e99c44acad3bb126e554996b0c48f1ccf24d5e6785226b1662850bb4e2182927b2ded24f226ae8 languageName: node linkType: hard -"@svgr/core@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/core@npm:6.3.1" +"@svgr/core@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/core@npm:6.5.0" dependencies: - "@svgr/plugin-jsx": ^6.3.1 + "@babel/core": ^7.18.5 + "@svgr/babel-preset": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 camelcase: ^6.2.0 cosmiconfig: ^7.0.1 - checksum: 753b043f48d5bfef8aa02976d5ed5a885c60e74e5ac0cdf2b00045e7867443ae722a57160ae46e7a2db563d1ecbe1fda4585a21e84ac1337b6b94113f25b9925 + checksum: 235747a1d1c0e8918aa16da7e44c9dd2024a9ebaadc6bdff00001756d604566437b25e42d61e521f6f38a32c386d44a18dee57a77a92aedef1116f2410b504e6 languageName: node linkType: hard -"@svgr/hast-util-to-babel-ast@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/hast-util-to-babel-ast@npm:6.3.1" +"@svgr/hast-util-to-babel-ast@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.0" dependencies: "@babel/types": ^7.18.4 entities: ^4.3.0 - checksum: e54b48a85795e103cfe918fa7102bf4603e6541dd35ee04061fad62edffa5a60d8aa210f709fe81b50c9fb6041f8e62fabf093443266323c649903200aaea604 + checksum: 77dcadb467eded0ce5cba71dbd4cbb4c7ffd7961f351828a4066ab6105d466d55533506bc3bc7db78f938af6692008ceda9fa2ea3dda75fd54e2b736b81ae458 languageName: node linkType: hard -"@svgr/plugin-jsx@npm:6.3.x, @svgr/plugin-jsx@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/plugin-jsx@npm:6.3.1" +"@svgr/plugin-jsx@npm:6.5.x, @svgr/plugin-jsx@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/plugin-jsx@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 - "@svgr/babel-preset": ^6.3.1 - "@svgr/hast-util-to-babel-ast": ^6.3.1 + "@svgr/babel-preset": ^6.5.0 + "@svgr/hast-util-to-babel-ast": ^6.5.0 svg-parser: ^2.0.4 peerDependencies: "@svgr/core": ^6.0.0 - checksum: a2e487dc28d2b69b94b7d96e5cb2593857e559e64c8cb4f818035b8a43ba84e5ddb67f966d15f173b544e807e48a1cda1065da8f9064b94b8d62bbe8cb8c4d73 + checksum: dec7cd47f16cc1b23dd37e333594d596401e7e49825545f4d808c74fe0a3a12a56c1bd55f507794addd6675e1b81dd58346bac4f752daa04016d3edac03e1625 languageName: node linkType: hard -"@svgr/plugin-svgo@npm:6.3.x, @svgr/plugin-svgo@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/plugin-svgo@npm:6.3.1" +"@svgr/plugin-svgo@npm:6.5.x, @svgr/plugin-svgo@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/plugin-svgo@npm:6.5.0" dependencies: cosmiconfig: ^7.0.1 deepmerge: ^4.2.2 svgo: ^2.8.0 peerDependencies: "@svgr/core": ^6.0.0 - checksum: 037d6f91ba7f362764527408661f7fc4a4a296e9dc142a5f2e33fc88dc63dafd305452caae3091e9adb63adf029e0ce20c604d5af787b968f98aad261a834679 + checksum: d1a0ee79283a997aa4af2c2848f502dff71a9fd99623b47b0b2476effd7fe53077456afd1ff54283512a7dd10f30df91736f5d20eb931a462b34f281c90347e0 languageName: node linkType: hard -"@svgr/rollup@npm:6.3.x": - version: 6.3.1 - resolution: "@svgr/rollup@npm:6.3.1" +"@svgr/rollup@npm:6.5.x": + version: 6.5.0 + resolution: "@svgr/rollup@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 "@babel/plugin-transform-react-constant-elements": ^7.17.12 @@ -12481,26 +12483,26 @@ __metadata: "@babel/preset-react": ^7.17.12 "@babel/preset-typescript": ^7.17.12 "@rollup/pluginutils": ^4.2.1 - "@svgr/core": ^6.3.1 - "@svgr/plugin-jsx": ^6.3.1 - "@svgr/plugin-svgo": ^6.3.1 - checksum: 8d96f95a4c89d96a14bc0095469a4ceb39a2ae4ea3517d08573ed8ca11b05416a505723ef09d00c3cee056adc2d95ddcea6c8055a116bbb461ba33d601bfced8 + "@svgr/core": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 + "@svgr/plugin-svgo": ^6.5.0 + checksum: a3a043034689a335aa9657580251a8636225002439a4667c31fe2b0faa043980182b28aac7da0219804609ef19c0b3b397626850f056a7065c20f61c2f17b4f8 languageName: node linkType: hard -"@svgr/webpack@npm:6.3.x": - version: 6.3.1 - resolution: "@svgr/webpack@npm:6.3.1" +"@svgr/webpack@npm:6.5.x": + version: 6.5.0 + resolution: "@svgr/webpack@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 "@babel/plugin-transform-react-constant-elements": ^7.17.12 "@babel/preset-env": ^7.18.2 "@babel/preset-react": ^7.17.12 "@babel/preset-typescript": ^7.17.12 - "@svgr/core": ^6.3.1 - "@svgr/plugin-jsx": ^6.3.1 - "@svgr/plugin-svgo": ^6.3.1 - checksum: 36784eacf80601462ede7eab66347423a8635e68aa9f152308c81878b071807adee152a28eed2cce9c72faaf6553dd500f68f00601062ec6821ec0a3a77f4e13 + "@svgr/core": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 + "@svgr/plugin-svgo": ^6.5.0 + checksum: 2c0b18b20694b1301e86893e488269882ae136259cc17b4ab47c208c6edcdfeaefa23894b3fe68d36384e26380eafde23571b0cb143aadf538c027d72eb1d702 languageName: node linkType: hard From c1784a49809df21323b4762e464eed5fa7b045ed Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Mon, 17 Oct 2022 21:38:44 +0200 Subject: [PATCH 073/221] chore(integration): use consistent naming of `[gG]ithub` in code Relates to the discussion at PR #14039. Relates-to: PR #14039 Relates-to: PR #14174 Signed-off-by: Patrick Jungermann --- .changeset/gorgeous-queens-pull.md | 14 ++++ .changeset/sixty-pigs-shave.md | 13 ++++ packages/backend-common/api-report.md | 4 +- .../src/reading/GithubUrlReader.test.ts | 16 ++--- .../src/reading/GithubUrlReader.ts | 8 +-- packages/integration/api-report.md | 60 +++++++++++----- .../integration/src/ScmIntegrations.test.ts | 8 +-- packages/integration/src/ScmIntegrations.ts | 8 +-- .../DefaultGithubCredentialsProvider.test.ts | 4 +- ...tion.test.ts => GithubIntegration.test.ts} | 22 +++--- ...HubIntegration.ts => GithubIntegration.ts} | 22 +++--- ...SingleInstanceGithubCredentialsProvider.ts | 6 +- .../integration/src/github/config.test.ts | 34 ++++----- packages/integration/src/github/config.ts | 12 ++-- packages/integration/src/github/core.test.ts | 42 +++++------ packages/integration/src/github/core.ts | 10 +-- packages/integration/src/github/deprecated.ts | 71 +++++++++++++++++++ packages/integration/src/github/index.ts | 12 ++-- packages/integration/src/registry.ts | 4 +- .../api-report.md | 4 +- .../GithubMultiOrgReaderProcessor.ts | 4 +- .../src/providers/GithubEntityProvider.ts | 8 +-- .../providers/GithubOrgEntityProvider.test.ts | 4 +- .../src/providers/GithubOrgEntityProvider.ts | 4 +- .../src/api/CatalogImportClient.ts | 4 +- .../src/api/GitReleaseClient.ts | 4 +- .../src/api/GithubActionsClient.ts | 4 +- .../src/components/Cards/Cards.tsx | 4 +- .../Cards/RecentWorkflowRunsCard.tsx | 4 +- .../WorkflowRunDetails/WorkflowRunDetails.tsx | 4 +- .../WorkflowRunLogs/WorkflowRunLogs.tsx | 4 +- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 4 +- .../github-issues/src/api/gitHubIssuesApi.ts | 4 +- .../src/api/useOctokitGraphQl.ts | 4 +- .../src/ReportIssue/hooks.ts | 4 +- .../reader/transformers/addGitFeedbackLink.ts | 4 +- 36 files changed, 283 insertions(+), 159 deletions(-) create mode 100644 .changeset/gorgeous-queens-pull.md create mode 100644 .changeset/sixty-pigs-shave.md rename packages/integration/src/github/{GitHubIntegration.test.ts => GithubIntegration.test.ts} (86%) rename packages/integration/src/github/{GitHubIntegration.ts => GithubIntegration.ts} (78%) create mode 100644 packages/integration/src/github/deprecated.ts diff --git a/.changeset/gorgeous-queens-pull.md b/.changeset/gorgeous-queens-pull.md new file mode 100644 index 0000000000..bb257e85f0 --- /dev/null +++ b/.changeset/gorgeous-queens-pull.md @@ -0,0 +1,14 @@ +--- +'@backstage/integration': minor +--- + +Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + +Deprecates: + +- `getGitHubFileFetchUrl` replaced by `getGithubFileFetchUrl` +- `GitHubIntegrationConfig` replaced by `GithubIntegrationConfig` +- `GitHubIntegration` replaced by `GithubIntegration` +- `readGitHubIntegrationConfig` replaced by `readGithubIntegrationConfig` +- `readGitHubIntegrationConfigs` replaced by `readGithubIntegrationConfigs` +- `replaceGitHubUrlType` replaced by `replaceGithubUrlType` diff --git a/.changeset/sixty-pigs-shave.md b/.changeset/sixty-pigs-shave.md new file mode 100644 index 0000000000..e1f4443ad4 --- /dev/null +++ b/.changeset/sixty-pigs-shave.md @@ -0,0 +1,13 @@ +--- +'@backstage/backend-common': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-github-issues': patch +'@backstage/plugin-github-pull-requests-board': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 600e3edef8..7e42157d8d 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -22,7 +22,7 @@ import { ErrorRequestHandler } from 'express'; import express from 'express'; import { GerritIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { GitHubIntegration } from '@backstage/integration'; +import { GithubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; import { isChildPath } from '@backstage/cli-common'; import { JsonValue } from '@backstage/types'; @@ -409,7 +409,7 @@ export class Git { // @public export class GithubUrlReader implements UrlReader { constructor( - integration: GitHubIntegration, + integration: GithubIntegration, deps: { treeResponseFactory: ReadTreeResponseFactory; credentialsProvider: GithubCredentialsProvider; diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index fddb728648..a5f9d26de5 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -17,8 +17,8 @@ import { ConfigReader } from '@backstage/config'; import { GithubCredentialsProvider, - GitHubIntegration, - readGitHubIntegrationConfig, + GithubIntegration, + readGithubIntegrationConfig, } from '@backstage/integration'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; @@ -46,8 +46,8 @@ const mockCredentialsProvider = { } as unknown as GithubCredentialsProvider; const githubProcessor = new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'github.com', apiBaseUrl: 'https://api.github.com', @@ -58,8 +58,8 @@ const githubProcessor = new GithubUrlReader( ); const gheProcessor = new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'ghe.github.com', apiBaseUrl: 'https://ghe.github.com/api/v3', @@ -539,8 +539,8 @@ describe('GithubUrlReader', () => { expect(() => { /* eslint-disable no-new */ new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'ghe.mycompany.net', }), diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 3806253a6f..7abf1b2518 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -15,10 +15,10 @@ */ import { - getGitHubFileFetchUrl, + getGithubFileFetchUrl, DefaultGithubCredentialsProvider, GithubCredentialsProvider, - GitHubIntegration, + GithubIntegration, ScmIntegrations, } from '@backstage/integration'; import { RestEndpointMethodTypes } from '@octokit/rest'; @@ -72,7 +72,7 @@ export class GithubUrlReader implements UrlReader { }; constructor( - private readonly integration: GitHubIntegration, + private readonly integration: GithubIntegration, private readonly deps: { treeResponseFactory: ReadTreeResponseFactory; credentialsProvider: GithubCredentialsProvider; @@ -97,7 +97,7 @@ export class GithubUrlReader implements UrlReader { const credentials = await this.deps.credentialsProvider.getCredentials({ url, }); - const ghUrl = getGitHubFileFetchUrl( + const ghUrl = getGithubFileFetchUrl( url, this.integration.config, credentials, diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index cf52bec65b..0f24ded066 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -323,16 +323,19 @@ export function getGerritRequestOptions(config: GerritIntegrationConfig): { headers?: Record; }; +// @public @deprecated (undocumented) +export const getGitHubFileFetchUrl: typeof getGithubFileFetchUrl; + // @public -export function getGitHubFileFetchUrl( +export function getGithubFileFetchUrl( url: string, - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): string; // @public @deprecated export function getGitHubRequestOptions( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): { headers: Record; @@ -366,7 +369,7 @@ export type GithubAppConfig = { // @public export class GithubAppCredentialsMux { - constructor(config: GitHubIntegrationConfig); + constructor(config: GithubIntegrationConfig); // (undocumented) getAllInstallations(): Promise< RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] @@ -393,13 +396,22 @@ export interface GithubCredentialsProvider { // @public export type GithubCredentialType = 'app' | 'token'; -// @public -export class GitHubIntegration implements ScmIntegration { +// @public @deprecated (undocumented) +export class GitHubIntegration extends GithubIntegration { constructor(integrationConfig: GitHubIntegrationConfig); // (undocumented) get config(): GitHubIntegrationConfig; // (undocumented) static factory: ScmIntegrationsFactory; +} + +// @public +export class GithubIntegration implements ScmIntegration { + constructor(integrationConfig: GithubIntegrationConfig); + // (undocumented) + get config(): GithubIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory; // (undocumented) resolveEditUrl(url: string): string; // (undocumented) @@ -414,8 +426,11 @@ export class GitHubIntegration implements ScmIntegration { get type(): string; } +// @public @deprecated (undocumented) +export type GitHubIntegrationConfig = GithubIntegrationConfig; + // @public -export type GitHubIntegrationConfig = { +export type GithubIntegrationConfig = { host: string; apiBaseUrl?: string; rawBaseUrl?: string; @@ -473,7 +488,7 @@ export interface IntegrationsByType { // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; } @@ -551,15 +566,21 @@ export function readGerritIntegrationConfigs( configs: Config[], ): GerritIntegrationConfig[]; -// @public -export function readGitHubIntegrationConfig( - config: Config, -): GitHubIntegrationConfig; +// @public @deprecated (undocumented) +export const readGitHubIntegrationConfig: typeof readGithubIntegrationConfig; // @public -export function readGitHubIntegrationConfigs( +export function readGithubIntegrationConfig( + config: Config, +): GithubIntegrationConfig; + +// @public @deprecated (undocumented) +export const readGitHubIntegrationConfigs: typeof readGithubIntegrationConfigs; + +// @public +export function readGithubIntegrationConfigs( configs: Config[], -): GitHubIntegrationConfig[]; +): GithubIntegrationConfig[]; // @public export function readGitLabIntegrationConfig( @@ -576,8 +597,11 @@ export function readGoogleGcsIntegrationConfig( config: Config, ): GoogleGcsIntegrationConfig; +// @public @deprecated (undocumented) +export const replaceGitHubUrlType: typeof replaceGithubUrlType; + // @public -export function replaceGitHubUrlType( +export function replaceGithubUrlType( url: string, type: 'blob' | 'tree' | 'edit', ): string; @@ -616,7 +640,7 @@ export interface ScmIntegrationRegistry // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; resolveEditUrl(url: string): string; @@ -649,7 +673,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { // (undocumented) get gerrit(): ScmIntegrationsGroup; // (undocumented) - get github(): ScmIntegrationsGroup; + get github(): ScmIntegrationsGroup; // (undocumented) get gitlab(): ScmIntegrationsGroup; // (undocumented) @@ -681,7 +705,7 @@ export class SingleInstanceGithubCredentialsProvider implements GithubCredentialsProvider { // (undocumented) - static create: (config: GitHubIntegrationConfig) => GithubCredentialsProvider; + static create: (config: GithubIntegrationConfig) => GithubCredentialsProvider; getCredentials(opts: { url: string }): Promise; } ``` diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index d9fcaf36f0..f5fd480609 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -29,8 +29,8 @@ import { } from './bitbucketServer'; import { GerritIntegrationConfig } from './gerrit'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegrationConfig } from './github'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegrationConfig } from './github'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegrationConfig } from './gitlab'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { basicIntegrations } from './helpers'; @@ -61,9 +61,9 @@ describe('ScmIntegrations', () => { host: 'gerrit.local', } as GerritIntegrationConfig); - const github = new GitHubIntegration({ + const github = new GithubIntegration({ host: 'github.local', - } as GitHubIntegrationConfig); + } as GithubIntegrationConfig); const gitlab = new GitLabIntegration({ host: 'gitlab.local', diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index 6d745dcf1c..f5decebc66 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -21,7 +21,7 @@ import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegr import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { defaultScmResolveUrl } from './helpers'; import { ScmIntegration, ScmIntegrationsGroup } from './types'; @@ -42,7 +42,7 @@ export interface IntegrationsByType { bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; } @@ -62,7 +62,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { bitbucketCloud: BitbucketCloudIntegration.factory({ config }), bitbucketServer: BitbucketServerIntegration.factory({ config }), gerrit: GerritIntegration.factory({ config }), - github: GitHubIntegration.factory({ config }), + github: GithubIntegration.factory({ config }), gitlab: GitLabIntegration.factory({ config }), }); } @@ -98,7 +98,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gerrit; } - get github(): ScmIntegrationsGroup { + get github(): ScmIntegrationsGroup { return this.byType.github; } diff --git a/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts index 7c2b53e822..98b8edecef 100644 --- a/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts @@ -15,7 +15,7 @@ */ import { ScmIntegrations } from '../ScmIntegrations'; -import { GitHubIntegrationConfig } from './config'; +import { GithubIntegrationConfig } from './config'; import { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider'; import { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; @@ -65,7 +65,7 @@ describe('DefaultGithubCredentialsProvider tests', () => { ); jest.resetAllMocks(); SingleInstanceGithubCredentialsProvider.create = ( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, ) => { return { getCredentials: (_opts: { url: string }) => { diff --git a/packages/integration/src/github/GitHubIntegration.test.ts b/packages/integration/src/github/GithubIntegration.test.ts similarity index 86% rename from packages/integration/src/github/GitHubIntegration.test.ts rename to packages/integration/src/github/GithubIntegration.test.ts index ccceb34d88..d8e6020a64 100644 --- a/packages/integration/src/github/GitHubIntegration.test.ts +++ b/packages/integration/src/github/GithubIntegration.test.ts @@ -15,11 +15,11 @@ */ import { ConfigReader } from '@backstage/config'; -import { GitHubIntegration, replaceGitHubUrlType } from './GitHubIntegration'; +import { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; -describe('GitHubIntegration', () => { +describe('GithubIntegration', () => { it('has a working factory', () => { - const integrations = GitHubIntegration.factory({ + const integrations = GithubIntegration.factory({ config: new ConfigReader({ integrations: { github: [ @@ -39,7 +39,7 @@ describe('GitHubIntegration', () => { }); it('returns the basics', () => { - const integration = new GitHubIntegration({ + const integration = new GithubIntegration({ host: 'h.com', apiBaseUrl: 'a', rawBaseUrl: 'r', @@ -51,7 +51,7 @@ describe('GitHubIntegration', () => { }); it('resolveUrl', () => { - const integration = new GitHubIntegration({ host: 'h.com' }); + const integration = new GithubIntegration({ host: 'h.com' }); expect( integration.resolveUrl({ @@ -70,7 +70,7 @@ describe('GitHubIntegration', () => { }); it('resolve edit URL', () => { - const integration = new GitHubIntegration({ host: 'h.com' }); + const integration = new GithubIntegration({ host: 'h.com' }); expect( integration.resolveEditUrl( @@ -80,28 +80,28 @@ describe('GitHubIntegration', () => { }); }); -describe('replaceGitHubUrlType', () => { +describe('replaceGithubUrlType', () => { it('should replace with expected type', () => { expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/backstage/backstage/blob/master/README.md', 'edit', ), ).toBe('https://github.com/backstage/backstage/edit/master/README.md'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/webmodules/blob/blob/master/test', 'tree', ), ).toBe('https://github.com/webmodules/blob/tree/master/test'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/blob/blob/blob/master/test', 'tree', ), ).toBe('https://github.com/blob/blob/tree/master/test'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/backstage/backstage/edit/tree/README.md', 'blob', ), diff --git a/packages/integration/src/github/GitHubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts similarity index 78% rename from packages/integration/src/github/GitHubIntegration.ts rename to packages/integration/src/github/GithubIntegration.ts index 1db0b1fae5..50fc129ccf 100644 --- a/packages/integration/src/github/GitHubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -17,8 +17,8 @@ import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; import { ScmIntegration, ScmIntegrationsFactory } from '../types'; import { - GitHubIntegrationConfig, - readGitHubIntegrationConfigs, + GithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; /** @@ -26,18 +26,18 @@ import { * * @public */ -export class GitHubIntegration implements ScmIntegration { - static factory: ScmIntegrationsFactory = ({ config }) => { - const configs = readGitHubIntegrationConfigs( +export class GithubIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory = ({ config }) => { + const configs = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], ); return basicIntegrations( - configs.map(c => new GitHubIntegration(c)), + configs.map(c => new GithubIntegration(c)), i => i.config.host, ); }; - constructor(private readonly integrationConfig: GitHubIntegrationConfig) {} + constructor(private readonly integrationConfig: GithubIntegrationConfig) {} get type(): string { return 'github'; @@ -47,7 +47,7 @@ export class GitHubIntegration implements ScmIntegration { return this.integrationConfig.host; } - get config(): GitHubIntegrationConfig { + get config(): GithubIntegrationConfig { return this.integrationConfig; } @@ -59,11 +59,11 @@ export class GitHubIntegration implements ScmIntegration { // GitHub uses blob URLs for files and tree urls for directory listings. But // there is a redirect from tree to blob for files, so we can always return // tree urls here. - return replaceGitHubUrlType(defaultScmResolveUrl(options), 'tree'); + return replaceGithubUrlType(defaultScmResolveUrl(options), 'tree'); } resolveEditUrl(url: string): string { - return replaceGitHubUrlType(url, 'edit'); + return replaceGithubUrlType(url, 'edit'); } } @@ -74,7 +74,7 @@ export class GitHubIntegration implements ScmIntegration { * @param type - The desired type, e.g. "blob" * @public */ -export function replaceGitHubUrlType( +export function replaceGithubUrlType( url: string, type: 'blob' | 'tree' | 'edit', ): string { diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index 02f050f69f..d42ad4597d 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -15,7 +15,7 @@ */ import parseGitUrl from 'git-url-parse'; -import { GithubAppConfig, GitHubIntegrationConfig } from './config'; +import { GithubAppConfig, GithubIntegrationConfig } from './config'; import { createAppAuth } from '@octokit/auth-app'; import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; import { DateTime } from 'luxon'; @@ -199,7 +199,7 @@ class GithubAppManager { export class GithubAppCredentialsMux { private readonly apps: GithubAppManager[]; - constructor(config: GitHubIntegrationConfig) { + constructor(config: GithubIntegrationConfig) { this.apps = config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? []; } @@ -259,7 +259,7 @@ export class SingleInstanceGithubCredentialsProvider implements GithubCredentialsProvider { static create: ( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, ) => GithubCredentialsProvider = config => { return new SingleInstanceGithubCredentialsProvider( new GithubAppCredentialsMux(config), diff --git a/packages/integration/src/github/config.test.ts b/packages/integration/src/github/config.test.ts index 73efb4f81a..c4d9eb1900 100644 --- a/packages/integration/src/github/config.test.ts +++ b/packages/integration/src/github/config.test.ts @@ -17,18 +17,18 @@ import { Config, ConfigReader } from '@backstage/config'; import { loadConfigSchema } from '@backstage/config-loader'; import { - GitHubIntegrationConfig, - readGitHubIntegrationConfig, - readGitHubIntegrationConfigs, + GithubIntegrationConfig, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; -describe('readGitHubIntegrationConfig', () => { - function buildConfig(provider: Partial) { +describe('readGithubIntegrationConfig', () => { + function buildConfig(provider: Partial) { return new ConfigReader(provider); } async function buildFrontendConfig( - data: Partial, + data: Partial, ): Promise { const fullSchema = await loadConfigSchema({ dependencies: ['@backstage/integration'], @@ -52,7 +52,7 @@ describe('readGitHubIntegrationConfig', () => { } it('reads all values', () => { - const output = readGitHubIntegrationConfig( + const output = readGithubIntegrationConfig( buildConfig({ host: 'a.com', apiBaseUrl: 'https://a.com/api', @@ -69,7 +69,7 @@ describe('readGitHubIntegrationConfig', () => { }); it('injects the correct GitHub API base URL when missing', () => { - const output = readGitHubIntegrationConfig( + const output = readGithubIntegrationConfig( buildConfig({ host: 'github.com' }), ); expect(output).toEqual({ @@ -87,22 +87,22 @@ describe('readGitHubIntegrationConfig', () => { token: 't', }; expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, host: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, host: 7 })), ).toThrow(/host/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })), ).toThrow(/apiBaseUrl/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, rawBaseUrl: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, rawBaseUrl: 7 })), ).toThrow(/rawBaseUrl/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, token: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, token: 7 })), ).toThrow(/token/); }); it('works on the frontend', async () => { expect( - readGitHubIntegrationConfig( + readGithubIntegrationConfig( await buildFrontendConfig({ host: 'a.com', apiBaseUrl: 'https://a.com/api', @@ -118,15 +118,15 @@ describe('readGitHubIntegrationConfig', () => { }); }); -describe('readGitHubIntegrationConfigs', () => { +describe('readGithubIntegrationConfigs', () => { function buildConfig( - providers: Partial[], + providers: Partial[], ): Config[] { return providers.map(provider => new ConfigReader(provider)); } it('reads all values', () => { - const output = readGitHubIntegrationConfigs( + const output = readGithubIntegrationConfigs( buildConfig([ { host: 'a.com', @@ -145,7 +145,7 @@ describe('readGitHubIntegrationConfigs', () => { }); it('adds a default GitHub entry when missing', () => { - const output = readGitHubIntegrationConfigs(buildConfig([])); + const output = readGithubIntegrationConfigs(buildConfig([])); expect(output).toEqual([ { host: 'github.com', diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 93801e96fb..cd76eba3d3 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -27,7 +27,7 @@ const GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com'; * * @public */ -export type GitHubIntegrationConfig = { +export type GithubIntegrationConfig = { /** * The host of the target that this matches on, e.g. "github.com" */ @@ -117,9 +117,9 @@ export type GithubAppConfig = { * @param config - The config object of a single integration * @public */ -export function readGitHubIntegrationConfig( +export function readGithubIntegrationConfig( config: Config, -): GitHubIntegrationConfig { +): GithubIntegrationConfig { const host = config.getOptionalString('host') ?? GITHUB_HOST; let apiBaseUrl = config.getOptionalString('apiBaseUrl'); let rawBaseUrl = config.getOptionalString('rawBaseUrl'); @@ -163,11 +163,11 @@ export function readGitHubIntegrationConfig( * @param configs - All of the integration config objects * @public */ -export function readGitHubIntegrationConfigs( +export function readGithubIntegrationConfigs( configs: Config[], -): GitHubIntegrationConfig[] { +): GithubIntegrationConfig[] { // First read all the explicit integrations - const result = configs.map(readGitHubIntegrationConfig); + const result = configs.map(readGithubIntegrationConfig); // If no explicit github.com integration was added, put one in the list as // a convenience diff --git a/packages/integration/src/github/core.test.ts b/packages/integration/src/github/core.test.ts index c5d04f5b4f..982a096b43 100644 --- a/packages/integration/src/github/core.test.ts +++ b/packages/integration/src/github/core.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { GitHubIntegrationConfig } from './config'; -import { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; +import { GithubIntegrationConfig } from './config'; +import { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; import { GithubCredentials } from './types'; describe('github core', () => { @@ -37,12 +37,12 @@ describe('github core', () => { describe('getGitHubRequestOptions', () => { it('inserts a token when needed', () => { - const withToken: GitHubIntegrationConfig = { + const withToken: GithubIntegrationConfig = { host: '', rawBaseUrl: '', token: 'A', }; - const withoutToken: GitHubIntegrationConfig = { + const withoutToken: GithubIntegrationConfig = { host: '', rawBaseUrl: '', }; @@ -57,21 +57,21 @@ describe('github core', () => { }); }); - describe('getGitHubFileFetchUrl', () => { + describe('getGithubFileFetchUrl', () => { it('rejects targets that do not look like URLs', () => { - const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' }; - expect(() => getGitHubFileFetchUrl('a/b', config, noCredentials)).toThrow( + const config: GithubIntegrationConfig = { host: '', apiBaseUrl: '' }; + expect(() => getGithubFileFetchUrl('a/b', config, noCredentials)).toThrow( /Incorrect URL: a\/b/, ); }); it('happy path for github api', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', apiBaseUrl: 'https://api.github.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, appCredentials, @@ -80,7 +80,7 @@ describe('github core', () => { 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname', ); expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -91,12 +91,12 @@ describe('github core', () => { }); it('happy path for ghe api', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', apiBaseUrl: 'https://ghe.mycompany.net/api/v3', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, appCredentials, @@ -105,7 +105,7 @@ describe('github core', () => { 'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname', ); expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -116,12 +116,12 @@ describe('github core', () => { }); it('happy path for github tree', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', apiBaseUrl: 'https://api.github.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/tree/branchname/path/to/c.yaml', config, tokenCredentials, @@ -132,12 +132,12 @@ describe('github core', () => { }); it('happy path for ghe tree', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', apiBaseUrl: 'https://ghe.mycompany.net/api/v3', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/tree/branchname/path/to/c.yaml', config, tokenCredentials, @@ -148,12 +148,12 @@ describe('github core', () => { }); it('happy path for github raw', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', rawBaseUrl: 'https://raw.githubusercontent.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -164,12 +164,12 @@ describe('github core', () => { }); it('happy path for ghe raw', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', rawBaseUrl: 'https://ghe.mycompany.net/raw', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, diff --git a/packages/integration/src/github/core.ts b/packages/integration/src/github/core.ts index 5b630d5182..a755f3383c 100644 --- a/packages/integration/src/github/core.ts +++ b/packages/integration/src/github/core.ts @@ -15,7 +15,7 @@ */ import parseGitUrl from 'git-url-parse'; -import { GitHubIntegrationConfig } from './config'; +import { GithubIntegrationConfig } from './config'; import { GithubCredentials } from './types'; /** @@ -33,9 +33,9 @@ import { GithubCredentials } from './types'; * @param config - The relevant provider config * @public */ -export function getGitHubFileFetchUrl( +export function getGithubFileFetchUrl( url: string, - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): string { try { @@ -71,7 +71,7 @@ export function getGitHubFileFetchUrl( * @public */ export function getGitHubRequestOptions( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): { headers: Record } { const headers: Record = {}; @@ -88,7 +88,7 @@ export function getGitHubRequestOptions( } export function chooseEndpoint( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): 'api' | 'raw' { if (config.apiBaseUrl && (credentials.token || !config.rawBaseUrl)) { diff --git a/packages/integration/src/github/deprecated.ts b/packages/integration/src/github/deprecated.ts new file mode 100644 index 0000000000..127040c1a7 --- /dev/null +++ b/packages/integration/src/github/deprecated.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + GithubIntegrationConfig, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, +} from './config'; +import { getGithubFileFetchUrl } from './core'; +import { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; +import { ScmIntegrationsFactory } from '../types'; + +/** + * @public + * @deprecated Use {@link getGithubFileFetchUrl} instead. + */ +export const getGitHubFileFetchUrl = getGithubFileFetchUrl; + +/** + * @public + * @deprecated Use {@link GithubIntegrationConfig} instead. + */ +export type GitHubIntegrationConfig = GithubIntegrationConfig; + +/** + * @public + * @deprecated Use {@link GithubIntegration} instead. + */ +export class GitHubIntegration extends GithubIntegration { + static factory: ScmIntegrationsFactory = + GithubIntegration.factory; + + constructor(integrationConfig: GitHubIntegrationConfig) { + super(integrationConfig as GithubIntegrationConfig); + } + + get config(): GitHubIntegrationConfig { + return super.config as GitHubIntegrationConfig; + } +} + +/** + * @public + * @deprecated Use {@link readGithubIntegrationConfig} instead. + */ +export const readGitHubIntegrationConfig = readGithubIntegrationConfig; + +/** + * @public + * @deprecated Use {@link readGithubIntegrationConfigs} instead. + */ +export const readGitHubIntegrationConfigs = readGithubIntegrationConfigs; + +/** + * @public + * @deprecated Use {@link replaceGithubUrlType} instead. + */ +export const replaceGitHubUrlType = replaceGithubUrlType; diff --git a/packages/integration/src/github/index.ts b/packages/integration/src/github/index.ts index c6a7799c3d..816b0d2ff9 100644 --- a/packages/integration/src/github/index.ts +++ b/packages/integration/src/github/index.ts @@ -15,11 +15,11 @@ */ export { - readGitHubIntegrationConfig, - readGitHubIntegrationConfigs, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; -export type { GithubAppConfig, GitHubIntegrationConfig } from './config'; -export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; +export type { GithubAppConfig, GithubIntegrationConfig } from './config'; +export { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; export { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; export { GithubAppCredentialsMux, @@ -30,4 +30,6 @@ export type { GithubCredentialsProvider, GithubCredentialType, } from './types'; -export { GitHubIntegration, replaceGitHubUrlType } from './GitHubIntegration'; +export { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; + +export * from './deprecated'; diff --git a/packages/integration/src/registry.ts b/packages/integration/src/registry.ts index f4f759cfe9..debb819156 100644 --- a/packages/integration/src/registry.ts +++ b/packages/integration/src/registry.ts @@ -21,7 +21,7 @@ import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegr import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; /** @@ -40,7 +40,7 @@ export interface ScmIntegrationRegistry bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; /** diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 5b94f7169e..195b150dea 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -12,7 +12,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { GitHubIntegrationConfig } from '@backstage/integration'; +import { GithubIntegrationConfig } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -160,7 +160,7 @@ export class GithubOrgEntityProvider implements EntityProvider { constructor(options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }); diff --git a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts index c5315ad166..7bb73990b3 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts @@ -19,7 +19,7 @@ import { DefaultGithubCredentialsProvider, GithubAppCredentialsMux, GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrationRegistry, ScmIntegrations, } from '@backstage/integration'; @@ -182,7 +182,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { // Note: Does not support usage of PATs private async getAllOrgs( - gitHubConfig: GitHubIntegrationConfig, + gitHubConfig: GithubIntegrationConfig, ): Promise { const githubAppMux = new GithubAppCredentialsMux(gitHubConfig); const installs = await githubAppMux.getAllInstallations(); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 2004cb7469..378e1a96a7 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -19,8 +19,8 @@ import { Config } from '@backstage/config'; import { GithubCredentialsProvider, ScmIntegrations, - GitHubIntegrationConfig, - GitHubIntegration, + GithubIntegrationConfig, + GithubIntegration, SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; import { @@ -51,7 +51,7 @@ import { satisfiesTopicFilter } from '../lib/util'; export class GithubEntityProvider implements EntityProvider { private readonly config: GithubEntityProviderConfig; private readonly logger: Logger; - private readonly integration: GitHubIntegrationConfig; + private readonly integration: GithubIntegrationConfig; private readonly scheduleFn: () => Promise; private connection?: EntityProviderConnection; private readonly githubCredentialsProvider: GithubCredentialsProvider; @@ -101,7 +101,7 @@ export class GithubEntityProvider implements EntityProvider { private constructor( config: GithubEntityProviderConfig, - integration: GitHubIntegration, + integration: GithubIntegration, logger: Logger, taskRunner: TaskRunner, ) { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 1d8d125d91..b3fc5022ec 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -18,7 +18,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, } from '@backstage/integration'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { graphql } from '@octokit/graphql'; @@ -87,7 +87,7 @@ describe('GithubOrgEntityProvider', () => { }; const logger = getVoidLogger(); - const gitHubConfig: GitHubIntegrationConfig = { + const gitHubConfig: GithubIntegrationConfig = { host: 'https://github.com', }; diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index f8fafbe53c..2fc8ce7300 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -24,7 +24,7 @@ import { Config } from '@backstage/config'; import { DefaultGithubCredentialsProvider, GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrations, SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; @@ -134,7 +134,7 @@ export class GithubOrgEntityProvider implements EntityProvider { private options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }, diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 0663122253..4981331c89 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -21,7 +21,7 @@ import { IdentityApi, } from '@backstage/core-plugin-api'; import { - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrationRegistry, } from '@backstage/integration'; import { ScmAuthApi } from '@backstage/integration-react'; @@ -239,7 +239,7 @@ the component will become available.\n\nFor more information, read an \ body: string; fileContent: string; repositoryUrl: string; - githubIntegrationConfig: GitHubIntegrationConfig; + githubIntegrationConfig: GithubIntegrationConfig; }): Promise<{ link: string; location: string }> { const { owner, diff --git a/plugins/git-release-manager/src/api/GitReleaseClient.ts b/plugins/git-release-manager/src/api/GitReleaseClient.ts index 996b8bfdd3..91b1dbbecc 100644 --- a/plugins/git-release-manager/src/api/GitReleaseClient.ts +++ b/plugins/git-release-manager/src/api/GitReleaseClient.ts @@ -15,7 +15,7 @@ */ import { Octokit } from '@octokit/rest'; -import { GitHubIntegration, ScmIntegrations } from '@backstage/integration'; +import { GithubIntegration, ScmIntegrations } from '@backstage/integration'; import { DISABLE_CACHE } from '../constants/constants'; import { Project } from '../contexts/ProjectContext'; @@ -50,7 +50,7 @@ export class GitReleaseClient implements GitReleaseApi { private getGithubIntegrationConfig({ gitHubIntegrations, }: { - gitHubIntegrations: GitHubIntegration[]; + gitHubIntegrations: GithubIntegration[]; }) { const defaultIntegration = gitHubIntegrations.find( ({ config: { host } }) => host === 'github.com', diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index ebe3d92c4f..724081320f 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { GithubActionsApi } from './GithubActionsApi'; import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; import { ConfigApi, OAuthApi } from '@backstage/core-plugin-api'; @@ -36,7 +36,7 @@ export class GithubActionsClient implements GithubActionsApi { private async getOctokit(hostname?: string): Promise { // TODO: Get access token for the specified hostname const token = await this.githubAuthApi.getAccessToken(['repo']); - const configs = readGitHubIntegrationConfigs( + const configs = readGithubIntegrationConfigs( this.configApi.getOptionalConfigArray('integrations.github') ?? [], ); const githubIntegrationConfig = configs.find( diff --git a/plugins/github-actions/src/components/Cards/Cards.tsx b/plugins/github-actions/src/components/Cards/Cards.tsx index 79777ed8c3..e23f7f7d59 100644 --- a/plugins/github-actions/src/components/Cards/Cards.tsx +++ b/plugins/github-actions/src/components/Cards/Cards.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import { LinearProgress, @@ -88,7 +88,7 @@ export const LatestWorkflowRunCard = (props: { const config = useApi(configApiRef); const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = ( diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx index 931b5b676e..89fd40da33 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import React, { useEffect } from 'react'; import { Link as RouterLink } from 'react-router-dom'; @@ -52,7 +52,7 @@ export const RecentWorkflowRunsCard = (props: { const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 4e920a4e3a..cda0b9c219 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { Accordion, AccordionDetails, @@ -170,7 +170,7 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName && projectName.split('/')) || []; diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx index bf66399288..075b73dd98 100644 --- a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx +++ b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { LogViewer } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { Accordion, AccordionSummary, @@ -80,7 +80,7 @@ export const WorkflowRunLogs = ({ const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName && projectName.split('/')) || []; diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index c8c42eb1bc..fb80376354 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -31,7 +31,7 @@ import SyncIcon from '@material-ui/icons/Sync'; import { buildRouteRef } from '../../routes'; import { getProjectNameFromEntity } from '../getProjectNameFromEntity'; import { Entity } from '@backstage/catalog-model'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { EmptyState, Table, TableColumn } from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; @@ -159,7 +159,7 @@ export const WorkflowRunsTable = ({ const config = useApi(configApiRef); const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName ?? '/').split('/'); diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.ts b/plugins/github-issues/src/api/gitHubIssuesApi.ts index b4be445d13..bef1b20441 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.ts +++ b/plugins/github-issues/src/api/gitHubIssuesApi.ts @@ -20,7 +20,7 @@ import { ErrorApi, OAuthApi, } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { ForwardedError } from '@backstage/errors'; /** @internal */ @@ -115,7 +115,7 @@ export const gitHubIssuesApi = ( let octokit: Octokit; const getOctokit = async () => { - const baseUrl = readGitHubIntegrationConfigs( + const baseUrl = readGithubIntegrationConfigs( configApi.getOptionalConfigArray('integrations.github') ?? [], )[0].apiBaseUrl; diff --git a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts index a864f9950e..6dadcef6d2 100644 --- a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts +++ b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts @@ -19,7 +19,7 @@ import { githubAuthApiRef, configApiRef, } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; let octokit: any; @@ -27,7 +27,7 @@ export const useOctokitGraphQl = () => { const auth = useApi(githubAuthApiRef); const config = useApi(configApiRef); - const baseUrl = readGitHubIntegrationConfigs( + const baseUrl = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].apiBaseUrl; diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts b/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts index 19cb47ec40..22dc359942 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts @@ -18,7 +18,7 @@ import parseGitUrl from 'git-url-parse'; import { useApi } from '@backstage/core-plugin-api'; import { - replaceGitHubUrlType, + replaceGithubUrlType, replaceGitLabUrlType, } from '@backstage/integration'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; @@ -31,7 +31,7 @@ import { PAGE_EDIT_LINK_SELECTOR } from './constants'; const resolveBlobUrl = (url: string, type: string) => { if (type === 'github') { - return replaceGitHubUrlType(url, 'blob'); + return replaceGithubUrlType(url, 'blob'); } else if (type === 'gitlab') { return replaceGitLabUrlType(url, 'blob'); } diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts index 9c7865936c..3fca7ddeb9 100644 --- a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts @@ -16,7 +16,7 @@ import type { Transformer } from './index'; import { - replaceGitHubUrlType, + replaceGithubUrlType, ScmIntegrationRegistry, } from '@backstage/integration'; import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined'; @@ -59,7 +59,7 @@ export const addGitFeedbackLink = ( // Convert GitHub edit url to blob type so it can be parsed by git-url-parse correctly const gitUrl = integration?.type === 'github' - ? replaceGitHubUrlType(sourceURL.href, 'blob') + ? replaceGithubUrlType(sourceURL.href, 'blob') : sourceURL.href; const gitInfo = parseGitUrl(gitUrl); const repoPath = `/${gitInfo.organization}/${gitInfo.name}`; From e0aa20ab120fcfbcd6cff50e4ef798586f639efa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:21:12 +0200 Subject: [PATCH 074/221] REVIEWING: use valid png link Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 6a54ae875e..29bdcc3877 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -110,7 +110,7 @@ It also includes changes affecting both the Catalog and Catalog React library. I Added a new `EntityList` component that can be used to display detailed information about a list of entities. The component looks like this: -![EntityList screenshot](./screenshot.png) +![EntityList screenshot](./docs/assets/headline.png) It accepts the following properties: From c20e2b2f33f6571e92bca42d5e72e303c0afbf0b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:26:07 +0200 Subject: [PATCH 075/221] Update REVIEWING.md Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 29bdcc3877..1e34938a0f 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -200,7 +200,7 @@ When modifying an input type, any change that increases constraints are breaking On the other hand, it's fine to relax constraints without it being a breaking change. For example, if we made the `title` prop optional, that would not be breaking. Likewise, if we changed the type of `size` to `'small' | 'medium' | 'large' | 'huge'`, that would not be breaking either. It is also possible to add new properties without it being a breaking change, as long as they are optional. -There's an edge-case where completely removing a property is also considered a breaking change. That's because of TypeScript being strict and refusing unknown properties, rather than a runtime breaking change. It is typically and easy thing for consumers to fix though. +There's an edge-case where completely removing a property is also considered a breaking change. That's because of TypeScript being strict and refusing unknown properties, rather than a runtime breaking change. It is typically an easy thing for consumers to fix though. Another way to think about the rules for evolving input types is that the old type must be assignable to the new type. In this case for example `_props: NewComponentProps = {} as OldComponentProps`. It's not a silver bullet though, because of edge-cases like the one mentioned above. From b573005f62f5f10220b3e5a841443906b7d0306c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:26:35 +0200 Subject: [PATCH 076/221] Update REVIEWING.md Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 1e34938a0f..38bae08423 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -155,7 +155,7 @@ For tricky behavioral changes you may simply need to let end users provide feedb Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. If you determine that a change is breaking at the TypeScript level, then it is a breaking change. -In this section we will be talking about changed "types", but by that we mean any kind of exported symbol from a packages, such as TypeScript types aliases or interfaces, functions, classes, constants, etc. +In this section we will be talking about changed "types", but by that we mean any kind of exported symbol from packages, such as TypeScript types aliases or interfaces, functions, classes, constants, etc. #### API Reports From 1fc2b3efe2136bab76a10d04c46c621cd97daff9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 19 Oct 2022 16:30:03 +0200 Subject: [PATCH 077/221] Update REVIEWING.md Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- REVIEWING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/REVIEWING.md b/REVIEWING.md index 38bae08423..9d88c36b36 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -59,7 +59,7 @@ In general our changeset feedback bot will take care of informing whether a chan Changes that do NOT need a new changeset: -- Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. +- Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx`, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. - When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset instead. - Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. - Changes that do not end up having an effect on the published package, such as whitespace fixes or code formatting changes. Although it's also fine to have a short changeset for these kind of changes too. From 383574c49bcf3b6c9f0fd8ddf26013a8ca6c1bcf Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Wed, 19 Oct 2022 11:47:10 -0400 Subject: [PATCH 078/221] Update screenshots to match latest plugin Signed-off-by: Adam Harvey --- .changeset/sweet-readers-compare.md | 5 +++++ plugins/circleci/README.md | 11 +++++++---- plugins/circleci/src/assets/screenshot-1.png | Bin 158862 -> 0 bytes plugins/circleci/src/assets/screenshot-2.png | Bin 138245 -> 0 bytes .../src/assets/screenshot-build-details.png | Bin 0 -> 408215 bytes .../src/assets/screenshot-build-failure.png | Bin 0 -> 333305 bytes .../src/assets/screenshot-pipeline-list.png | Bin 0 -> 389554 bytes 7 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .changeset/sweet-readers-compare.md delete mode 100644 plugins/circleci/src/assets/screenshot-1.png delete mode 100644 plugins/circleci/src/assets/screenshot-2.png create mode 100644 plugins/circleci/src/assets/screenshot-build-details.png create mode 100644 plugins/circleci/src/assets/screenshot-build-failure.png create mode 100644 plugins/circleci/src/assets/screenshot-pipeline-list.png diff --git a/.changeset/sweet-readers-compare.md b/.changeset/sweet-readers-compare.md new file mode 100644 index 0000000000..dd61b0afdc --- /dev/null +++ b/.changeset/sweet-readers-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-circleci': patch +--- + +Update screenshots in documentation to match latest CircleCI plugin diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index fa80469991..741d1714fa 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -2,8 +2,11 @@ Website: [https://circleci.com/](https://circleci.com/) - - +## Screenshots + + + + ## Setup @@ -37,13 +40,13 @@ const cicdContent = ( # In app-config.yaml proxy: '/circleci/api': - target: https://circleci.com/api/v1.1 + target: https://app.circleci.com/api/v1.1 headers: Circle-Token: ${CIRCLECI_AUTH_TOKEN} ``` 5. Get and provide a `CIRCLECI_AUTH_TOKEN` as an environment variable (see the [CircleCI docs](https://circleci.com/docs/api/#add-an-api-token)). -6. Add a `circleci.com/project-slug` annotation to your respective `catalog-info.yaml` files, on the format // (https://backstage.io/docs/architecture-decisions/adrs-adr002#format). +6. Add an annotation to your respective `catalog-info.yaml` files, with the format `circleci.com/project-slug: //` (See reference in [ADR002](https://backstage.io/docs/architecture-decisions/adrs-adr002#format)). ```yaml # Example catalog-info.yaml entity definition file diff --git a/plugins/circleci/src/assets/screenshot-1.png b/plugins/circleci/src/assets/screenshot-1.png deleted file mode 100644 index bef8a5a06fa283c022d0d87db26f7c2ec4961df7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158862 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 {6aHu&=}6VN#IAw^2| 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 UOK5W;9>$=pzEADJ-Xnhh3rAE?&;S4c diff --git a/plugins/circleci/src/assets/screenshot-2.png b/plugins/circleci/src/assets/screenshot-2.png deleted file mode 100644 index e4b82990131c00348ed187ecd7f8870371e35caa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138245 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 diff --git a/plugins/circleci/src/assets/screenshot-build-details.png b/plugins/circleci/src/assets/screenshot-build-details.png new file mode 100644 index 0000000000000000000000000000000000000000..fc83ae56b505c66fcfd772bd74b948ffcaa55e37 GIT binary patch literal 408215 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�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 z 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

; + export const StatusChip = ({ alert }: { alert: Alert }) => { const label = `${alertStatusLabels[alert.status]}`; diff --git a/plugins/ilert/src/components/AlertsPage/TableTitle.tsx b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx index c610e91d94..9aab95c6f3 100644 --- a/plugins/ilert/src/components/AlertsPage/TableTitle.tsx +++ b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx @@ -22,7 +22,7 @@ import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import React from 'react'; import { ACCEPTED, AlertStatus, PENDING, RESOLVED } from '../../types'; -import { alertStatusLabels } from '../Alert/AlertStatus'; +import { alertStatusLabels } from './StatusChip'; const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx index f9c28430d8..8ef5a990ec 100644 --- a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx +++ b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useApi } from '@backstage/core-plugin-api'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import { makeStyles } from '@material-ui/core/styles'; @@ -20,6 +21,7 @@ import Typography from '@material-ui/core/Typography'; import RepeatIcon from '@material-ui/icons/Repeat'; import { DateTime as dt } from 'luxon'; import React from 'react'; +import { ilertApiRef } from '../../api'; import { Shift } from '../../types'; import { ShiftOverrideModal } from '../Shift/ShiftOverrideModal'; @@ -48,6 +50,7 @@ export const OnCallShiftItem = ({ refetchOnCallSchedules: () => void; }) => { const classes = useStyles(); + const ilertApi = useApi(ilertApiRef); const [isModalOpened, setIsModalOpened] = React.useState(false); const handleOverride = () => { @@ -71,7 +74,7 @@ export const OnCallShiftItem = ({ {shift && shift.user ? ( - {`${shift.user.firstName} ${shift.user.lastName}`} + {ilertApi.getUserInitials(shift.user)} ) : null} diff --git a/plugins/ilert/src/components/Service/ServiceLink.tsx b/plugins/ilert/src/components/Service/ServiceLink.tsx index f8b1e88531..8e7868a2d2 100644 --- a/plugins/ilert/src/components/Service/ServiceLink.tsx +++ b/plugins/ilert/src/components/Service/ServiceLink.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { ilertApiRef } from '../../api'; import { Service } from '../../types'; @@ -21,23 +20,12 @@ import { Service } from '../../types'; import { Link } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - export const ServiceLink = ({ service }: { service: Service | null }) => { const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); if (!service) { return null; } - return ( - - #{service.id} - - ); + return #{service.id}; }; diff --git a/plugins/ilert/src/components/Service/ServiceStatus.tsx b/plugins/ilert/src/components/Service/ServiceStatus.tsx deleted file mode 100644 index 05923058aa..0000000000 --- a/plugins/ilert/src/components/Service/ServiceStatus.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { StatusError, StatusOK } from '@backstage/core-components'; -import { makeStyles } from '@material-ui/core/styles'; -import Tooltip from '@material-ui/core/Tooltip'; -import React from 'react'; -import { - DEGRADED, - MAJOR_OUTAGE, - OPERATIONAL, - PARTIAL_OUTAGE, - Service, - UNDER_MAINTENANCE, -} from '../../types'; - -const useStyles = makeStyles({ - denseListIcon: { - marginRight: 0, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - }, -}); - -export const serviceStatusLabels = { - [OPERATIONAL]: 'Operational', - [UNDER_MAINTENANCE]: 'Under maintenance', - [DEGRADED]: 'Degraded', - [PARTIAL_OUTAGE]: 'Partial outage', - [MAJOR_OUTAGE]: 'Major outage', -} as Record; - -export const ServiceStatus = ({ service }: { service: Service }) => { - const classes = useStyles(); - - return ( - -
- {service.status === 'OPERATIONAL' ? : } -
-
- ); -}; diff --git a/plugins/ilert/src/components/Service/index.ts b/plugins/ilert/src/components/Service/index.ts index 5227f44573..11b2148046 100644 --- a/plugins/ilert/src/components/Service/index.ts +++ b/plugins/ilert/src/components/Service/index.ts @@ -14,4 +14,3 @@ * limitations under the License. */ export * from './ServiceActionsMenu'; -export * from './ServiceStatus'; diff --git a/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx index e3afd4cc1a..0e012b07a2 100644 --- a/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx +++ b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx @@ -19,7 +19,6 @@ import { ResponseErrorPanel, SupportButton, } from '@backstage/core-components'; -import { AuthenticationError } from '@backstage/errors'; import React from 'react'; import { useServices } from '../../hooks/useServices'; import { MissingAuthorizationHeaderError } from '../Errors'; @@ -32,7 +31,7 @@ export const ServicesPage = () => { ] = useServices(true); if (error) { - if (error instanceof AuthenticationError) { + if (error.name === 'AuthenticationError') { return ( diff --git a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx index 4ed1d581ae..30a83e4cec 100644 --- a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx +++ b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx @@ -50,91 +50,59 @@ export const ServicesTable = ({ }) => { const classes = useStyles(); - // const xsColumnStyle = { - // width: '5%', - // maxWidth: '5%', - // }; const smColumnStyle = { width: '10%', maxWidth: '10%', }; - // const mdColumnStyle = { - // width: '15%', - // maxWidth: '15%', - // }; - // const lgColumnStyle = { - // width: '20%', - // maxWidth: '20%', - // }; const xlColumnStyle = { width: '30%', maxWidth: '30%', }; - - const idColumn: TableColumn = { + const idColumn: TableColumn = { title: 'ID', field: 'id', highlight: true, cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const nameColumn: TableColumn = { + const nameColumn: TableColumn = { title: 'Name', field: 'name', cellStyle: !compact ? xlColumnStyle : undefined, headerStyle: !compact ? xlColumnStyle : undefined, - render: rowData => {(rowData as Service).name}, + render: rowData => {rowData.name}, }; - const statusColumn: TableColumn = { + const statusColumn: TableColumn = { title: 'Status', field: 'status', cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const uptimeColumn: TableColumn = { + const uptimeColumn: TableColumn = { title: 'Uptime in the last 90 days', field: 'uptimePercentage', cellStyle: smColumnStyle, headerStyle: smColumnStyle, render: rowData => ( - - {(rowData as Service).uptime.uptimePercentage.p90} - + {rowData.uptime.uptimePercentage.p90} ), }; - const actionsColumn: TableColumn = { + const actionsColumn: TableColumn = { title: '', field: '', cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const columns: TableColumn[] = compact + const columns: TableColumn[] = compact ? [nameColumn, statusColumn, uptimeColumn, actionsColumn] : [idColumn, nameColumn, statusColumn, uptimeColumn, actionsColumn]; - let tableStyle: React.CSSProperties = {}; - if (compact) { - tableStyle = { - width: '100%', - maxWidth: '100%', - minWidth: '0', - height: 'calc(100% - 10px)', - boxShadow: 'none !important', - borderRadius: 'none !important', - }; - } else { - tableStyle = { - width: '100%', - maxWidth: '100%', - }; - } return (
; + export const StatusChip = ({ service }: { service: Service }) => { const label = `${serviceStatusLabels[service.status]}`; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx index 60daddf9df..edc6922e0d 100644 --- a/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx +++ b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { ilertApiRef } from '../../api'; import { StatusPage } from '../../types'; @@ -21,29 +20,19 @@ import { StatusPage } from '../../types'; import { Link } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - export const StatusPageLink = ({ statusPage, }: { statusPage: StatusPage | null; }) => { const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); if (!statusPage) { return null; } return ( - + #{statusPage.id} ); diff --git a/plugins/ilert/src/components/StatusPage/StatusPageStatus.tsx b/plugins/ilert/src/components/StatusPage/StatusPageStatus.tsx deleted file mode 100644 index b40db5d8f3..0000000000 --- a/plugins/ilert/src/components/StatusPage/StatusPageStatus.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { StatusError, StatusOK } from '@backstage/core-components'; -import { makeStyles } from '@material-ui/core/styles'; -import Tooltip from '@material-ui/core/Tooltip'; -import React from 'react'; -import { - DEGRADED, - MAJOR_OUTAGE, - OPERATIONAL, - PARTIAL_OUTAGE, - StatusPage, - UNDER_MAINTENANCE, -} from '../../types'; - -const useStyles = makeStyles({ - denseListIcon: { - marginRight: 0, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - }, -}); - -export const statusPageStatusLabels = { - [OPERATIONAL]: 'Operational', - [UNDER_MAINTENANCE]: 'Under maintenance', - [DEGRADED]: 'Degraded', - [PARTIAL_OUTAGE]: 'Partial outage', - [MAJOR_OUTAGE]: 'Major outage', -} as Record; - -export const StatusPageStatus = ({ - statusPage, -}: { - statusPage: StatusPage; -}) => { - const classes = useStyles(); - - return ( - -
- {statusPage.status === 'OPERATIONAL' ? : } -
-
- ); -}; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx index b94903333a..1aaf209770 100644 --- a/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx +++ b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { ilertApiRef } from '../../api'; import { StatusPage } from '../../types'; @@ -21,19 +20,12 @@ import { StatusPage } from '../../types'; import { Link } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - export const StatusPageURL = ({ statusPage, }: { statusPage: StatusPage | null; }) => { const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); if (!statusPage) { return null; @@ -41,9 +33,5 @@ export const StatusPageURL = ({ const url = ilertApi.getStatusPageURL(statusPage); - return ( - - {url} - - ); + return {url}; }; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageVisibility.tsx b/plugins/ilert/src/components/StatusPage/StatusPageVisibility.tsx deleted file mode 100644 index 7c8f63dd35..0000000000 --- a/plugins/ilert/src/components/StatusPage/StatusPageVisibility.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { StatusError, StatusOK } from '@backstage/core-components'; -import { makeStyles } from '@material-ui/core/styles'; -import Tooltip from '@material-ui/core/Tooltip'; -import React from 'react'; -import { PRIVATE, PUBLIC, StatusPage } from '../../types'; - -const useStyles = makeStyles({ - denseListIcon: { - marginRight: 0, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - }, -}); - -export const statusPageVisibilityLabels = { - [PUBLIC]: 'Public', - [PRIVATE]: 'Private', -} as Record; - -export const StatusPageVisibility = ({ - statusPage, -}: { - statusPage: StatusPage; -}) => { - const classes = useStyles(); - - return ( - -
- {statusPage.visibility === 'PUBLIC' ? : } -
-
- ); -}; diff --git a/plugins/ilert/src/components/StatusPage/index.ts b/plugins/ilert/src/components/StatusPage/index.ts index 93ea9deaba..d805811d90 100644 --- a/plugins/ilert/src/components/StatusPage/index.ts +++ b/plugins/ilert/src/components/StatusPage/index.ts @@ -14,4 +14,3 @@ * limitations under the License. */ export * from './StatusPageActionsMenu'; -export * from './StatusPageStatus'; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx index 50a64de212..5cd785c4cd 100644 --- a/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx +++ b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx @@ -23,7 +23,6 @@ import { StatusPage, UNDER_MAINTENANCE, } from '../../types'; -import { statusPageStatusLabels } from '../StatusPage/StatusPageStatus'; const OperationalChip = withStyles({ root: { @@ -62,6 +61,14 @@ const MajorOutageChip = withStyles({ }, })(Chip); +const statusPageStatusLabels = { + [OPERATIONAL]: 'Operational', + [UNDER_MAINTENANCE]: 'Under maintenance', + [DEGRADED]: 'Degraded', + [PARTIAL_OUTAGE]: 'Partial outage', + [MAJOR_OUTAGE]: 'Major outage', +} as Record; + export const StatusChip = ({ statusPage }: { statusPage: StatusPage }) => { const label = `${statusPageStatusLabels[statusPage.status]}`; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx index 6e4290c7c9..9d709c5e73 100644 --- a/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx @@ -19,7 +19,6 @@ import { ResponseErrorPanel, SupportButton, } from '@backstage/core-components'; -import { AuthenticationError } from '@backstage/errors'; import React from 'react'; import { useStatusPages } from '../../hooks/useStatusPages'; import { MissingAuthorizationHeaderError } from '../Errors'; @@ -32,7 +31,7 @@ export const StatusPagesPage = () => { ] = useStatusPages(true); if (error) { - if (error instanceof AuthenticationError) { + if (error.name === 'AuthenticationError') { return ( diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx index 8b013fdb35..8e965b42de 100644 --- a/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx @@ -52,74 +52,60 @@ export const StatusPagesTable = ({ }) => { const classes = useStyles(); - // const xsColumnStyle = { - // width: '5%', - // maxWidth: '5%', - // }; const smColumnStyle = { width: '10%', maxWidth: '10%', }; - // const mdColumnStyle = { - // width: '15%', - // maxWidth: '15%', - // }; - // const lgColumnStyle = { - // width: '20%', - // maxWidth: '20%', - // }; const xlColumnStyle = { width: '30%', maxWidth: '30%', }; - const idColumn: TableColumn = { + const idColumn: TableColumn = { title: 'ID', field: 'id', highlight: true, cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const nameColumn: TableColumn = { + const nameColumn: TableColumn = { title: 'Name', field: 'name', cellStyle: !compact ? xlColumnStyle : undefined, headerStyle: !compact ? xlColumnStyle : undefined, - render: rowData => {(rowData as StatusPage).name}, + render: rowData => {rowData.name}, }; - const urlColumn: TableColumn = { + const urlColumn: TableColumn = { title: 'URL', field: 'url', cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const visibilityColumn: TableColumn = { + const visibilityColumn: TableColumn = { title: 'Visibility', field: 'visibility', cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const statusColumn: TableColumn = { + const statusColumn: TableColumn = { title: 'Status', field: 'status', cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const actionsColumn: TableColumn = { + const actionsColumn: TableColumn = { title: '', field: '', cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => ( - - ), + render: rowData => , }; - const columns: TableColumn[] = compact + const columns: TableColumn[] = compact ? [nameColumn, statusColumn, urlColumn, actionsColumn] : [ idColumn, @@ -129,26 +115,9 @@ export const StatusPagesTable = ({ visibilityColumn, actionsColumn, ]; - let tableStyle: React.CSSProperties = {}; - if (compact) { - tableStyle = { - width: '100%', - maxWidth: '100%', - minWidth: '0', - height: 'calc(100% - 10px)', - boxShadow: 'none !important', - borderRadius: 'none !important', - }; - } else { - tableStyle = { - width: '100%', - maxWidth: '100%', - }; - } return (
; + export const VisibilityChip = ({ statusPage }: { statusPage: StatusPage }) => { const label = `${statusPageVisibilityLabels[statusPage.visibility]}`; From 8959267df0b15bc207aa0a3bf39f83247b8b53a0 Mon Sep 17 00:00:00 2001 From: Calum Lind Date: Thu, 20 Oct 2022 12:50:22 +0100 Subject: [PATCH 114/221] Fix "new" ribbon showing on all plugins in marketplace The original commit 913145399e added a ribbon to show which plugins were recently added to the marketplace but on the live site all plugins were showing as 'new'. The problem is due to relying on the git author date which is incorrect when using shallow depth clones in the build process. Fixed by adding new key to plugin marketplace metadata to be parsed. Fixes: https://github.com/backstage/backstage/issues/13015 Signed-off-by: Calum Lind --- docs/plugins/add-to-marketplace.md | 1 + microsite/data/plugins/adr.yaml | 1 + microsite/data/plugins/airbrake.yaml | 1 + microsite/data/plugins/allure.yaml | 1 + microsite/data/plugins/api-docs.yaml | 1 + microsite/data/plugins/apollo-explorer.yaml | 1 + microsite/data/plugins/argo-cd.yaml | 1 + .../data/plugins/aws-cloudformation.yaml | 1 + microsite/data/plugins/aws-lambda.yaml | 1 + microsite/data/plugins/aws-proton.yaml | 1 + microsite/data/plugins/azure-pipelines.yaml | 1 + microsite/data/plugins/azure-resources.yaml | 1 + microsite/data/plugins/azure-sites.yaml | 1 + .../backstage-analytics-module-ga.yaml | 1 + .../data/plugins/backstage-kubernetes.yaml | 1 + .../plugins/backstage-search-platform.yaml | 1 + .../plugins/backstage-software-catalog.yaml | 1 + .../plugins/backstage-software-templates.yaml | 1 + .../data/plugins/backstage-techdocs.yaml | 1 + microsite/data/plugins/badges.yaml | 1 + microsite/data/plugins/bazaar.yaml | 1 + microsite/data/plugins/bitrise.yaml | 1 + microsite/data/plugins/bugsnag.yaml | 1 + microsite/data/plugins/buildkite.yaml | 1 + microsite/data/plugins/catalog-graph.yaml | 1 + microsite/data/plugins/circleci.yaml | 1 + microsite/data/plugins/cloud-build.yaml | 1 + .../data/plugins/cloud-carbon-footprint.yaml | 1 + microsite/data/plugins/cloudify.yaml | 1 + microsite/data/plugins/codescene.yaml | 1 + microsite/data/plugins/cortex.yaml | 1 + microsite/data/plugins/cost-insights.yaml | 1 + microsite/data/plugins/datadog.yaml | 1 + microsite/data/plugins/dora-metrics.yaml | 1 + microsite/data/plugins/dynatrace.yaml | 1 + .../data/plugins/firebase-functions.yaml | 1 + microsite/data/plugins/firehydrant.yaml | 1 + microsite/data/plugins/fossa.yaml | 1 + microsite/data/plugins/gcp-projects.yaml | 1 + .../data/plugins/git-release-manager.yaml | 1 + microsite/data/plugins/github-actions.yaml | 1 + microsite/data/plugins/github-insights.yaml | 1 + .../plugins/github-pull-requests-board.yaml | 1 + .../data/plugins/github-pull-requests.yaml | 1 + microsite/data/plugins/gitlab.yaml | 1 + microsite/data/plugins/gitops-cluster.yaml | 1 + microsite/data/plugins/gocd.yaml | 1 + microsite/data/plugins/grafana.yaml | 1 + microsite/data/plugins/graphiql.yaml | 1 + microsite/data/plugins/grpc-playground.yaml | 1 + microsite/data/plugins/harbor.yaml | 1 + microsite/data/plugins/home.yaml | 1 + microsite/data/plugins/humanitec.yaml | 1 + microsite/data/plugins/ilert.yaml | 1 + microsite/data/plugins/jenkins.yaml | 1 + microsite/data/plugins/jira.yaml | 1 + microsite/data/plugins/kafka.yaml | 1 + .../data/plugins/kpt-config-as-data.yaml | 1 + microsite/data/plugins/lighthouse.yaml | 1 + microsite/data/plugins/new-relic.yaml | 1 + .../data/plugins/newrelic-dashboard.yaml | 1 + .../data/plugins/okta-entity-providers.yaml | 1 + microsite/data/plugins/opsgenie.yaml | 1 + microsite/data/plugins/pager-duty.yaml | 1 + microsite/data/plugins/periskop.yaml | 1 + microsite/data/plugins/playlist.yaml | 1 + microsite/data/plugins/prometheus.yaml | 1 + microsite/data/plugins/rollbar.yaml | 1 + microsite/data/plugins/rootly.yaml | 1 + .../plugins/scaffolder-backend-dotnet.yaml | 1 + .../data/plugins/scaffolder-backend-git.yaml | 1 + .../scaffolder-backend-module-rails.yaml | 1 + .../scaffolder-backend-roadie-aws.yaml | 1 + ...caffolder-backend-roadie-http-request.yaml | 1 + .../scaffolder-backend-roadie-utils.yaml | 1 + microsite/data/plugins/score-card.yaml | 1 + microsite/data/plugins/security-insights.yaml | 1 + microsite/data/plugins/sentry.yaml | 1 + microsite/data/plugins/shortcuts.yaml | 1 + microsite/data/plugins/snyk-security.yaml | 1 + microsite/data/plugins/sonarqube.yaml | 1 + microsite/data/plugins/splunk-on-call.yaml | 1 + microsite/data/plugins/stack-overflow.yaml | 1 + microsite/data/plugins/tech-insights.yaml | 1 + microsite/data/plugins/tech-radar.yaml | 1 + microsite/data/plugins/tekton-pipelines.yaml | 1 + microsite/data/plugins/todo.yaml | 1 + microsite/data/plugins/travis-ci.yaml | 1 + microsite/data/plugins/vault.yaml | 1 + microsite/data/plugins/xcmetrics.yaml | 1 + microsite/pages/en/plugins.js | 26 ++++--------------- 91 files changed, 95 insertions(+), 21 deletions(-) diff --git a/docs/plugins/add-to-marketplace.md b/docs/plugins/add-to-marketplace.md index 790512e6aa..e4281d9b3c 100644 --- a/docs/plugins/add-to-marketplace.md +++ b/docs/plugins/add-to-marketplace.md @@ -23,4 +23,5 @@ iconUrl: # Used as the src attribute for your logo. # You can provide an external url or add your logo under static/img and provide a path # relative to static/ e.g. img/my-logo.png npmPackageName: # Your npm package name E.g. '@backstage/plugin-' quotes are required +addedDate: # The date plugin added to marketplace E.g. '2022-10-01' quotes are required ``` diff --git a/microsite/data/plugins/adr.yaml b/microsite/data/plugins/adr.yaml index f180c80e36..fabb26e685 100644 --- a/microsite/data/plugins/adr.yaml +++ b/microsite/data/plugins/adr.yaml @@ -7,3 +7,4 @@ description: Browse your project's ADRs. documentation: https://github.com/backstage/backstage/tree/master/plugins/adr iconUrl: img/adr-logo.png npmPackageName: '@backstage/plugin-adr' +addedDate: '2022-04-13' diff --git a/microsite/data/plugins/airbrake.yaml b/microsite/data/plugins/airbrake.yaml index 29c1f394d7..7f946597ef 100644 --- a/microsite/data/plugins/airbrake.yaml +++ b/microsite/data/plugins/airbrake.yaml @@ -7,3 +7,4 @@ description: Access Airbrake error monitoring and other integrations from within documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png npmPackageName: '@backstage/plugin-airbrake' +addedDate: '2022-01-10' diff --git a/microsite/data/plugins/allure.yaml b/microsite/data/plugins/allure.yaml index defa7158c0..fc21849268 100644 --- a/microsite/data/plugins/allure.yaml +++ b/microsite/data/plugins/allure.yaml @@ -7,3 +7,4 @@ description: View Allure reports for your components in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/allure iconUrl: https://avatars.githubusercontent.com/u/5879127 npmPackageName: '@backstage/plugin-allure' +addedDate: '2021-09-02' diff --git a/microsite/data/plugins/api-docs.yaml b/microsite/data/plugins/api-docs.yaml index 03ec818445..645e32c54b 100644 --- a/microsite/data/plugins/api-docs.yaml +++ b/microsite/data/plugins/api-docs.yaml @@ -7,3 +7,4 @@ description: Components to discover and display API entities as an extension to documentation: https://github.com/backstage/backstage/blob/master/plugins/api-docs/README.md iconUrl: https://raw.githubusercontent.com/vscode-icons/vscode-icons/master/icons/file_type_swagger.svg npmPackageName: '@backstage/plugin-api-docs' +addedDate: '2020-11-19' diff --git a/microsite/data/plugins/apollo-explorer.yaml b/microsite/data/plugins/apollo-explorer.yaml index 068e26f0dc..1a95eaa035 100644 --- a/microsite/data/plugins/apollo-explorer.yaml +++ b/microsite/data/plugins/apollo-explorer.yaml @@ -7,3 +7,4 @@ description: Integrates Apollo Explorer graphs as a tool to browse GraphQL API e documentation: https://github.com/backstage/backstage/blob/master/plugins/apollo-explorer/README.md iconUrl: img/apollo-explorer.png npmPackageName: '@backstage/plugin-apollo-explorer' +addedDate: '2022-07-20' diff --git a/microsite/data/plugins/argo-cd.yaml b/microsite/data/plugins/argo-cd.yaml index 121c833cb4..daac6dabd1 100644 --- a/microsite/data/plugins/argo-cd.yaml +++ b/microsite/data/plugins/argo-cd.yaml @@ -10,3 +10,4 @@ npmPackageName: '@roadiehq/backstage-plugin-argo-cd' tags: - cd - ci +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/aws-cloudformation.yaml b/microsite/data/plugins/aws-cloudformation.yaml index 7f0feee143..6ee36e82b0 100644 --- a/microsite/data/plugins/aws-cloudformation.yaml +++ b/microsite/data/plugins/aws-cloudformation.yaml @@ -7,3 +7,4 @@ description: Load Backstage entities from AWS CloudFormation stacks documentation: https://github.com/purple-technology/backstage-aws-cloudformation-plugin#readme iconUrl: https://raw.githubusercontent.com/purple-technology/backstage-aws-cloudformation-plugin/master/docs/cloudformation-logo.png npmPackageName: 'backstage-aws-cloudformation-plugin' +addedDate: '2021-08-30' diff --git a/microsite/data/plugins/aws-lambda.yaml b/microsite/data/plugins/aws-lambda.yaml index f20cc0d393..80cebd06a8 100644 --- a/microsite/data/plugins/aws-lambda.yaml +++ b/microsite/data/plugins/aws-lambda.yaml @@ -7,3 +7,4 @@ description: View AWS Lambda functions for your components in Backstage. documentation: https://roadie.io/backstage/plugins/aws-lambda/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=aws-lambda iconUrl: https://roadie.io/images/logos/lambda.png npmPackageName: '@roadiehq/backstage-plugin-aws-lambda' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/aws-proton.yaml b/microsite/data/plugins/aws-proton.yaml index 022b553b24..f66b556d42 100644 --- a/microsite/data/plugins/aws-proton.yaml +++ b/microsite/data/plugins/aws-proton.yaml @@ -7,3 +7,4 @@ description: Create and view AWS Proton services for your components in Backstag documentation: https://github.com/awslabs/aws-proton-plugins-for-backstage#readme iconUrl: https://github.com/awslabs/aws-proton-plugins-for-backstage/blob/main/docs/images/proton-logo.png?raw=true npmPackageName: '@aws/aws-proton-plugin-for-backstage' +addedDate: '2022-06-21' diff --git a/microsite/data/plugins/azure-pipelines.yaml b/microsite/data/plugins/azure-pipelines.yaml index c591591478..d4caa90507 100644 --- a/microsite/data/plugins/azure-pipelines.yaml +++ b/microsite/data/plugins/azure-pipelines.yaml @@ -7,3 +7,4 @@ description: Easily view your Azure Pipelines within the Software Catalog documentation: https://github.com/backstage/backstage/blob/master/plugins/azure-devops/README.md iconUrl: img/azure-pipelines.svg npmPackageName: '@backstage/plugin-azure-devops' +addedDate: '2021-12-22' diff --git a/microsite/data/plugins/azure-resources.yaml b/microsite/data/plugins/azure-resources.yaml index 2cc5665395..a6cb389b0a 100644 --- a/microsite/data/plugins/azure-resources.yaml +++ b/microsite/data/plugins/azure-resources.yaml @@ -6,3 +6,4 @@ category: Infrastructure description: A plugin showing Azure resource groups and security recommendations in relation to an entity in the catalog. documentation: https://github.com/vippsas/backstage-azure-resource-frontend npmPackageName: '@vippsno/plugin-azure-resources' +addedDate: '2022-09-05' diff --git a/microsite/data/plugins/azure-sites.yaml b/microsite/data/plugins/azure-sites.yaml index 3ea9d0439b..70e586f30e 100644 --- a/microsite/data/plugins/azure-sites.yaml +++ b/microsite/data/plugins/azure-sites.yaml @@ -6,3 +6,4 @@ category: Infrastructure description: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. documentation: https://github.com/backstage/backstage/tree/master/plugins/azure-sites npmPackageName: '@backstage/plugin-azure-sites' +addedDate: '2022-10-18' diff --git a/microsite/data/plugins/backstage-analytics-module-ga.yaml b/microsite/data/plugins/backstage-analytics-module-ga.yaml index 0a03ec20e8..051345d43b 100644 --- a/microsite/data/plugins/backstage-analytics-module-ga.yaml +++ b/microsite/data/plugins/backstage-analytics-module-ga.yaml @@ -7,3 +7,4 @@ description: Track usage of your Backstage instance using Google Analytics. documentation: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga/README.md iconUrl: img/ga-icon.png npmPackageName: '@backstage/plugin-analytics-module-ga' +addedDate: '2021-10-07' diff --git a/microsite/data/plugins/backstage-kubernetes.yaml b/microsite/data/plugins/backstage-kubernetes.yaml index c821d034c5..a070ea99b0 100644 --- a/microsite/data/plugins/backstage-kubernetes.yaml +++ b/microsite/data/plugins/backstage-kubernetes.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/kubernetes/overview iconUrl: img/backstage-k8s.svg npmPackageName: '@backstage/plugin-kubernetes' order: 4 +addedDate: '2021-03-29' diff --git a/microsite/data/plugins/backstage-search-platform.yaml b/microsite/data/plugins/backstage-search-platform.yaml index 18bbbd1fd1..5a313ae9da 100644 --- a/microsite/data/plugins/backstage-search-platform.yaml +++ b/microsite/data/plugins/backstage-search-platform.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/software-catalog/software-cata iconUrl: img/backstage-search-platform.svg npmPackageName: '@backstage/plugin-search' order: 5 +addedDate: '2022-09-07' diff --git a/microsite/data/plugins/backstage-software-catalog.yaml b/microsite/data/plugins/backstage-software-catalog.yaml index ae16550297..e633e17e8e 100644 --- a/microsite/data/plugins/backstage-software-catalog.yaml +++ b/microsite/data/plugins/backstage-software-catalog.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/software-catalog/software-cata iconUrl: img/backstage-software-catalog.svg npmPackageName: '@backstage/plugin-catalog' order: 1 +addedDate: '2021-06-17' diff --git a/microsite/data/plugins/backstage-software-templates.yaml b/microsite/data/plugins/backstage-software-templates.yaml index 4d478f9435..f111ee30ce 100644 --- a/microsite/data/plugins/backstage-software-templates.yaml +++ b/microsite/data/plugins/backstage-software-templates.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/software-templates/software-te iconUrl: img/backstage-software-templates.svg npmPackageName: '@backstage/plugin-scaffolder' order: 2 +addedDate: '2021-03-29' diff --git a/microsite/data/plugins/backstage-techdocs.yaml b/microsite/data/plugins/backstage-techdocs.yaml index cd2ddefed4..8d89ac5060 100644 --- a/microsite/data/plugins/backstage-techdocs.yaml +++ b/microsite/data/plugins/backstage-techdocs.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/techdocs/techdocs-overview iconUrl: img/backstage-techdocs.svg npmPackageName: '@backstage/plugin-techdocs' order: 3 +addedDate: '2021-03-29' diff --git a/microsite/data/plugins/badges.yaml b/microsite/data/plugins/badges.yaml index 646a519b25..a5f397843d 100644 --- a/microsite/data/plugins/badges.yaml +++ b/microsite/data/plugins/badges.yaml @@ -7,3 +7,4 @@ description: The badges plugin offers a set of badges that can be used outside o documentation: https://github.com/backstage/backstage/blob/master/plugins/badges/README.md iconUrl: img/badges.svg npmPackageName: '@backstage/plugin-badges' +addedDate: '2021-09-29' diff --git a/microsite/data/plugins/bazaar.yaml b/microsite/data/plugins/bazaar.yaml index 6e27692d78..c408229be2 100644 --- a/microsite/data/plugins/bazaar.yaml +++ b/microsite/data/plugins/bazaar.yaml @@ -6,3 +6,4 @@ description: A marketplace where engineers can propose projects suitable for inn documentation: https://github.com/backstage/backstage/blob/master/plugins/bazaar/README.md iconUrl: img/bazaar.svg npmPackageName: '@backstage/plugin-bazaar' +addedDate: '2022-01-11' diff --git a/microsite/data/plugins/bitrise.yaml b/microsite/data/plugins/bitrise.yaml index 2a362a9d0f..5e5b74f3ad 100644 --- a/microsite/data/plugins/bitrise.yaml +++ b/microsite/data/plugins/bitrise.yaml @@ -7,3 +7,4 @@ description: View Bitrise builds and download the build artifacts within Backsta documentation: https://github.com/backstage/backstage/blob/master/plugins/bitrise/README.md iconUrl: https://avatars.githubusercontent.com/u/7174390?s=400&v=4 npmPackageName: '@backstage/plugin-bitrise' +addedDate: '2021-03-01' diff --git a/microsite/data/plugins/bugsnag.yaml b/microsite/data/plugins/bugsnag.yaml index 8f36153382..dc593ce5e8 100644 --- a/microsite/data/plugins/bugsnag.yaml +++ b/microsite/data/plugins/bugsnag.yaml @@ -7,3 +7,4 @@ description: View and monitor Bugsnag errors. documentation: https://roadie.io/backstage/plugins/bugsnag/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=bugsnag iconUrl: https://roadie.io/images/logos/bugsnag.png npmPackageName: '@roadiehq/backstage-plugin-bugsnag' +addedDate: '2021-09-24' diff --git a/microsite/data/plugins/buildkite.yaml b/microsite/data/plugins/buildkite.yaml index 4e9b1594d3..5713b6b9e4 100644 --- a/microsite/data/plugins/buildkite.yaml +++ b/microsite/data/plugins/buildkite.yaml @@ -10,3 +10,4 @@ npmPackageName: '@roadiehq/backstage-plugin-buildkite' tags: - ci - cd +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/catalog-graph.yaml b/microsite/data/plugins/catalog-graph.yaml index 336954b5dc..6fd7214c4c 100644 --- a/microsite/data/plugins/catalog-graph.yaml +++ b/microsite/data/plugins/catalog-graph.yaml @@ -7,3 +7,4 @@ description: Extend the Backstage Software Catalog with a graph that shows all e documentation: https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md iconUrl: img/catalog-graph.svg npmPackageName: '@backstage/plugin-catalog-graph' +addedDate: '2021-09-15' diff --git a/microsite/data/plugins/circleci.yaml b/microsite/data/plugins/circleci.yaml index cffbedca37..64582440ec 100644 --- a/microsite/data/plugins/circleci.yaml +++ b/microsite/data/plugins/circleci.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-circleci' tags: - ci - cd +addedDate: '2021-04-28' diff --git a/microsite/data/plugins/cloud-build.yaml b/microsite/data/plugins/cloud-build.yaml index 253a935392..67ef13011b 100644 --- a/microsite/data/plugins/cloud-build.yaml +++ b/microsite/data/plugins/cloud-build.yaml @@ -11,3 +11,4 @@ tags: - ci - cd - test +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/cloud-carbon-footprint.yaml b/microsite/data/plugins/cloud-carbon-footprint.yaml index 5a50d02fe3..2a213644ad 100644 --- a/microsite/data/plugins/cloud-carbon-footprint.yaml +++ b/microsite/data/plugins/cloud-carbon-footprint.yaml @@ -13,3 +13,4 @@ tags: - climate - carbon-emissions - carbon-footprint +addedDate: '2022-05-03' diff --git a/microsite/data/plugins/cloudify.yaml b/microsite/data/plugins/cloudify.yaml index e5bcbe1545..93b7a26423 100644 --- a/microsite/data/plugins/cloudify.yaml +++ b/microsite/data/plugins/cloudify.yaml @@ -7,3 +7,4 @@ description: Cloudify provides a remote execution and environment management bac documentation: https://github.com/cloudify-cosmo/backstage-cloudify-plugin#readme iconUrl: https://avatars.githubusercontent.com/u/6260555?s=200&v=4 npmPackageName: 'plugin-cloudify' +addedDate: '2022-05-31' diff --git a/microsite/data/plugins/codescene.yaml b/microsite/data/plugins/codescene.yaml index 4a7c224cf1..bca23fc1fa 100644 --- a/microsite/data/plugins/codescene.yaml +++ b/microsite/data/plugins/codescene.yaml @@ -7,3 +7,4 @@ description: CodeScene is a multi-purpose tool bridging code, business and peopl documentation: https://github.com/backstage/backstage/tree/master/plugins/codescene iconUrl: img/codescene_logo.svg npmPackageName: '@backstage/plugin-codescene' +addedDate: '2022-04-12' diff --git a/microsite/data/plugins/cortex.yaml b/microsite/data/plugins/cortex.yaml index 1f1c57adfd..bf480ccfcd 100644 --- a/microsite/data/plugins/cortex.yaml +++ b/microsite/data/plugins/cortex.yaml @@ -11,3 +11,4 @@ tags: - web - monitoring - sre +addedDate: '2021-06-03' diff --git a/microsite/data/plugins/cost-insights.yaml b/microsite/data/plugins/cost-insights.yaml index ec2d162b9d..824769c174 100644 --- a/microsite/data/plugins/cost-insights.yaml +++ b/microsite/data/plugins/cost-insights.yaml @@ -9,3 +9,4 @@ iconUrl: img/cost-insights.png npmPackageName: '@backstage/plugin-cost-insights' tags: - web +addedDate: '2021-04-28' diff --git a/microsite/data/plugins/datadog.yaml b/microsite/data/plugins/datadog.yaml index 266627009c..0a720698f9 100644 --- a/microsite/data/plugins/datadog.yaml +++ b/microsite/data/plugins/datadog.yaml @@ -7,3 +7,4 @@ description: Embed Datadog graphs and dashboards in Backstage. documentation: https://roadie.io/backstage/plugins/datadog/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=datadog iconUrl: https://roadie.io/images/logos/datadog-white-background.png npmPackageName: '@roadiehq/backstage-plugin-datadog' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/dora-metrics.yaml b/microsite/data/plugins/dora-metrics.yaml index b94499e324..65c2e4fe6e 100644 --- a/microsite/data/plugins/dora-metrics.yaml +++ b/microsite/data/plugins/dora-metrics.yaml @@ -7,3 +7,4 @@ description: Embed dashboards (like DORA metrics) in your team or service pages documentation: https://github.com/OkayHQ/backstage-plugin iconUrl: img/okay.png npmPackageName: '@okayhq/backstage-plugin' +addedDate: '2022-06-16' diff --git a/microsite/data/plugins/dynatrace.yaml b/microsite/data/plugins/dynatrace.yaml index f204c4105c..b68d993e4b 100644 --- a/microsite/data/plugins/dynatrace.yaml +++ b/microsite/data/plugins/dynatrace.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-dynatrace' tags: - dynatrace - monitoring +addedDate: '2022-06-23' diff --git a/microsite/data/plugins/firebase-functions.yaml b/microsite/data/plugins/firebase-functions.yaml index 0b98948f88..176cfc1360 100644 --- a/microsite/data/plugins/firebase-functions.yaml +++ b/microsite/data/plugins/firebase-functions.yaml @@ -7,3 +7,4 @@ description: View Firebase Functions details for your service in Backstage. documentation: https://roadie.io/backstage/plugins/firebase-functions/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=firebase-functions iconUrl: https://roadie.io/images/logos/firebase.png npmPackageName: '@roadiehq/backstage-plugin-firebase-functions' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/firehydrant.yaml b/microsite/data/plugins/firehydrant.yaml index e4dd5e7416..b52357e808 100644 --- a/microsite/data/plugins/firehydrant.yaml +++ b/microsite/data/plugins/firehydrant.yaml @@ -7,3 +7,4 @@ description: View service incidents information from FireHydrant, such as active documentation: https://github.com/backstage/backstage/blob/master/plugins/firehydrant/README.md iconUrl: https://raw.githubusercontent.com/backstage/backstage/master/plugins/firehydrant/doc/firehydrant_logo.png npmPackageName: '@backstage/plugin-firehydrant' +addedDate: '2021-08-18' diff --git a/microsite/data/plugins/fossa.yaml b/microsite/data/plugins/fossa.yaml index c49e89c994..e46228a848 100644 --- a/microsite/data/plugins/fossa.yaml +++ b/microsite/data/plugins/fossa.yaml @@ -7,3 +7,4 @@ description: View FOSSA license compliance of your components in Backstage. documentation: https://github.com/backstage/backstage/blob/master/plugins/fossa/README.md iconUrl: https://avatars0.githubusercontent.com/u/9543448?s=400&v=4 npmPackageName: '@backstage/plugin-fossa' +addedDate: '2020-12-10' diff --git a/microsite/data/plugins/gcp-projects.yaml b/microsite/data/plugins/gcp-projects.yaml index 7c4b75d2a4..02e66db27e 100644 --- a/microsite/data/plugins/gcp-projects.yaml +++ b/microsite/data/plugins/gcp-projects.yaml @@ -11,3 +11,4 @@ tags: - cloud - project - resources +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/git-release-manager.yaml b/microsite/data/plugins/git-release-manager.yaml index 9c2372a6cb..0541894e47 100644 --- a/microsite/data/plugins/git-release-manager.yaml +++ b/microsite/data/plugins/git-release-manager.yaml @@ -7,3 +7,4 @@ description: Manage releases without having to juggle git commands. documentation: https://github.com/backstage/backstage/tree/master/plugins/git-release-manager iconUrl: img/git-release-manager-logo.svg npmPackageName: '@backstage/plugin-git-release-manager' +addedDate: '2021-10-04' diff --git a/microsite/data/plugins/github-actions.yaml b/microsite/data/plugins/github-actions.yaml index 8d5a74d9ce..82611c3933 100644 --- a/microsite/data/plugins/github-actions.yaml +++ b/microsite/data/plugins/github-actions.yaml @@ -11,3 +11,4 @@ tags: - ci - cd - github +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/github-insights.yaml b/microsite/data/plugins/github-insights.yaml index 3515065916..87a636f006 100644 --- a/microsite/data/plugins/github-insights.yaml +++ b/microsite/data/plugins/github-insights.yaml @@ -7,3 +7,4 @@ description: View GitHub Insights for your components in Backstage. documentation: https://roadie.io/backstage/plugins/github-insights/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=github-insights iconUrl: https://roadie.io/images/logos/insights.png npmPackageName: '@roadiehq/backstage-plugin-github-insights' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/github-pull-requests-board.yaml b/microsite/data/plugins/github-pull-requests-board.yaml index fa710f5fc8..0c3435b581 100644 --- a/microsite/data/plugins/github-pull-requests-board.yaml +++ b/microsite/data/plugins/github-pull-requests-board.yaml @@ -7,3 +7,4 @@ description: View all open GitHub pull requests owned by your team in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/github-pull-requests-board iconUrl: img/github-pull-requests-board-logo.svg npmPackageName: '@backstage/plugin-github-pull-requests-board' +addedDate: '2022-05-10' diff --git a/microsite/data/plugins/github-pull-requests.yaml b/microsite/data/plugins/github-pull-requests.yaml index 8fc0dd5a6e..57276a634b 100644 --- a/microsite/data/plugins/github-pull-requests.yaml +++ b/microsite/data/plugins/github-pull-requests.yaml @@ -7,3 +7,4 @@ description: View GitHub pull requests for your service in Backstage. documentation: https://roadie.io/backstage/plugins/github-pull-requests/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=github-pull-requests iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-github-pull-requests' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/gitlab.yaml b/microsite/data/plugins/gitlab.yaml index 7a5493c7f4..689e8004fd 100644 --- a/microsite/data/plugins/gitlab.yaml +++ b/microsite/data/plugins/gitlab.yaml @@ -10,3 +10,4 @@ npmPackageName: '@loblaw/backstage-plugin-gitlab' tags: - ci - cd +addedDate: '2021-08-17' diff --git a/microsite/data/plugins/gitops-cluster.yaml b/microsite/data/plugins/gitops-cluster.yaml index ad6e7979aa..1ebe4c40f8 100644 --- a/microsite/data/plugins/gitops-cluster.yaml +++ b/microsite/data/plugins/gitops-cluster.yaml @@ -12,3 +12,4 @@ tags: - gitops - github - eks +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/gocd.yaml b/microsite/data/plugins/gocd.yaml index 7c7190effc..1e5f4c3acb 100644 --- a/microsite/data/plugins/gocd.yaml +++ b/microsite/data/plugins/gocd.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-gocd' tags: - ci - cd +addedDate: '2022-01-15' diff --git a/microsite/data/plugins/grafana.yaml b/microsite/data/plugins/grafana.yaml index ff4e33e3df..c26d10b751 100644 --- a/microsite/data/plugins/grafana.yaml +++ b/microsite/data/plugins/grafana.yaml @@ -11,3 +11,4 @@ tags: - dashboards - monitoring - alerting +addedDate: '2021-10-11' diff --git a/microsite/data/plugins/graphiql.yaml b/microsite/data/plugins/graphiql.yaml index eda91206eb..4cff5ca8ba 100644 --- a/microsite/data/plugins/graphiql.yaml +++ b/microsite/data/plugins/graphiql.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-graphiql' tags: - graphql - graphiql +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/grpc-playground.yaml b/microsite/data/plugins/grpc-playground.yaml index 097759be11..b0f12d4aa2 100644 --- a/microsite/data/plugins/grpc-playground.yaml +++ b/microsite/data/plugins/grpc-playground.yaml @@ -7,3 +7,4 @@ description: Easily view and test your gRPC API with a GUI Client, inspired from documentation: https://github.com/zalopay-oss/backstage-grpc-playground iconUrl: https://raw.githubusercontent.com/zalopay-oss/backstage-grpc-playground/main/images/gprc-logo.png npmPackageName: 'backstage-grpc-playground' +addedDate: '2022-06-08' diff --git a/microsite/data/plugins/harbor.yaml b/microsite/data/plugins/harbor.yaml index f959613d4c..5a142b46fa 100644 --- a/microsite/data/plugins/harbor.yaml +++ b/microsite/data/plugins/harbor.yaml @@ -11,3 +11,4 @@ tags: - goharbor - harbor - docker +addedDate: '2022-06-23' diff --git a/microsite/data/plugins/home.yaml b/microsite/data/plugins/home.yaml index e37b8c5e03..cf67f6b43f 100644 --- a/microsite/data/plugins/home.yaml +++ b/microsite/data/plugins/home.yaml @@ -7,3 +7,4 @@ description: This plugin provides a composable home page, and ability to create documentation: https://github.com/backstage/backstage/blob/master/plugins/home/README.md iconUrl: img/home.png npmPackageName: '@backstage/plugin-home' +addedDate: '2021-08-31' diff --git a/microsite/data/plugins/humanitec.yaml b/microsite/data/plugins/humanitec.yaml index bd5dec9dc6..5b3c459116 100644 --- a/microsite/data/plugins/humanitec.yaml +++ b/microsite/data/plugins/humanitec.yaml @@ -9,3 +9,4 @@ description: | documentation: https://github.com/thefrontside/backstage/tree/main/plugins/humanitec iconUrl: img/humanitec-logo.png npmPackageName: '@frontside/backstage-plugin-humanitec' +addedDate: '2022-06-22' diff --git a/microsite/data/plugins/ilert.yaml b/microsite/data/plugins/ilert.yaml index 29cc4f6aa5..feb54e2d6e 100644 --- a/microsite/data/plugins/ilert.yaml +++ b/microsite/data/plugins/ilert.yaml @@ -13,3 +13,4 @@ tags: - alerting - uptime - on-call +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/jenkins.yaml b/microsite/data/plugins/jenkins.yaml index 7a8f884221..0b1c4d3bf4 100644 --- a/microsite/data/plugins/jenkins.yaml +++ b/microsite/data/plugins/jenkins.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-jenkins' tags: - ci - cd +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/jira.yaml b/microsite/data/plugins/jira.yaml index 3342a2594c..6c803bcd68 100644 --- a/microsite/data/plugins/jira.yaml +++ b/microsite/data/plugins/jira.yaml @@ -7,3 +7,4 @@ description: View Jira summary for your projects in Backstage. documentation: https://roadie.io/backstage/plugins/jira/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=jira iconUrl: https://roadie.io/images/logos/jira.png npmPackageName: '@roadiehq/backstage-plugin-jira' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/kafka.yaml b/microsite/data/plugins/kafka.yaml index 47d354b44f..9546f8e2fc 100644 --- a/microsite/data/plugins/kafka.yaml +++ b/microsite/data/plugins/kafka.yaml @@ -9,3 +9,4 @@ iconUrl: https://kafka.apache.org/images/apache-kafka.png npmPackageName: '@backstage/plugin-kafka' tags: - monitoring +addedDate: '2021-01-21' diff --git a/microsite/data/plugins/kpt-config-as-data.yaml b/microsite/data/plugins/kpt-config-as-data.yaml index 03d5d66947..2ed67262a8 100644 --- a/microsite/data/plugins/kpt-config-as-data.yaml +++ b/microsite/data/plugins/kpt-config-as-data.yaml @@ -7,3 +7,4 @@ description: Configuration GUI over GitOps using kpt, with WYSIWYG editing, revi documentation: https://github.com/GoogleContainerTools/kpt-backstage-plugins/tree/main/plugins/cad iconUrl: https://github.com/GoogleContainerTools/kpt/blob/main/logo/KptLogoSmall.png?raw=true npmPackageName: '@kpt/backstage-plugin-cad' +addedDate: '2022-05-13' diff --git a/microsite/data/plugins/lighthouse.yaml b/microsite/data/plugins/lighthouse.yaml index be70dfd74a..4f302cdb3b 100644 --- a/microsite/data/plugins/lighthouse.yaml +++ b/microsite/data/plugins/lighthouse.yaml @@ -12,3 +12,4 @@ tags: - seo - accessibility - performance +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/new-relic.yaml b/microsite/data/plugins/new-relic.yaml index d728a9d1c7..a68cc90314 100644 --- a/microsite/data/plugins/new-relic.yaml +++ b/microsite/data/plugins/new-relic.yaml @@ -12,3 +12,4 @@ tags: - monitoring - errors - alerting +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/newrelic-dashboard.yaml b/microsite/data/plugins/newrelic-dashboard.yaml index c314d4c458..bbb1c4be7e 100644 --- a/microsite/data/plugins/newrelic-dashboard.yaml +++ b/microsite/data/plugins/newrelic-dashboard.yaml @@ -13,3 +13,4 @@ tags: - errors - alerting - dashboards +addedDate: '2021-12-23' diff --git a/microsite/data/plugins/okta-entity-providers.yaml b/microsite/data/plugins/okta-entity-providers.yaml index 93e4473fde..047992e785 100644 --- a/microsite/data/plugins/okta-entity-providers.yaml +++ b/microsite/data/plugins/okta-entity-providers.yaml @@ -7,3 +7,4 @@ description: Load users and groups from Okta into the Backstage catalog. documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/backend/catalog-backend-module-okta iconUrl: https://roadie.io/images/logos/okta.png npmPackageName: '@roadiehq/catalog-backend-module-okta' +addedDate: '2022-07-26' diff --git a/microsite/data/plugins/opsgenie.yaml b/microsite/data/plugins/opsgenie.yaml index 3db06c04c8..2a7818f7da 100644 --- a/microsite/data/plugins/opsgenie.yaml +++ b/microsite/data/plugins/opsgenie.yaml @@ -11,3 +11,4 @@ tags: - monitoring - errors - alerting +addedDate: '2022-03-13' diff --git a/microsite/data/plugins/pager-duty.yaml b/microsite/data/plugins/pager-duty.yaml index d0006c09ec..a4e6e42d47 100644 --- a/microsite/data/plugins/pager-duty.yaml +++ b/microsite/data/plugins/pager-duty.yaml @@ -11,3 +11,4 @@ tags: - monitoring - errors - alerting +addedDate: '2020-12-22' diff --git a/microsite/data/plugins/periskop.yaml b/microsite/data/plugins/periskop.yaml index cce8d0dde5..6b6693b3ef 100644 --- a/microsite/data/plugins/periskop.yaml +++ b/microsite/data/plugins/periskop.yaml @@ -7,3 +7,4 @@ description: Periskop is a pull-based, language agnostic exception aggregator fo documentation: https://github.com/backstage/backstage/tree/master/plugins/periskop iconUrl: https://raw.githubusercontent.com/periskop-dev/periskop/master/docs/assets/periskop-logo.png npmPackageName: '@backstage/plugin-periskop' +addedDate: '2022-02-25' diff --git a/microsite/data/plugins/playlist.yaml b/microsite/data/plugins/playlist.yaml index 3d69103d69..7d9c13082d 100644 --- a/microsite/data/plugins/playlist.yaml +++ b/microsite/data/plugins/playlist.yaml @@ -7,3 +7,4 @@ description: Create, share, and follow custom collections of entities available documentation: https://github.com/backstage/backstage/tree/master/plugins/playlist iconUrl: img/playlist-logo.png npmPackageName: '@backstage/plugin-playlist' +addedDate: '2022-07-02' diff --git a/microsite/data/plugins/prometheus.yaml b/microsite/data/plugins/prometheus.yaml index 0d1e02dd74..2a2fb3729c 100644 --- a/microsite/data/plugins/prometheus.yaml +++ b/microsite/data/plugins/prometheus.yaml @@ -11,3 +11,4 @@ tags: - monitoring - graphs - alerting +addedDate: '2021-10-06' diff --git a/microsite/data/plugins/rollbar.yaml b/microsite/data/plugins/rollbar.yaml index 05f0d4ef78..7742e6a231 100644 --- a/microsite/data/plugins/rollbar.yaml +++ b/microsite/data/plugins/rollbar.yaml @@ -7,3 +7,4 @@ description: View Rollbar errors for your services in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/rollbar iconUrl: https://rollbar.com/assets/media/rollbar-mark-color.png npmPackageName: '@backstage/plugin-rollbar' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/rootly.yaml b/microsite/data/plugins/rootly.yaml index c1769f637a..4aaf10ee7b 100644 --- a/microsite/data/plugins/rootly.yaml +++ b/microsite/data/plugins/rootly.yaml @@ -7,3 +7,4 @@ description: Import your Backstage entities into Rootly services and view incide documentation: https://github.com/rootlyhq/backstage-plugin/blob/master/README.md iconUrl: https://raw.githubusercontent.com/rootlyhq/backstage-plugin/master/docs/logo.png npmPackageName: '@rootly/backstage-plugin' +addedDate: '2022-06-16' diff --git a/microsite/data/plugins/scaffolder-backend-dotnet.yaml b/microsite/data/plugins/scaffolder-backend-dotnet.yaml index 10c6530c94..2b032621c9 100644 --- a/microsite/data/plugins/scaffolder-backend-dotnet.yaml +++ b/microsite/data/plugins/scaffolder-backend-dotnet.yaml @@ -7,3 +7,4 @@ description: Here you can find all .NET related actions to improve your scaffold documentation: https://github.com/alefcarlos/plusultra-dotnet-backstage-plugins/blob/main/plugins/scaffolder-dotnet-backend/README.md iconUrl: img/scaffolder-backend-dotnet-icon.png npmPackageName: '@plusultra/plugin-scaffolder-dotnet-backend' +addedDate: '2022-01-24' diff --git a/microsite/data/plugins/scaffolder-backend-git.yaml b/microsite/data/plugins/scaffolder-backend-git.yaml index dc23a202ba..4378b36a1e 100644 --- a/microsite/data/plugins/scaffolder-backend-git.yaml +++ b/microsite/data/plugins/scaffolder-backend-git.yaml @@ -7,3 +7,4 @@ description: Easily run git CLI commands from your scaffolder actions documentation: https://github.com/arhill05/backstage-plugin-scaffolder-git-actions#readme iconUrl: https://git-scm.com/images/logos/downloads/Git-Logo-2Color.png npmPackageName: '@mdude2314/backstage-plugin-scaffolder-git-actions' +addedDate: '2022-05-13' diff --git a/microsite/data/plugins/scaffolder-backend-module-rails.yaml b/microsite/data/plugins/scaffolder-backend-module-rails.yaml index 5f5a29cf3c..4110dcbed0 100644 --- a/microsite/data/plugins/scaffolder-backend-module-rails.yaml +++ b/microsite/data/plugins/scaffolder-backend-module-rails.yaml @@ -7,3 +7,4 @@ description: Here you can find all Rails related features to improve your scaffo documentation: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-rails/README.md iconUrl: img/rails-icon.png npmPackageName: '@backstage/plugin-scaffolder-backend-module-rails' +addedDate: '2021-06-24' diff --git a/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml b/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml index 196a9388f9..e8e67dbdad 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml @@ -7,3 +7,4 @@ description: Here you can find some AWS cli actions documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/scaffolder-actions/scaffolder-backend-module-aws/README.md iconUrl: https://upload.wikimedia.org/wikipedia/commons/9/93/Amazon_Web_Services_Logo.svg npmPackageName: '@roadiehq/scaffolder-backend-module-aws' +addedDate: '2022-03-07' diff --git a/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml b/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml index 8e61733ca1..254bebe8e7 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml @@ -7,3 +7,4 @@ description: An action to fire an arbitrary HTTP request documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/scaffolder-actions/scaffolder-backend-module-http-request/README.md iconUrl: img/scaffolder-http-request-logo.svg npmPackageName: '@roadiehq/scaffolder-backend-module-http-request' +addedDate: '2022-03-03' diff --git a/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml b/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml index 3ed3a646dd..ef3814f8ea 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml @@ -7,3 +7,4 @@ description: A collection of utility actions including sleep, zip and file manip documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/scaffolder-actions/scaffolder-backend-module-utils/README.md iconUrl: img/scaffolder-utils-logo.png npmPackageName: '@roadiehq/scaffolder-backend-module-utils' +addedDate: '2022-03-03' diff --git a/microsite/data/plugins/score-card.yaml b/microsite/data/plugins/score-card.yaml index 040d94ed44..c647f8dd15 100644 --- a/microsite/data/plugins/score-card.yaml +++ b/microsite/data/plugins/score-card.yaml @@ -7,3 +7,4 @@ description: Visualization of maturity (and improving it) of services/systems th documentation: https://github.com/Oriflame/backstage-plugins/tree/main/plugins/score-card iconUrl: img/score-card-plugin-logo.png npmPackageName: '@oriflame/backstage-plugin-score-card' +addedDate: '2022-10-06' diff --git a/microsite/data/plugins/security-insights.yaml b/microsite/data/plugins/security-insights.yaml index 1761bdf30a..fd4a001e14 100644 --- a/microsite/data/plugins/security-insights.yaml +++ b/microsite/data/plugins/security-insights.yaml @@ -7,3 +7,4 @@ description: View Security Insights for your components in Backstage. documentation: https://roadie.io/backstage/plugins/security-insights/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=security-insights iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-security-insights' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/sentry.yaml b/microsite/data/plugins/sentry.yaml index 3445d6abb7..7bffdcfd4d 100644 --- a/microsite/data/plugins/sentry.yaml +++ b/microsite/data/plugins/sentry.yaml @@ -7,3 +7,4 @@ description: View Sentry issues in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/sentry iconUrl: https://sentry-brand.storage.googleapis.com/sentry-glyph-white.png npmPackageName: '@backstage/plugin-sentry' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/shortcuts.yaml b/microsite/data/plugins/shortcuts.yaml index 437a0e44b1..d7c63ba666 100644 --- a/microsite/data/plugins/shortcuts.yaml +++ b/microsite/data/plugins/shortcuts.yaml @@ -7,3 +7,4 @@ description: The shortcuts plugin allows a user to have easy access to pages wit documentation: https://github.com/backstage/backstage/blob/master/plugins/shortcuts/README.md iconUrl: img/shortcuts.svg npmPackageName: '@backstage/plugin-shortcuts' +addedDate: '2021-10-06' diff --git a/microsite/data/plugins/snyk-security.yaml b/microsite/data/plugins/snyk-security.yaml index 35f00595d1..4abd068247 100644 --- a/microsite/data/plugins/snyk-security.yaml +++ b/microsite/data/plugins/snyk-security.yaml @@ -7,3 +7,4 @@ description: View Snyk scanned vulnerabilities and license compliance of your co documentation: https://github.com/snyk-tech-services/backstage-plugin-snyk/blob/main/README.md iconUrl: https://storage.googleapis.com/snyk-technical-services.appspot.com/snyk-logo-vertical-black.png npmPackageName: 'backstage-plugin-snyk' +addedDate: '2021-01-22' diff --git a/microsite/data/plugins/sonarqube.yaml b/microsite/data/plugins/sonarqube.yaml index e9e22d18a4..2eab08efff 100644 --- a/microsite/data/plugins/sonarqube.yaml +++ b/microsite/data/plugins/sonarqube.yaml @@ -7,3 +7,4 @@ description: Components to display code quality metrics from SonarCloud and Sona documentation: https://github.com/backstage/backstage/blob/master/plugins/sonarqube/README.md iconUrl: img/sonarqube-icon.svg npmPackageName: '@backstage/plugin-sonarqube' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/splunk-on-call.yaml b/microsite/data/plugins/splunk-on-call.yaml index bb0e25111a..7a29ab3bf9 100644 --- a/microsite/data/plugins/splunk-on-call.yaml +++ b/microsite/data/plugins/splunk-on-call.yaml @@ -12,3 +12,4 @@ tags: - errors - alerting - splunk +addedDate: '2021-11-17' diff --git a/microsite/data/plugins/stack-overflow.yaml b/microsite/data/plugins/stack-overflow.yaml index 0d734446d7..27a3a8cf97 100644 --- a/microsite/data/plugins/stack-overflow.yaml +++ b/microsite/data/plugins/stack-overflow.yaml @@ -7,3 +7,4 @@ description: Provides Stack Overflow specific functionality that can be used in documentation: https://github.com/backstage/backstage/blob/master/plugins/stack-overflow iconUrl: img/stack-overflow-logo.svg npmPackageName: '@backstage/plugin-stack-overflow' +addedDate: '2022-06-14' diff --git a/microsite/data/plugins/tech-insights.yaml b/microsite/data/plugins/tech-insights.yaml index b57ca06ee6..009cbce29e 100644 --- a/microsite/data/plugins/tech-insights.yaml +++ b/microsite/data/plugins/tech-insights.yaml @@ -12,3 +12,4 @@ tags: - reporting - tech health - migrations +addedDate: '2022-03-31' diff --git a/microsite/data/plugins/tech-radar.yaml b/microsite/data/plugins/tech-radar.yaml index f2d5b12663..3187a33d42 100644 --- a/microsite/data/plugins/tech-radar.yaml +++ b/microsite/data/plugins/tech-radar.yaml @@ -7,3 +7,4 @@ description: Visualize the your company's official guidelines of different areas documentation: https://github.com/backstage/backstage/tree/master/plugins/tech-radar iconUrl: https://www.materialui.co/materialIcons/action/track_changes_white_192x192.png npmPackageName: '@backstage/plugin-tech-radar' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/tekton-pipelines.yaml b/microsite/data/plugins/tekton-pipelines.yaml index e53418037d..2da80d34bf 100644 --- a/microsite/data/plugins/tekton-pipelines.yaml +++ b/microsite/data/plugins/tekton-pipelines.yaml @@ -7,3 +7,4 @@ description: View the status of the Tekton PipelineRun resources. documentation: https://github.com/jquad-group/backstage-jquad#readme iconUrl: https://raw.githubusercontent.com/jquad-group/backstage-jquad/main/img/tekton-horizontal-color.png npmPackageName: '@jquad-group/plugin-tekton-pipelines' +addedDate: '2022-08-08' diff --git a/microsite/data/plugins/todo.yaml b/microsite/data/plugins/todo.yaml index 44128c40b2..67a5151a89 100644 --- a/microsite/data/plugins/todo.yaml +++ b/microsite/data/plugins/todo.yaml @@ -7,3 +7,4 @@ description: Browse TODO comments in your project's source code. documentation: https://github.com/backstage/backstage/tree/master/plugins/todo iconUrl: https://backstage.io/img/todo-logo.png npmPackageName: '@backstage/plugin-todo' +addedDate: '2021-03-16' diff --git a/microsite/data/plugins/travis-ci.yaml b/microsite/data/plugins/travis-ci.yaml index 7cb108a5d8..a5cec13d22 100644 --- a/microsite/data/plugins/travis-ci.yaml +++ b/microsite/data/plugins/travis-ci.yaml @@ -7,3 +7,4 @@ description: View Travis CI builds for your service in Backstage. documentation: https://roadie.io/backstage/plugins/travis-ci/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=travis-ci iconUrl: https://roadie.io/images/logos/travis.png npmPackageName: '@roadiehq/backstage-plugin-travis-ci' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/vault.yaml b/microsite/data/plugins/vault.yaml index 3214cf669a..822f5dcc90 100644 --- a/microsite/data/plugins/vault.yaml +++ b/microsite/data/plugins/vault.yaml @@ -9,3 +9,4 @@ iconUrl: img/vault.png npmPackageName: '@backstage/plugin-vault' tags: - vault +addedDate: '2022-06-03' diff --git a/microsite/data/plugins/xcmetrics.yaml b/microsite/data/plugins/xcmetrics.yaml index f2f4f7c013..91db85ebaa 100644 --- a/microsite/data/plugins/xcmetrics.yaml +++ b/microsite/data/plugins/xcmetrics.yaml @@ -7,3 +7,4 @@ description: Discover valuable insights hiding inside Xcode’s build logs. documentation: https://xcmetrics.io/ iconUrl: img/xcmetrics-icon.png npmPackageName: '@backstage/plugin-xcmetrics' +addedDate: '2021-08-06' diff --git a/microsite/pages/en/plugins.js b/microsite/pages/en/plugins.js index 91f4138174..9a0a59bd4b 100644 --- a/microsite/pages/en/plugins.js +++ b/microsite/pages/en/plugins.js @@ -16,24 +16,7 @@ const { const pluginsDirectory = require('path').join(process.cwd(), 'data/plugins'); const pluginMetadata = fs .readdirSync(pluginsDirectory) - .map(file => { - const fileContent = fs.readFileSync(`./data/plugins/${file}`, 'utf8'); - let metadata = yaml.load(fileContent); - - const gitIsoDate = require('child_process') - .execFileSync('git', [ - 'log', - '-1', - '--format="%ai"', - '--reverse', - `./data/plugins/${file}`, - ]) - .toString(); - - metadata.date = new Date(gitIsoDate); - - return metadata; - }) + .map(file => yaml.load(fs.readFileSync(`./data/plugins/${file}`, 'utf8'))) .sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase())); const truncate = text => text.length > 170 ? text.substr(0, 170) + '...' : text; @@ -119,11 +102,12 @@ const Plugins = () => ( authorUrl, documentation, category, - date, + addedDate, }) => (
- {Math.trunc((Date.now() - date) / (1000 * 60 * 60 * 24)) < - newForDays && ( + {Math.trunc( + (Date.now() - new Date(addedDate)) / (1000 * 60 * 60 * 24), + ) < newForDays && (
NEW
From b2adf21bda0afe08fba534bb86e9b19d0b4169be Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Thu, 20 Oct 2022 16:07:13 +0200 Subject: [PATCH 115/221] remove uptime monitors Signed-off-by: Marko Simon --- plugins/ilert/src/api/client.ts | 64 ------- plugins/ilert/src/api/types.ts | 9 +- .../src/components/ILertPage/ILertPage.tsx | 4 - .../UptimeMonitorActionsMenu.tsx | 137 -------------- .../UptimeMonitor/UptimeMonitorLink.tsx | 50 ----- .../src/components/UptimeMonitor/index.ts | 16 -- .../UptimeMonitorsPage/StatusChip.tsx | 73 -------- .../UptimeMonitorCheckType.tsx | 42 ----- .../UptimeMonitorsPage/UptimeMonitorsPage.tsx | 67 ------- .../UptimeMonitorsTable.tsx | 176 ------------------ .../components/UptimeMonitorsPage/index.ts | 17 -- plugins/ilert/src/hooks/useUptimeMonitors.ts | 87 --------- plugins/ilert/src/types.ts | 26 --- 13 files changed, 1 insertion(+), 767 deletions(-) delete mode 100644 plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx delete mode 100644 plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx delete mode 100644 plugins/ilert/src/components/UptimeMonitor/index.ts delete mode 100644 plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx delete mode 100644 plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx delete mode 100644 plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx delete mode 100644 plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx delete mode 100644 plugins/ilert/src/components/UptimeMonitorsPage/index.ts delete mode 100644 plugins/ilert/src/hooks/useUptimeMonitors.ts diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts index bc438786c7..9eb60c8c6a 100644 --- a/plugins/ilert/src/api/client.ts +++ b/plugins/ilert/src/api/client.ts @@ -30,7 +30,6 @@ import { Schedule, Service, StatusPage, - UptimeMonitor, User, } from '../types'; import { @@ -293,63 +292,6 @@ export class ILertClient implements ILertApi { return response; } - async fetchUptimeMonitors(): Promise { - const init = { - headers: JSON_HEADERS, - }; - - const response = await this.fetch('/api/uptime-monitors', init); - - return response; - } - - async fetchUptimeMonitor(id: number): Promise { - const init = { - headers: JSON_HEADERS, - }; - - const response: UptimeMonitor = await this.fetch( - `/api/uptime-monitors/${encodeURIComponent(id)}`, - init, - ); - - return response; - } - - async pauseUptimeMonitor( - uptimeMonitor: UptimeMonitor, - ): Promise { - const init = { - method: 'PUT', - headers: JSON_HEADERS, - body: JSON.stringify({ ...uptimeMonitor, paused: true }), - }; - - const response = await this.fetch( - `/api/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, - init, - ); - - return response; - } - - async resumeUptimeMonitor( - uptimeMonitor: UptimeMonitor, - ): Promise { - const init = { - method: 'PUT', - headers: JSON_HEADERS, - body: JSON.stringify({ ...uptimeMonitor, paused: false }), - }; - - const response = await this.fetch( - `/api/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, - init, - ); - - return response; - } - async fetchAlertSources(): Promise { const init = { headers: JSON_HEADERS, @@ -546,12 +488,6 @@ export class ILertClient implements ILertApi { )}`; } - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string { - return `${this.baseUrl}/uptime/view.jsf?id=${encodeURIComponent( - uptimeMonitor.id, - )}`; - } - getScheduleDetailsURL(schedule: Schedule): string { return `${this.baseUrl}/schedule/view.jsf?id=${encodeURIComponent( schedule.id, diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index 6cbea48c5d..28a7cea7c0 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -25,8 +25,7 @@ import { Schedule, Service, StatusPage, - UptimeMonitor, - User, + User } from '../types'; /** @public */ @@ -82,11 +81,6 @@ export interface ILertApi { createAlert(eventRequest: EventRequest): Promise; triggerAlertAction(alert: Alert, action: AlertAction): Promise; - fetchUptimeMonitors(): Promise; - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - fetchUptimeMonitor(id: number): Promise; - fetchAlertSources(): Promise; fetchAlertSource(idOrIntegrationKey: number | string): Promise; fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; @@ -115,7 +109,6 @@ export interface ILertApi { getAlertDetailsURL(alert: Alert): string; getAlertSourceDetailsURL(alertSource: AlertSource | null): string; getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; getScheduleDetailsURL(schedule: Schedule): string; getServiceDetailsURL(service: Service): string; getStatusPageDetailsURL(statusPage: StatusPage): string; diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx index 51512f6d39..5b95677ce8 100644 --- a/plugins/ilert/src/components/ILertPage/ILertPage.tsx +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -25,7 +25,6 @@ import { AlertsPage } from '../AlertsPage'; import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; import { ServicesPage } from '../ServicesPage'; import { StatusPagesPage } from '../StatusPagePage'; -import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; /** @public */ export const ILertPage = () => { @@ -35,7 +34,6 @@ export const ILertPage = () => { { label: 'Alerts' }, { label: 'Services' }, { label: 'Status pages' }, - { label: 'Uptime Monitors' }, ]; const renderTab = () => { switch (selectedTab) { @@ -47,8 +45,6 @@ export const ILertPage = () => { return ; case 3: return ; - case 4: - return ; default: return null; } diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx deleted file mode 100644 index 2d25f8e50e..0000000000 --- a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; - -import { ilertApiRef } from '../../api'; -import { UptimeMonitor } from '../../types'; - -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { Link } from '@backstage/core-components'; - -export const UptimeMonitorActionsMenu = ({ - uptimeMonitor, - onUptimeMonitorChanged, -}: { - uptimeMonitor: UptimeMonitor; - onUptimeMonitorChanged?: (uptimeMonitor: UptimeMonitor) => void; -}) => { - const ilertApi = useApi(ilertApiRef); - const alertApi = useApi(alertApiRef); - const [anchorEl, setAnchorEl] = React.useState(null); - const callback = onUptimeMonitorChanged || ((_: UptimeMonitor): void => {}); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleCloseMenu = () => { - setAnchorEl(null); - }; - - const handlePause = async (): Promise => { - try { - const newUptimeMonitor = await ilertApi.pauseUptimeMonitor(uptimeMonitor); - handleCloseMenu(); - alertApi.post({ message: 'Uptime monitor paused.' }); - - callback(newUptimeMonitor); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - const handleResume = async (): Promise => { - try { - const newUptimeMonitor = await ilertApi.resumeUptimeMonitor( - uptimeMonitor, - ); - handleCloseMenu(); - alertApi.post({ message: 'Uptime monitor resumed.' }); - - callback(newUptimeMonitor); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - const handleOpenReport = async (): Promise => { - try { - const um = await ilertApi.fetchUptimeMonitor(uptimeMonitor.id); - handleCloseMenu(); - window.open(um.shareUrl, '_blank'); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - return ( - <> - - - - - {uptimeMonitor.paused ? ( - - - Resume - - - ) : null} - - {!uptimeMonitor.paused ? ( - - - Pause - - - ) : null} - - - - - View Report - - - - - - - - View in iLert - - - - - - ); -}; diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx deleted file mode 100644 index c70a8a40f0..0000000000 --- a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { UptimeMonitor } from '../../types'; -import { ilertApiRef } from '../../api'; - -import { useApi } from '@backstage/core-plugin-api'; -import { Link } from '@backstage/core-components'; - -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - -export const UptimeMonitorLink = ({ - uptimeMonitor, -}: { - uptimeMonitor: UptimeMonitor | null; -}) => { - const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); - - if (!uptimeMonitor) { - return null; - } - - return ( - - #{uptimeMonitor.id} - - ); -}; diff --git a/plugins/ilert/src/components/UptimeMonitor/index.ts b/plugins/ilert/src/components/UptimeMonitor/index.ts deleted file mode 100644 index a4aa2fb88c..0000000000 --- a/plugins/ilert/src/components/UptimeMonitor/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from './UptimeMonitorActionsMenu'; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx deleted file mode 100644 index 39f3ef250b..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import Chip from '@material-ui/core/Chip'; -import { withStyles } from '@material-ui/core/styles'; -import { UptimeMonitor } from '../../types'; - -const UpChip = withStyles({ - root: { - backgroundColor: '#4caf50', - color: 'white', - margin: 0, - }, -})(Chip); - -const DownChip = withStyles({ - root: { - backgroundColor: '#d32f2f', - color: 'white', - margin: 0, - }, -})(Chip); - -const UnknownChip = withStyles({ - root: { - backgroundColor: '#92949c', - color: 'white', - margin: 0, - }, -})(Chip); - -export const uptimeMonitorStatusLabels = { - ['up']: 'Up', - ['down']: 'Down', - ['unknown']: 'Unknown', -} as Record; - -export const StatusChip = ({ - uptimeMonitor, -}: { - uptimeMonitor: UptimeMonitor; -}) => { - let label = `${uptimeMonitorStatusLabels[uptimeMonitor.status]}`; - - if (uptimeMonitor.paused) { - label = 'Paused'; - return ; - } - - switch (uptimeMonitor.status) { - case 'up': - return ; - case 'down': - return ; - case 'unknown': - return ; - default: - return ; - } -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx deleted file mode 100644 index a39901836f..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { UptimeMonitor } from '../../types'; -import Typography from '@material-ui/core/Typography'; - -export const UptimeMonitorCheckType = ({ - uptimeMonitor, -}: { - uptimeMonitor: UptimeMonitor; -}) => { - switch (uptimeMonitor.region) { - case 'EU': - return ( - {`${uptimeMonitor.checkType.toUpperCase()} 🇩🇪`} - ); - default: - return ( - {`${uptimeMonitor.checkType.toUpperCase()} 🇺🇸`} - ); - } -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx deleted file mode 100644 index 33612d492b..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { AuthenticationError } from '@backstage/errors'; -import { UptimeMonitorsTable } from './UptimeMonitorsTable'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useUptimeMonitors } from '../../hooks/useUptimeMonitors'; -import { - Content, - ContentHeader, - SupportButton, - ResponseErrorPanel, -} from '@backstage/core-components'; - -export const UptimeMonitorsPage = () => { - const [ - { tableState, uptimeMonitors, isLoading, error }, - { onChangePage, onChangeRowsPerPage, onUptimeMonitorChanged }, - ] = useUptimeMonitors(); - - if (error) { - if (error instanceof AuthenticationError) { - return ( - - - - ); - } - - return ( - - - - ); - } - - return ( - - - - This helps you to bring iLert into your developer portal. - - - - - ); -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx deleted file mode 100644 index 3099018e91..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { TableState } from '../../api'; -import { UptimeMonitor } from '../../types'; -import { StatusChip } from './StatusChip'; -import Typography from '@material-ui/core/Typography'; -import { DateTime as dt, Interval } from 'luxon'; -import humanizeDuration from 'humanize-duration'; -import { EscalationPolicyLink } from '../EscalationPolicy/EscalationPolicyLink'; -import { UptimeMonitorCheckType } from './UptimeMonitorCheckType'; -import { UptimeMonitorActionsMenu } from '../UptimeMonitor/UptimeMonitorActionsMenu'; -import { UptimeMonitorLink } from '../UptimeMonitor/UptimeMonitorLink'; -import { Table, TableColumn } from '@backstage/core-components'; - -const useStyles = makeStyles(theme => ({ - empty: { - padding: theme.spacing(2), - display: 'flex', - justifyContent: 'center', - }, -})); - -export const UptimeMonitorsTable = ({ - uptimeMonitors, - tableState, - isLoading, - onChangePage, - onChangeRowsPerPage, - onUptimeMonitorChanged, -}: { - uptimeMonitors: UptimeMonitor[]; - tableState: TableState; - isLoading: boolean; - onChangePage: (page: number) => void; - onChangeRowsPerPage: (pageSize: number) => void; - onUptimeMonitorChanged: (uptimeMonitor: UptimeMonitor) => void; -}) => { - const classes = useStyles(); - - const smColumnStyle = { - width: '5%', - maxWidth: '5%', - }; - const mdColumnStyle = { - width: '10%', - maxWidth: '10%', - }; - const lgColumnStyle = { - width: '15%', - maxWidth: '15%', - }; - - const columns: TableColumn[] = [ - { - title: 'ID', - field: 'id', - highlight: true, - cellStyle: mdColumnStyle, - headerStyle: mdColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Name', - field: 'name', - render: rowData => ( - {(rowData as UptimeMonitor).name} - ), - }, - { - title: 'Check Type', - field: 'checkType', - cellStyle: lgColumnStyle, - headerStyle: lgColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Last state change', - field: 'lastStatusChange', - type: 'datetime', - cellStyle: mdColumnStyle, - headerStyle: mdColumnStyle, - render: rowData => ( - - {humanizeDuration( - Interval.fromDateTimes( - dt.fromISO((rowData as UptimeMonitor).lastStatusChange), - dt.now(), - ) - .toDuration() - .valueOf(), - { units: ['h', 'm', 's'], largest: 2, round: true }, - )} - - ), - }, - { - title: 'Escalation policy', - field: 'assignedTo', - cellStyle: lgColumnStyle, - headerStyle: lgColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Status', - field: 'status', - cellStyle: smColumnStyle, - headerStyle: smColumnStyle, - render: rowData => ( - - ), - }, - { - title: '', - field: '', - cellStyle: smColumnStyle, - headerStyle: smColumnStyle, - render: rowData => ( - - ), - }, - ]; - - return ( -
- No uptime monitor - - } - page={tableState.page} - onPageChange={onChangePage} - onRowsPerPageChange={onChangeRowsPerPage} - localization={{ header: { actions: undefined } }} - isLoading={isLoading} - columns={columns} - data={uptimeMonitors} - /> - ); -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/index.ts b/plugins/ilert/src/components/UptimeMonitorsPage/index.ts deleted file mode 100644 index 865783b5e6..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from './UptimeMonitorsPage'; -export * from './UptimeMonitorsTable'; diff --git a/plugins/ilert/src/hooks/useUptimeMonitors.ts b/plugins/ilert/src/hooks/useUptimeMonitors.ts deleted file mode 100644 index c71d5f02af..0000000000 --- a/plugins/ilert/src/hooks/useUptimeMonitors.ts +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { ilertApiRef, TableState } from '../api'; -import { AuthenticationError } from '@backstage/errors'; -import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { UptimeMonitor } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; - -export const useUptimeMonitors = () => { - const ilertApi = useApi(ilertApiRef); - const errorApi = useApi(errorApiRef); - - const [tableState, setTableState] = React.useState({ - page: 0, - pageSize: 10, - }); - const [uptimeMonitorsList, setUptimeMonitorsList] = React.useState< - UptimeMonitor[] - >([]); - const [isLoading, setIsLoading] = React.useState(false); - - const { error, retry } = useAsyncRetry(async () => { - try { - setIsLoading(true); - const data = await ilertApi.fetchUptimeMonitors(); - setUptimeMonitorsList(data || []); - setIsLoading(false); - } catch (e) { - setIsLoading(false); - if (!(e instanceof AuthenticationError)) { - errorApi.post(e); - } - throw e; - } - }, [tableState]); - - const onUptimeMonitorChanged = (newUptimeMonitor: UptimeMonitor) => { - setUptimeMonitorsList( - uptimeMonitorsList.map((uptimeMonitor: UptimeMonitor): UptimeMonitor => { - if (newUptimeMonitor.id === uptimeMonitor.id) { - return newUptimeMonitor; - } - - return uptimeMonitor; - }), - ); - }; - - const onChangePage = (page: number) => { - setTableState({ ...tableState, page }); - }; - const onChangeRowsPerPage = (pageSize: number) => { - setTableState({ ...tableState, pageSize }); - }; - - return [ - { - tableState, - uptimeMonitors: uptimeMonitorsList, - error, - isLoading, - }, - { - setTableState, - setUptimeMonitorsList, - retry, - onUptimeMonitorChanged, - onChangePage, - onChangeRowsPerPage, - setIsLoading, - }, - ] as const; -}; diff --git a/plugins/ilert/src/types.ts b/plugins/ilert/src/types.ts index 28b8b7ca8a..e55abb67a5 100644 --- a/plugins/ilert/src/types.ts +++ b/plugins/ilert/src/types.ts @@ -351,32 +351,6 @@ export interface Shift { end: string; } -/** @public */ -export interface UptimeMonitor { - id: number; - name: string; - region: 'EU' | 'US'; - checkType: 'http' | 'tcp' | 'udp' | 'ping'; - checkParams: UptimeMonitorCheckParams; - intervalSec: number; - timeoutMs: number; - createAlertAfterFailedChecks: number; - paused: boolean; - embedUrl: string; - shareUrl: string; - status: string; - lastStatusChange: string; - escalationPolicy: EscalationPolicy; - teams: TeamShort[]; -} - -/** @public */ -export interface UptimeMonitorCheckParams { - host?: string; - port?: number; - url?: string; -} - /** @public */ export interface AlertResponder { group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; From 64a8b7aebebe11d912475980f3acc3e55ec8e068 Mon Sep 17 00:00:00 2001 From: Saikat Sundar Das <92164254+Saikatssd@users.noreply.github.com> Date: Thu, 20 Oct 2022 19:55:30 +0530 Subject: [PATCH 116/221] Update Readme.md Akash190104 Contributor Akash190104 commented yesterday Fixed some typos. @scopsy @p-fernandez Please Review. What kind of change does this PR introduce? (Bug fix, feature, docs update, ...) Docs Update Why was this change needed? (You can also link to an open issue here) To improve the quality of documentation. Other information: Signed-off-by: Saikat Sundar Das <92164254+Saikatssd@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 53f8275b88..c262134f1c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## What is Backstage? -[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. +[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized software catalogue, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end. @@ -53,7 +53,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how - [Adopters](ADOPTERS.md) - Companies already using Backstage - [Blog](https://backstage.io/blog/) - Announcements and updates - [Newsletter](https://mailchi.mp/spotify/backstage-community) - Subscribe to our email newsletter -- [Backstage Community Sessions](https://github.com/backstage/community) - Join monthly meetup and explore Backstage community +- [Backstage Community Sessions](https://github.com/backstage/community) - Join monthly meetups and explore Backstage community - Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star ❤️ ## License From dc1214e8aabc4f47df3ce407f564f8ada168f844 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Thu, 20 Oct 2022 16:28:20 +0200 Subject: [PATCH 117/221] fix last commit, update api-reports Signed-off-by: Marko Simon --- plugins/ilert/api-report.md | 64 ------------------- .../src/components/ILertCard/ILertCard.tsx | 7 +- .../ILertCard/ILertCardActionsHeader.tsx | 16 +---- plugins/ilert/src/hooks/useAlertSource.ts | 37 ++--------- 4 files changed, 7 insertions(+), 117 deletions(-) diff --git a/plugins/ilert/api-report.md b/plugins/ilert/api-report.md index 51e2020dee..6cc0c12a8f 100644 --- a/plugins/ilert/api-report.md +++ b/plugins/ilert/api-report.md @@ -406,10 +406,6 @@ export interface ILertApi { // (undocumented) fetchStatusPages(opts?: GetStatusPagesOpts): Promise; // (undocumented) - fetchUptimeMonitor(id: number): Promise; - // (undocumented) - fetchUptimeMonitors(): Promise; - // (undocumented) fetchUsers(): Promise; // (undocumented) getAlertDetailsURL(alert: Alert): string; @@ -426,8 +422,6 @@ export interface ILertApi { // (undocumented) getStatusPageURL(statusPage: StatusPage): string; // (undocumented) - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; - // (undocumented) getUserInitials(user: User | null): string; // (undocumented) getUserPhoneNumber(user: User | null): string; @@ -439,12 +433,8 @@ export interface ILertApi { end: string, ): Promise; // (undocumented) - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) triggerAlertAction(alert: Alert, action: AlertAction): Promise; } @@ -499,10 +489,6 @@ export class ILertClient implements ILertApi { // (undocumented) fetchStatusPages(opts?: GetStatusPagesOpts): Promise; // (undocumented) - fetchUptimeMonitor(id: number): Promise; - // (undocumented) - fetchUptimeMonitors(): Promise; - // (undocumented) fetchUsers(): Promise; // (undocumented) static fromConfig( @@ -524,8 +510,6 @@ export class ILertClient implements ILertApi { // (undocumented) getStatusPageURL(statusPage: StatusPage): string; // (undocumented) - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; - // (undocumented) getUserInitials(user: User | null): string; // (undocumented) getUserPhoneNumber(user: User | null): string; @@ -537,12 +521,8 @@ export class ILertClient implements ILertApi { end: string, ): Promise; // (undocumented) - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) triggerAlertAction(alert: Alert, action: AlertAction): Promise; } @@ -786,50 +766,6 @@ export interface Uptime { uptimePercentage: UptimePercentage; } -// @public (undocumented) -export interface UptimeMonitor { - // (undocumented) - checkParams: UptimeMonitorCheckParams; - // (undocumented) - checkType: 'http' | 'tcp' | 'udp' | 'ping'; - // (undocumented) - createAlertAfterFailedChecks: number; - // (undocumented) - embedUrl: string; - // (undocumented) - escalationPolicy: EscalationPolicy; - // (undocumented) - id: number; - // (undocumented) - intervalSec: number; - // (undocumented) - lastStatusChange: string; - // (undocumented) - name: string; - // (undocumented) - paused: boolean; - // (undocumented) - region: 'EU' | 'US'; - // (undocumented) - shareUrl: string; - // (undocumented) - status: string; - // (undocumented) - teams: TeamShort[]; - // (undocumented) - timeoutMs: number; -} - -// @public (undocumented) -export interface UptimeMonitorCheckParams { - // (undocumented) - host?: string; - // (undocumented) - port?: number; - // (undocumented) - url?: string; -} - // @public (undocumented) export interface UptimePercentage { // (undocumented) diff --git a/plugins/ilert/src/components/ILertCard/ILertCard.tsx b/plugins/ilert/src/components/ILertCard/ILertCard.tsx index d124751b2b..204c32369c 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCard.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCard.tsx @@ -55,10 +55,8 @@ const useStyles = makeStyles({ export const ILertCard = () => { const classes = useStyles(); const { integrationKey, name } = useILertEntity(); - const [ - { alertSource, uptimeMonitor }, - { setAlertSource, refetchAlertSource }, - ] = useAlertSource(integrationKey); + const [{ alertSource }, { setAlertSource, refetchAlertSource }] = + useAlertSource(integrationKey); const [ { tableState, states, alerts, alertsCount, isLoading, error }, { @@ -99,7 +97,6 @@ export const ILertCard = () => { setAlertSource={setAlertSource} setIsNewAlertModalOpened={setIsNewAlertModalOpened} setIsMaintenanceModalOpened={setIsMaintenanceModalOpened} - uptimeMonitor={uptimeMonitor} /> } action={} diff --git a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx index 49f2c789c4..85118b29b9 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx @@ -23,12 +23,11 @@ import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import BuildIcon from '@material-ui/icons/Build'; import PauseIcon from '@material-ui/icons/Pause'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; -import TimelineIcon from '@material-ui/icons/Timeline'; import WebIcon from '@material-ui/icons/Web'; import Alert from '@material-ui/lab/Alert'; import React from 'react'; import { ilertApiRef } from '../../api'; -import { AlertSource, UptimeMonitor } from '../../types'; +import { AlertSource } from '../../types'; import { HeaderIconLinkRow, @@ -41,13 +40,11 @@ export const ILertCardActionsHeader = ({ setAlertSource, setIsNewAlertModalOpened, setIsMaintenanceModalOpened, - uptimeMonitor, }: { alertSource: AlertSource | null; setAlertSource: (alertSource: AlertSource) => void; setIsNewAlertModalOpened: (isOpen: boolean) => void; setIsMaintenanceModalOpened: (isOpen: boolean) => void; - uptimeMonitor: UptimeMonitor | null; }) => { const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); @@ -140,13 +137,6 @@ export const ILertCardActionsHeader = ({ disabled: !alertSource || isLoading, }; - const uptimeMonitorReportLink: IconLinkVerticalProps = { - label: 'Uptime Report', - href: uptimeMonitor ? uptimeMonitor.shareUrl : '', - icon: , - disabled: !alertSource || !uptimeMonitor || isLoading, - }; - const links: IconLinkVerticalProps[] = [ alertSourceLink, createAlertLink, @@ -155,10 +145,6 @@ export const ILertCardActionsHeader = ({ : enableAlertSourceLink, ]; - if (alertSource && alertSource.integrationType === 'MONITOR') { - links.push(uptimeMonitorReportLink); - } - if (alertSource && alertSource.status !== 'IN_MAINTENANCE') { links.push(maintenanceAlertSourceLink); } diff --git a/plugins/ilert/src/hooks/useAlertSource.ts b/plugins/ilert/src/hooks/useAlertSource.ts index c80b3d59eb..9048ee3031 100644 --- a/plugins/ilert/src/hooks/useAlertSource.ts +++ b/plugins/ilert/src/hooks/useAlertSource.ts @@ -18,7 +18,7 @@ import { AuthenticationError } from '@backstage/errors'; import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; import { ilertApiRef } from '../api'; -import { AlertSource, UptimeMonitor } from '../types'; +import { AlertSource } from '../types'; export const useAlertSource = (integrationKey: string) => { const ilertApi = useApi(ilertApiRef); @@ -28,10 +28,6 @@ export const useAlertSource = (integrationKey: string) => { null, ); const [isAlertSourceLoading, setIsAlertSourceLoading] = React.useState(false); - const [uptimeMonitor, setUptimeMonitor] = - React.useState(null); - const [isUptimeMonitorLoading, setIsUptimeMonitorLoading] = - React.useState(false); const fetchAlertSourceCall = async () => { try { @@ -56,38 +52,13 @@ export const useAlertSource = (integrationKey: string) => { [integrationKey], ); - const fetchUptimeMonitorCall = async () => { - try { - if (!alertSource || alertSource.integrationType !== 'MONITOR') { - return; - } - setIsUptimeMonitorLoading(true); - const data = await ilertApi.fetchUptimeMonitor(alertSource.id); - setUptimeMonitor(data || null); - setIsUptimeMonitorLoading(false); - } catch (e) { - setIsUptimeMonitorLoading(false); - if (!(e instanceof AuthenticationError)) { - errorApi.post(e); - } - throw e; - } - }; - - const { error: uptimeMonitorError, retry: uptimeMonitorRetry } = - useAsyncRetry(fetchUptimeMonitorCall, [alertSource]); - - const retry = () => { - alertSourceRetry(); - uptimeMonitorRetry(); - }; + const retry = () => alertSourceRetry(); return [ { alertSource, - uptimeMonitor, - error: alertSourceError || uptimeMonitorError, - isLoading: isAlertSourceLoading || isUptimeMonitorLoading, + error: alertSourceError, + isLoading: isAlertSourceLoading, }, { retry, From bb4bdd0d156ceac2757bacf69c545056164fb2e0 Mon Sep 17 00:00:00 2001 From: Marko Simon Date: Thu, 20 Oct 2022 16:40:04 +0200 Subject: [PATCH 118/221] fix for prettier Signed-off-by: Marko Simon --- plugins/ilert/src/api/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index 28a7cea7c0..959d7975ac 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -25,7 +25,7 @@ import { Schedule, Service, StatusPage, - User + User, } from '../types'; /** @public */ From 975449c92de1f50bbdd05f3e474f9df70073e7d5 Mon Sep 17 00:00:00 2001 From: Saikat Sundar Das <92164254+Saikatssd@users.noreply.github.com> Date: Thu, 20 Oct 2022 21:40:27 +0530 Subject: [PATCH 119/221] Update README.md Made the changes as said by you. Signed-off-by: Saikat Sundar Das <92164254+Saikatssd@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c262134f1c..9a3c4cdb5a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## What is Backstage? -[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized software catalogue, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. +[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end. From 67fe5bc9a93a7817434a7540fa5bbc2ee71d98de Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Tue, 18 Oct 2022 18:10:13 -0400 Subject: [PATCH 120/221] fix(GithubLocationAnalyzer): support github app auth & authenticated backends Signed-off-by: Phil Kuang --- .changeset/eight-pears-attack.md | 17 +++++++ .changeset/popular-bulldogs-lie.md | 5 ++ .../api-report.md | 2 + .../analyzers/GithubLocationAnalyzer.test.ts | 11 +++- .../src/analyzers/GithubLocationAnalyzer.ts | 51 +++++++++++++------ 5 files changed, 70 insertions(+), 16 deletions(-) create mode 100644 .changeset/eight-pears-attack.md create mode 100644 .changeset/popular-bulldogs-lie.md diff --git a/.changeset/eight-pears-attack.md b/.changeset/eight-pears-attack.md new file mode 100644 index 0000000000..ca18abb2db --- /dev/null +++ b/.changeset/eight-pears-attack.md @@ -0,0 +1,17 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +BREAKING: Support authenticated backends by including a server token for catalog requests. The constructor of `GithubLocationAnalyzer` now requires an instance of `TokenManager` to be supplied: + +```diff +... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, ++ tokenManager: env.tokenManager, + }), + ); +... +``` diff --git a/.changeset/popular-bulldogs-lie.md b/.changeset/popular-bulldogs-lie.md new file mode 100644 index 0000000000..3bc83338c1 --- /dev/null +++ b/.changeset/popular-bulldogs-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Properly derive Github credentials when making requests in `GithubLocationAnalyzer` to support Github App authentication diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 195b150dea..a9587e172b 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -20,6 +20,7 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; +import { TokenManager } from '@backstage/backend-common'; // @public export class GithubDiscoveryProcessor implements CatalogProcessor { @@ -111,6 +112,7 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { export type GithubLocationAnalyzerOptions = { config: Config; discovery: PluginEndpointDiscovery; + tokenManager: TokenManager; }; // @public diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts index 936f8a3576..549ca385e8 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts @@ -32,7 +32,10 @@ jest.mock('@octokit/rest', () => { return { Octokit }; }); -import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { + PluginEndpointDiscovery, + TokenManager, +} from '@backstage/backend-common'; import { GithubLocationAnalyzer } from './GithubLocationAnalyzer'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; @@ -46,6 +49,10 @@ describe('GithubLocationAnalyzer', () => { getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), getExternalBaseUrl: jest.fn(), }; + const mockTokenManager: jest.Mocked = { + authenticate: jest.fn(), + getToken: jest.fn().mockResolvedValue('abc123'), + }; const config = new ConfigReader({ integrations: { github: [ @@ -117,6 +124,7 @@ describe('GithubLocationAnalyzer', () => { const analyzer = new GithubLocationAnalyzer({ discovery: mockDiscoveryApi, config, + tokenManager: mockTokenManager, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', @@ -142,6 +150,7 @@ describe('GithubLocationAnalyzer', () => { const analyzer = new GithubLocationAnalyzer({ discovery: mockDiscoveryApi, config, + tokenManager: mockTokenManager, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts index 32ef8bf946..a740fd7706 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts @@ -15,7 +15,12 @@ */ import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { ScmIntegrations } from '@backstage/integration'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrationRegistry, + ScmIntegrations, +} from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; @@ -23,28 +28,36 @@ import { AnalyzeOptions, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-backend'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { + PluginEndpointDiscovery, + TokenManager, +} from '@backstage/backend-common'; import { Config } from '@backstage/config'; /** @public */ export type GithubLocationAnalyzerOptions = { config: Config; discovery: PluginEndpointDiscovery; + tokenManager: TokenManager; }; /** @public */ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { private readonly catalogClient: CatalogApi; - private readonly config: Config; + private readonly githubCredentialsProvider: GithubCredentialsProvider; + private readonly integrations: ScmIntegrationRegistry; + private readonly tokenManager: TokenManager; constructor(options: GithubLocationAnalyzerOptions) { - this.config = options.config; this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); + this.integrations = ScmIntegrations.fromConfig(options.config); + this.githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(this.integrations); + this.tokenManager = options.tokenManager; } supports(url: string) { - const integrations = ScmIntegrations.fromConfig(this.config); - const integration = integrations.byUrl(url); + const integration = this.integrations.byUrl(url); return integration?.type === 'github'; } @@ -55,15 +68,18 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { const query = `filename:${catalogFile} repo:${owner}/${repo}`; - const integration = ScmIntegrations.fromConfig(this.config).github.byUrl( - url, - ); + const integration = this.integrations.github.byUrl(url); if (!integration) { throw new Error('Make sure you have a GitHub integration configured'); } + const { token: githubToken } = + await this.githubCredentialsProvider.getCredentials({ + url, + }); + const octokitClient = new Octokit({ - auth: integration.config.token, + auth: githubToken, baseUrl: integration.config.apiBaseUrl, }); @@ -82,15 +98,20 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { }); const defaultBranch = repoInformation.data.default_branch; + const { token: serviceToken } = await this.tokenManager.getToken(); + const result = await Promise.all( searchResult.data.items .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) .map(async target => { - const addLocationResult = await this.catalogClient.addLocation({ - type: 'url', - target, - dryRun: true, - }); + const addLocationResult = await this.catalogClient.addLocation( + { + type: 'url', + target, + dryRun: true, + }, + { token: serviceToken }, + ); return addLocationResult.entities.map(e => ({ location: { type: 'url', target }, isRegistered: !!addLocationResult.exists, From 28b39e0e0e33e4ec31f81970a20362fb6cb6a22e Mon Sep 17 00:00:00 2001 From: Frida Jacobsson Date: Wed, 19 Oct 2022 11:00:14 +0200 Subject: [PATCH 121/221] Changed style of BazaarOverviewCard Change-Id: I3e4e62c35015a7f0c39c521e985652a44e4b9047 Signed-off-by: Frida Jacobsson --- .changeset/sixty-singers-push.md | 5 +++++ plugins/bazaar/README.md | 8 ++++---- plugins/bazaar/api-report.md | 2 +- .../BazaarOverviewCard/BazaarOverviewCard.tsx | 10 +++++----- .../src/components/ProjectCard/ProjectCard.tsx | 8 ++------ .../components/ProjectPreview/ProjectPreview.tsx | 16 ++++------------ 6 files changed, 21 insertions(+), 28 deletions(-) create mode 100644 .changeset/sixty-singers-push.md diff --git a/.changeset/sixty-singers-push.md b/.changeset/sixty-singers-push.md new file mode 100644 index 0000000000..b5b50ae077 --- /dev/null +++ b/.changeset/sixty-singers-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-bazaar': minor +--- + +The limit prop of BazaarOverviewCard has been removed entirely, and instead replaced with a new optional boolean prop `fullWidth`. The BazaarOverviewCard now always use full height without fixed width. Also fixed problem with link to Bazaar. diff --git a/plugins/bazaar/README.md b/plugins/bazaar/README.md index 6168b0c4ba..40b2a8afbe 100644 --- a/plugins/bazaar/README.md +++ b/plugins/bazaar/README.md @@ -76,17 +76,17 @@ export const homePage = ( + -+ ++ + -+ -+ ++ ++ + {/* ...other homepage items */} ``` -Specify how many projects you want through the "limit" props. In the example above 4 cards is specified. +The property `fullWidth` is optional and can be used to adjust the card to fit a grid with column width 12. ## How does the Bazaar work? diff --git a/plugins/bazaar/api-report.md b/plugins/bazaar/api-report.md index 2e6ebe6746..c12c95a1f7 100644 --- a/plugins/bazaar/api-report.md +++ b/plugins/bazaar/api-report.md @@ -16,7 +16,7 @@ export const BazaarOverviewCard: ( // @public (undocumented) export type BazaarOverviewCardProps = { order: 'latest' | 'random'; - limit: number; + fullWidth?: boolean; }; // @public (undocumented) diff --git a/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx index 83358c0a05..d64e36baeb 100644 --- a/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx +++ b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx @@ -30,7 +30,7 @@ import { bazaarPlugin } from '../../plugin'; /** @public */ export type BazaarOverviewCardProps = { order: 'latest' | 'random'; - limit: number; + fullWidth?: boolean; }; const getUnlinkedCatalogEntities = ( @@ -48,14 +48,14 @@ const getUnlinkedCatalogEntities = ( /** @public */ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { - const { order, limit } = props; + const { order, fullWidth = false } = props; const bazaarApi = useApi(bazaarApiRef); const catalogApi = useApi(catalogApiRef); const root = useRouteRef(bazaarPlugin.routes.root); const bazaarLink = { title: 'Go to Bazaar', - link: root.toString(), + link: `${root()}`, }; const [unlinkedCatalogEntities, setUnlinkedCatalogEntities] = @@ -66,6 +66,7 @@ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { }); const [bazaarProjects, fetchBazaarProjects] = useAsyncFn(async () => { + const limit = fullWidth ? 6 : 3; const response = await bazaarApi.getProjects(limit, order); return response.data.map(parseBazaarProject) as BazaarProject[]; }); @@ -133,8 +134,7 @@ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { fetchBazaarProjects={fetchBazaarProjects} catalogEntities={unlinkedCatalogEntities || []} useTablePagination={false} - fullHeight={false} - fixedWidth + gridSize={fullWidth ? 2 : 4} /> ); diff --git a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx index dace9b8185..fcae411519 100644 --- a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx +++ b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx @@ -54,7 +54,7 @@ const useStyles = makeStyles({ float: 'right', }, content: { - overflow: 'scroll', + height: '13rem', }, header: { whiteSpace: 'nowrap', @@ -67,7 +67,6 @@ export const ProjectCard = ({ project, fetchBazaarProjects, catalogEntities, - fullHeight, }: Props) => { const classes = useStyles(); const [openCard, setOpenCard] = useState(false); @@ -103,10 +102,7 @@ export const ProjectCard = ({ base: DateTime.now(), })}`} /> - + {Number(membersCount) === Number(1) diff --git a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx index 77c16ef306..7dd82edf94 100644 --- a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx +++ b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx @@ -17,7 +17,7 @@ import React, { ChangeEvent, useState } from 'react'; import { Content } from '@backstage/core-components'; import { ProjectCard } from '../ProjectCard/ProjectCard'; -import { makeStyles, Grid, TablePagination } from '@material-ui/core'; +import { makeStyles, Grid, TablePagination, GridSize } from '@material-ui/core'; import { BazaarProject } from '../../types'; import { Entity } from '@backstage/catalog-model'; @@ -26,8 +26,7 @@ type Props = { fetchBazaarProjects: () => Promise; catalogEntities: Entity[]; useTablePagination?: boolean; - fullHeight?: boolean; - fixedWidth?: boolean; + gridSize?: GridSize; }; const useStyles = makeStyles({ @@ -55,8 +54,7 @@ export const ProjectPreview = ({ fetchBazaarProjects, catalogEntities, useTablePagination = true, - fullHeight = true, - fixedWidth = false, + gridSize = 2, }: Props) => { const classes = useStyles(); const [page, setPage] = useState(1); @@ -86,18 +84,12 @@ export const ProjectPreview = ({ .slice((page - 1) * rows, rows * page) .map((bazaarProject: BazaarProject, i: number) => { return ( - + ); From ebf0236d21e8b37c4059525a4dc46c736a3d2ebf Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 21 Oct 2022 09:42:16 +0200 Subject: [PATCH 122/221] chore(docs): remove reference to deprecated integration Remove reference to deprecated integration `bitbucket` at `index.md`. Signed-off-by: Patrick Jungermann --- docs/integrations/index.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/integrations/index.md b/docs/integrations/index.md index 94f18daa09..b090d6d77e 100644 --- a/docs/integrations/index.md +++ b/docs/integrations/index.md @@ -16,17 +16,13 @@ integrations are used by many Backstage core features and other plugins. Each key under `integrations` is a separate configuration for a single external provider. Providers each have different configuration; here's an example of -configuration to use both GitHub and Bitbucket: +configuration to use GitHub: ```yaml integrations: github: - host: github.com token: ${GITHUB_TOKEN} - bitbucket: - - host: bitbucket.org - username: ${BITBUCKET_USERNAME} - appPassword: ${BITBUCKET_APP_PASSWORD} ``` See documentation for each type of integration for full details on From 74595747078c42c44d4845a639444874254ba532 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 21 Oct 2022 09:42:57 +0200 Subject: [PATCH 123/221] chore(docs): remove docs for deprecated `bitbucket` integration Signed-off-by: Patrick Jungermann --- docs/integrations/bitbucket/discovery.md | 174 ------------------ docs/integrations/bitbucketCloud/discovery.md | 6 - .../integrations/bitbucketServer/discovery.md | 6 - microsite/sidebars.json | 5 - 4 files changed, 191 deletions(-) delete mode 100644 docs/integrations/bitbucket/discovery.md diff --git a/docs/integrations/bitbucket/discovery.md b/docs/integrations/bitbucket/discovery.md deleted file mode 100644 index 15ede69a4a..0000000000 --- a/docs/integrations/bitbucket/discovery.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -id: discovery -title: Bitbucket Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in Bitbucket ---- - -The Bitbucket integration has a special discovery processor for discovering -catalog entities located in Bitbucket. The processor will crawl your Bitbucket -account and register entities matching the configured path. This can be useful -as an alternative to static locations or manually adding things to the catalog. - -## Installation - -You will have to add the processor in the catalog initialization code of your -backend. The provider is not installed by default, therefore you have to add a -dependency to `@backstage/plugin-catalog-backend-module-bitbucket` to your backend -package. - -```bash -# From your Backstage root directory -yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-bitbucket -``` - -And then add the processor to your catalog builder: - -```diff -// In packages/backend/src/plugins/catalog.ts -+import { BitbucketDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-bitbucket'; - - export default async function createPlugin( - env: PluginEnvironment, - ): Promise { - const builder = await CatalogBuilder.create(env); -+ builder.addProcessor( -+ BitbucketDiscoveryProcessor.fromConfig(env.config, { logger: env.logger }) -+ ); -``` - -## Self-hosted Bitbucket Server - -To use the discovery processor with a self-hosted Bitbucket Server, you'll need -a Bitbucket integration [set up](../bitbucketServer/locations.md) with a `BITBUCKET_TOKEN` and a -`BITBUCKET_API_BASE_URL`. Then you can add a location target to the catalog -configuration: - -```yaml -catalog: - locations: - - type: bitbucket-discovery - target: https://bitbucket.mycompany.com/projects/my-project/repos/service-*/catalog-info.yaml -``` - -Note the `bitbucket-discovery` type, as this is not a regular `url` processor. - -The target is composed of four parts: - -- The base instance URL, `https://bitbucket.mycompany.com` in this case -- The project key to scan, which accepts \* wildcard tokens. This can simply be - `*` to scan repositories from all projects. This example only scans for - repositories in the `my-project` project. -- The repository blob to scan, which accepts \* wildcard tokens. This can simply - be `*` to scan all repositories in the project. This example only looks for - repositories prefixed with `service-`. -- The path within each repository to find the catalog YAML file. This will - usually be `/catalog-info.yaml` or a similar variation for catalog files - stored in the root directory of each repository. If omitted, the default value - `catalog-info.yaml` will be used. E.g. given that `my-project`and `service-a` - exists, `https://bitbucket.mycompany.com/projects/my-project/repos/service-*/` - will result in: - `https://bitbucket.mycompany.com/projects/my-project/repos/service-a/catalog-info.yaml`. - -## Bitbucket Cloud - -To use the discovery processor with Bitbucket Cloud, you'll need a Bitbucket -integration [set up](../bitbucketCloud/locations.md) with a `username` and an `appPassword`. Then -you can add a location target to the catalog configuration: - -```yaml -catalog: - locations: - - type: bitbucket-discovery - target: https://bitbucket.org/workspaces/my-workspace -``` - -Note the `bitbucket-discovery` type, as this is not a regular `url` processor. - -The target is composed of the following parts: - -- The base URL for Bitbucket, `https://bitbucket.org` -- The workspace name to scan (following the `workspaces/` path part), which must - match a workspace accessible with the username of your integration. -- (Optional) The project key to scan (following the `projects/` path part), - which accepts \* wildcard tokens. If omitted, repositories from all projects - in the workspace are included. -- (Optional) The repository blob to scan (following the `repos/` path part), - which accepts \* wildcard tokens. If omitted, all repositories in the - workspace are included. -- (Optional) The `catalogPath` query argument to specify the location within - each repository to find the catalog YAML file. This will usually be - `/catalog-info.yaml` or a similar variation for catalog files stored in the - root directory of each repository. If omitted, the default value - `catalog-info.yaml` will be used. -- (Optional) The `q` query argument to be passed through to Bitbucket for - filtering results via the API. This is the most flexible option and will - reduce the amount of API calls if you have a large workspace. - [See here for the specification](https://developer.atlassian.com/bitbucket/api/2/reference/meta/filtering) - for the query argument (will be passed as the `q` query parameter). -- (Optional) The `search=true` query argument to activate the mode utilizing code search. - - Is mutually exclusive to the `q` query argument. - - Allows providing values at `catalogPath` for finding catalog files as allowed by the `path` filter/modifier - [at Bitbucket Cloud's code search](https://confluence.atlassian.com/bitbucket/code-search-in-bitbucket-873876782.html#Search-Pathmodifier). - - `catalogPath=/catalog-info.yaml` - - `catalogPath=catalog-info.yaml` (anywhere in the repository) - - `catalogPath=/path/catalog-info.yaml` - - `catalogPath=path/catalog-info.yaml` - - `catalogPath=/path/*/catalog-info.yaml` - - `catalogPath=path/*/catalog-info.yaml` - - Supports multiple catalog files per repository depending on the `catalogPath` value. - - Registers `Location` entities for existing files only vs all matching repositories. - -Examples: - -- `https://bitbucket.org/workspaces/my-workspace/projects/my-project` will find - all repositories in the `my-project` project in the `my-workspace` workspace. -- `https://bitbucket.org/workspaces/my-workspace/repos/service-*` will find all - repositories starting with `service-` in the `my-workspace` workspace. -- `https://bitbucket.org/workspaces/my-workspace/projects/apis-*/repos/service-*` - will find all repositories starting with `service-`, in all projects starting - with `apis-` in the `my-workspace` workspace. -- `https://bitbucket.org/workspaces/my-workspace?q=project.key ~ "my-project"` - will find all repositories in a project containing `my-project` in its key. -- `https://bitbucket.org/workspaces/my-workspace?catalogPath=my/nested/path/catalog.yaml` - will find all repositories in the `my-workspace` workspace and use the catalog - file at `my/nested/path/catalog.yaml`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=/catalog.yaml` - will find all `catalog.yaml` files located in the root of repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=catalog.yaml` - will find all `catalog.yaml` files located anywhere within repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=/my/nested/path/catalog.yaml` - will find all `catalog.yaml` files located within the directory `/my/nested/path/` within - repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=my/nested/path/catalog.yaml` - will find all `catalog.yaml` files located within the directory `my/nested/path/` located anywhere within - repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=/my/*/path/catalog.yaml` - will find all `catalog.yaml` files located within a directory `path/` located within any (recursive) directory - within the directory `my/` in the root of repositories in the workspace `my-workspace` - (`/my/nested/path/catalog.yaml`, `/my/very/nested/path/catalog.yaml`, ...). -- `https://bitbucket.org/workspaces/my-workspace/projects/apis-*/repos/service-*?search=true&catalogPath=catalog.yaml` - will find all `catalog.yaml` files located anywhere within repositories starting with `service-` - in projects starting with `api-` in the workspace `my-workspace`. - -## Custom repository processing - -The Bitbucket Discovery Processor will by default emit a location for each -matching repository for further processing by other processors. However, it is -possible to override this functionality and take full control of how each -matching repository is processed. - -`BitbucketDiscoveryProcessor.fromConfig` takes an optional parameter -`options.parser` where you can set your own parser to be used for each matched -repository. - -```typescript -const processor = BitbucketDiscoveryProcessor.fromConfig(env.config, { - parser: async function* customRepositoryParser({ client, repository }) { - // Custom logic for interpreting the matching repository. - // See defaultRepositoryParser for an example - }, - logger: env.logger, -}); -``` diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index c5a17bb2c9..9b9bc4ecde 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -102,9 +102,3 @@ catalog: - **`workspace`**: Name of your organization account/workspace. If you want to add multiple workspaces, you need to add one provider config each. - -## Alternative - -_Deprecated!_ Please raise issues for use cases not covered by the entity provider. - -[You can use the `BitbucketDiscoveryProcessor`.](../bitbucket/discovery.md#bitbucket-cloud) diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index c4288b33ec..db1cc43ea4 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -120,9 +120,3 @@ const provider = BitbucketServerEntityProvider.fromConfig(env.config, { }, }); ``` - -## Alternative - -_Deprecated!_ Please raise issues for use cases not covered by the entity provider. - -[You can use the `BitbucketDiscoveryProcessor`.](../bitbucket/discovery.md#self-hosted-bitbucket-server) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 0826541071..1f171e1832 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -153,11 +153,6 @@ "integrations/azure/org" ] }, - { - "type": "subcategory", - "label": "Bitbucket", - "ids": ["integrations/bitbucket/discovery"] - }, { "type": "subcategory", "label": "Bitbucket Cloud", From 9b737e5f2ee7974d5ca8ebf80bb9fd41f2bb2e60 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 17 Oct 2022 13:27:17 +0200 Subject: [PATCH 124/221] core-app-api: use new basename router prop Signed-off-by: Patrik Oldsberg --- .changeset/brave-eels-allow.md | 5 + .../src/app/AppManager.compat.test.tsx | 107 ++++++++++++++++ .../src/app/AppManager.stable.test.tsx | 118 ++++++++++++++++++ packages/core-app-api/src/app/AppManager.tsx | 51 ++++++-- packages/core-app-api/src/app/types.ts | 2 +- 5 files changed, 274 insertions(+), 9 deletions(-) create mode 100644 .changeset/brave-eels-allow.md create mode 100644 packages/core-app-api/src/app/AppManager.compat.test.tsx create mode 100644 packages/core-app-api/src/app/AppManager.stable.test.tsx diff --git a/.changeset/brave-eels-allow.md b/.changeset/brave-eels-allow.md new file mode 100644 index 0000000000..8fbe7ba8d2 --- /dev/null +++ b/.changeset/brave-eels-allow.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. diff --git a/packages/core-app-api/src/app/AppManager.compat.test.tsx b/packages/core-app-api/src/app/AppManager.compat.test.tsx new file mode 100644 index 0000000000..bdebd79dc4 --- /dev/null +++ b/packages/core-app-api/src/app/AppManager.compat.test.tsx @@ -0,0 +1,107 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import tlr, { render } from '@testing-library/react'; +import React from 'react'; + +describe.each(['beta', 'stable'])('react-router %s', rrVersion => { + beforeAll(() => { + jest.doMock('react', () => React); + // This has some side effects, so need this to be stable to avoid re-require + jest.doMock('@testing-library/react', () => tlr); + jest.doMock('react-router', () => + rrVersion === 'beta' + ? jest.requireActual('react-router-beta') + : jest.requireActual('react-router-stable'), + ); + jest.doMock('react-router-dom', () => + rrVersion === 'beta' + ? jest.requireActual('react-router-dom-beta') + : jest.requireActual('react-router-dom-stable'), + ); + }); + + afterAll(() => { + jest.resetModules(); + }); + + function requireDeps() { + return { + ...(require('./AppManager') as typeof import('./AppManager')), + ...(require('../routing') as typeof import('../routing')), + ...(require('react-router-dom') as typeof import('react-router-dom')), + ...(require('@backstage/test-utils') as typeof import('@backstage/test-utils')), + }; + } + + describe('AppManager', () => { + it('supports base path', async () => { + const { AppManager, MemoryRouter, Navigate, Route, FlatRoutes } = + requireDeps(); + const app = new AppManager({ + apis: [], + defaultApis: [], + themes: [ + { + id: 'light', + title: 'Light Theme', + variant: 'light', + Provider: ({ children }) => <>{children}, + }, + ], + icons: {} as any, + plugins: [], + components: { + NotFoundErrorPage: () => null, + BootErrorPage: () => null, + Progress: () => null, + Router: ({ children, basename }) => ( + + ), + ErrorBoundaryFallback: () => null, + ThemeProvider: ({ children }) => <>{children}, + }, + configLoader: async () => [ + { + context: 'test', + data: { app: { baseUrl: 'http://localhost/foo' } }, + }, + ], + bindRoutes: () => {}, + }); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const rendered = render( + + + + } /> + bar} /> + + + , + ); + + await expect(rendered.findByText('bar')).resolves.toBeInTheDocument(); + }); + }); +}); diff --git a/packages/core-app-api/src/app/AppManager.stable.test.tsx b/packages/core-app-api/src/app/AppManager.stable.test.tsx new file mode 100644 index 0000000000..a9948a6070 --- /dev/null +++ b/packages/core-app-api/src/app/AppManager.stable.test.tsx @@ -0,0 +1,118 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter, Navigate, Route } from 'react-router-dom'; +import { FlatRoutes } from '../routing'; +import { AppManager } from './AppManager'; +import { AppOptions } from './types'; + +jest.mock('react-router', () => jest.requireActual('react-router-stable')); +jest.mock('react-router-dom', () => + jest.requireActual('react-router-dom-stable'), +); + +const mockAppOptions: AppOptions = { + apis: [], + defaultApis: [], + themes: [ + { + id: 'light', + title: 'Light Theme', + variant: 'light', + Provider: ({ children }) => <>{children}, + }, + ], + icons: {} as any, + plugins: [], + components: { + NotFoundErrorPage: () => null, + BootErrorPage: () => null, + Progress: () => null, + Router: props => , + ErrorBoundaryFallback: () => null, + ThemeProvider: ({ children }) => <>{children}, + }, + configLoader: async () => [], + bindRoutes: () => {}, +}; + +describe('AppManager', () => { + it('supports base path', async () => { + const app = new AppManager({ + ...mockAppOptions, + components: { + ...mockAppOptions.components, + Router: props => , + }, + configLoader: async () => [ + { + context: 'test', + data: { app: { baseUrl: 'http://localhost/foo' } }, + }, + ], + }); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const rendered = render( + + + + } /> + bar} /> + + + , + ); + + await expect(rendered.findByText('bar')).resolves.toBeInTheDocument(); + }); + + it('supports base path with absolute navigation', async () => { + const app = new AppManager({ + ...mockAppOptions, + components: { + ...mockAppOptions.components, + Router: props => , + }, + configLoader: async () => [ + { + context: 'test', + data: { app: { baseUrl: 'http://localhost/foo' } }, + }, + ], + }); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const rendered = render( + + + + } /> + bar} /> + + + , + ); + + await expect(rendered.findByText('bar')).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 89402476e3..0ca09c0a0c 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -99,6 +99,21 @@ const InternalAppContext = createContext<{ * The returned path does not have a trailing slash. */ function getBasePath(configApi: Config) { + if (!isReactRouterBeta()) { + // When using rr v6 stable the base path is handled through the + // basename prop on the router component instead. + return ''; + } + + return readBasePath(configApi); +} + +/** + * Read the configured base path. + * + * The returned path does not have a trailing slash. + */ +function readBasePath(configApi: ConfigApi) { let { pathname } = new URL( configApi.getOptionalString('app.baseUrl') ?? '/', 'http://dummy.dev', // baseUrl can be specified as just a path @@ -361,7 +376,7 @@ export class AppManager implements BackstageApp { const AppRouter = ({ children }: PropsWithChildren<{}>) => { const configApi = useApi(configApiRef); - const basePath = getBasePath(configApi); + const basePath = readBasePath(configApi); const mountPath = `${basePath}/*`; const { routeObjects } = useContext(InternalAppContext); @@ -390,23 +405,43 @@ export class AppManager implements BackstageApp { { signOutTargetUrl: basePath || '/' }, ); + if (isReactRouterBeta()) { + return ( + + + + {children}} /> + + + ); + } + + return ( + + + {children} + + ); + } + + if (isReactRouterBeta()) { return ( - - {children}} /> - + + + {children}} /> + + ); } return ( - + - - {children}} /> - + <>{children} ); diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 39bf9c0554..70022534a1 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -69,7 +69,7 @@ export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; BootErrorPage: ComponentType; Progress: ComponentType<{}>; - Router: ComponentType<{}>; + Router: ComponentType<{ basename?: string }>; ErrorBoundaryFallback: ComponentType; ThemeProvider?: ComponentType<{}>; From 858986f6b69d156b2736d1f02230deb5e1a4c3cc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 17 Oct 2022 13:28:32 +0200 Subject: [PATCH 125/221] core-components: deprecate Link base path workaround Signed-off-by: Patrik Oldsberg --- .changeset/shaggy-colts-watch.md | 5 +++++ .../core-components/src/components/Link/Link.tsx | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 .changeset/shaggy-colts-watch.md diff --git a/.changeset/shaggy-colts-watch.md b/.changeset/shaggy-colts-watch.md new file mode 100644 index 0000000000..67003bd2d3 --- /dev/null +++ b/.changeset/shaggy-colts-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Disable base path workaround in `Link` component when React Router v6 stable is used. diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index 92ac92daad..07bf8fd78f 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -26,6 +26,12 @@ import { LinkProps as RouterLinkProps, } from 'react-router-dom'; import { trimEnd } from 'lodash'; +import { createRoutesFromChildren, Route } from 'react-router-dom'; + +export function isReactRouterBeta(): boolean { + const [obj] = createRoutesFromChildren(} />); + return !obj.index; +} const useStyles = makeStyles( { @@ -79,6 +85,7 @@ const useBasePath = () => { return trimEnd(pathname, '/'); }; +/** @deprecated Remove once we no longer support React Router v6 beta */ export const useResolvedPath = (uri: LinkProps['to']) => { let resolvedPath = String(uri); @@ -125,7 +132,12 @@ export const Link = React.forwardRef( ({ onClick, noTrack, ...props }, ref) => { const classes = useStyles(); const analytics = useAnalytics(); - const to = useResolvedPath(props.to); + + // Adding the base path to URLs breaks react-router v6 stable, so we only + // do it for beta. The react router version won't change at runtime so it is + // fine to ignore the rules of hooks. + // eslint-disable-next-line react-hooks/rules-of-hooks + const to = isReactRouterBeta() ? useResolvedPath(props.to) : props.to; const linkText = getNodeText(props.children) || to; const external = isExternalUri(to); const newWindow = external && !!/^https?:/.exec(to); From 3cd3dc9adfbd7a6be2dd67df51477b1e76faa5e2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 17 Oct 2022 16:48:28 +0200 Subject: [PATCH 126/221] core-{app,plugin}-api: update API Reports Signed-off-by: Patrik Oldsberg --- packages/core-app-api/api-report.md | 4 +++- packages/core-plugin-api/api-report.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 4d1db6fde1..70312a54a5 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -144,7 +144,9 @@ export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; BootErrorPage: ComponentType; Progress: ComponentType<{}>; - Router: ComponentType<{}>; + Router: ComponentType<{ + basename?: string; + }>; ErrorBoundaryFallback: ComponentType; ThemeProvider?: ComponentType<{}>; SignInPage?: ComponentType; diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 0738d7bac5..cebf09e908 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -139,7 +139,9 @@ export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; BootErrorPage: ComponentType; Progress: ComponentType<{}>; - Router: ComponentType<{}>; + Router: ComponentType<{ + basename?: string; + }>; ErrorBoundaryFallback: ComponentType; ThemeProvider?: ComponentType<{}>; SignInPage?: ComponentType; From a228f113d0e19124f115375b9fd999760f142794 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 17 Oct 2022 16:49:29 +0200 Subject: [PATCH 127/221] changesets: added changeset for core-plugin-api basename addition Signed-off-by: Patrik Oldsberg --- .changeset/eleven-pets-sneeze.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/eleven-pets-sneeze.md diff --git a/.changeset/eleven-pets-sneeze.md b/.changeset/eleven-pets-sneeze.md new file mode 100644 index 0000000000..7c0d8147cc --- /dev/null +++ b/.changeset/eleven-pets-sneeze.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': minor +--- + +The app `Router` component now accepts an optional `basename` property. From 3fb4b478639bb2fe521a57e9cfda5917a34489c2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 20 Oct 2022 09:59:45 +0200 Subject: [PATCH 128/221] core-components: drop deprecated link resolution tests Signed-off-by: Patrik Oldsberg --- .../src/components/Link/Link.test.tsx | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx index 52e687d2e4..d65351d840 100644 --- a/packages/core-components/src/components/Link/Link.test.tsx +++ b/packages/core-components/src/components/Link/Link.test.tsx @@ -107,58 +107,6 @@ describe('', () => { }); }); - describe('resolves a sub-path correctly', () => { - it('when it starts with base path', async () => { - const testString = 'This is test string'; - const linkText = 'Navigate!'; - const configApi = new ConfigReader({ - app: { baseUrl: 'http://localhost:3000/example' }, - }); - - const { getByText } = render( - wrapInTestApp( - - {linkText} - - {testString}

} /> -
-
, - ), - ); - - expect(() => getByText(testString)).toThrow(); - fireEvent.click(getByText(linkText)); - await waitFor(() => { - expect(getByText(testString)).toBeInTheDocument(); - }); - }); - - it('when it does not start with base path', async () => { - const testString = 'This is test string'; - const linkText = 'Navigate!'; - const configApi = new ConfigReader({ - app: { baseUrl: 'http://localhost:3000/example' }, - }); - - const { getByText } = render( - wrapInTestApp( - - {linkText} - - {testString}

} /> -
-
, - ), - ); - - expect(() => getByText(testString)).toThrow(); - fireEvent.click(getByText(linkText)); - await waitFor(() => { - expect(getByText(testString)).toBeInTheDocument(); - }); - }); - }); - describe('isExternalUri', () => { it.each([ [true, 'http://'], From 8106afc7fdbd9e4f64b611a6b442a8ce2b206b6d Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 21 Oct 2022 15:09:34 +0200 Subject: [PATCH 129/221] feat: add initialDelay as option Signed-off-by: Leon --- .../src/service/fact/FactRetrieverEngine.ts | 10 ++++++++-- plugins/tech-insights-node/src/facts.ts | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts index a316b984ed..9078a72fba 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts @@ -76,6 +76,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { private readonly scheduler: PluginTaskScheduler, private readonly defaultCadence?: string, private readonly defaultTimeout?: Duration, + private readonly defaultInitialDelay?: Duration, ) {} static async create(options: { @@ -85,6 +86,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { scheduler: PluginTaskScheduler; defaultCadence?: string; defaultTimeout?: Duration; + defaultInitialDelay?: Duration; }) { const { repository, @@ -93,6 +95,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { scheduler, defaultCadence, defaultTimeout, + defaultInitialDelay, } = options; const retrievers = await factRetrieverRegistry.listRetrievers(); @@ -106,6 +109,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { scheduler, defaultCadence, defaultTimeout, + defaultInitialDelay, ); } @@ -115,11 +119,13 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { await Promise.all( registrations.map(async registration => { - const { factRetriever, cadence, lifecycle, timeout } = registration; + const { factRetriever, cadence, lifecycle, timeout, initialDelay } = registration; const cronExpression = cadence || this.defaultCadence || randomDailyCron(); const timeLimit = timeout || this.defaultTimeout || Duration.fromObject({ minutes: 5 }); + const initialDelaySetting = + initialDelay || this.defaultInitialDelay || Duration.fromObject({ seconds: 5 }); try { await this.scheduler.scheduleTask({ id: factRetriever.id, @@ -128,7 +134,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { timeout: timeLimit, // We add a delay in order to prevent errors due to the // fact that the backend is not yet online in a cold-start scenario - initialDelay: Duration.fromObject({ seconds: 5 }), + initialDelay: initialDelaySetting, }); newRegs.push(factRetriever.id); } catch (e) { diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts index 41db302fd5..1eb58b08ce 100644 --- a/plugins/tech-insights-node/src/facts.ts +++ b/plugins/tech-insights-node/src/facts.ts @@ -279,4 +279,11 @@ export type FactRetrieverRegistration = { * If defined this value will be used to determine expired items which will deleted when this fact retriever is run */ lifecycle?: FactLifecycle; + + /** + * A duration to determine the initial delay for the fact retriever. Useful for cold start scenarios when e.g. the + * catalog backend is not yet available. Defaults to 5 seconds. + * + */ + initialDelay?: Duration | HumanDuration; }; From 4fc30a3eb9aa4ef826d4d8084e2107106143edd1 Mon Sep 17 00:00:00 2001 From: Leon van Ginneken Date: Fri, 21 Oct 2022 15:13:19 +0200 Subject: [PATCH 130/221] Update tame-ads-appear.md Signed-off-by: Leon --- .changeset/tame-ads-appear.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/tame-ads-appear.md b/.changeset/tame-ads-appear.md index 3f7a564c71..887cc73252 100644 --- a/.changeset/tame-ads-appear.md +++ b/.changeset/tame-ads-appear.md @@ -1,5 +1,6 @@ --- '@backstage/plugin-tech-insights-backend': patch +'@backstage/plugin-tech-insights-node': patch --- Add a default delay to the fact retrievers to prevent cold-start errors From 4aeb9638cdaab15127b004fe67d7e1937b7413fd Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 21 Oct 2022 15:20:17 +0200 Subject: [PATCH 131/221] feat: add option to helper fn Signed-off-by: Leon --- .../src/service/fact/createFactRetriever.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts index aaba29ecb1..6d00219e45 100644 --- a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts @@ -35,6 +35,7 @@ export type FactRetrieverRegistrationOptions = { factRetriever: FactRetriever; lifecycle?: FactLifecycle; timeout?: Duration | HumanDuration; + initialDelay?: Duration | HumanDuration; }; /** @@ -45,6 +46,7 @@ export type FactRetrieverRegistrationOptions = { * @param factRetriever - Implementation of fact retriever consisting of at least id, version, schema and handler * @param lifecycle - Optional lifecycle definition indicating the cleanup logic of facts when this retriever is run * @param timeout - Optional duration to determine how long the fact retriever should be allowed to run, defaults to 5 minutes + * @param initialDelay - Optional initial delay to determine how long the fact retriever should wait before the initial run, defaults to 5 seconds * * * @remarks @@ -68,11 +70,12 @@ export type FactRetrieverRegistrationOptions = { export function createFactRetrieverRegistration( options: FactRetrieverRegistrationOptions, ): FactRetrieverRegistration { - const { cadence, factRetriever, lifecycle, timeout } = options; + const { cadence, factRetriever, lifecycle, timeout, initialDelay } = options; return { cadence, factRetriever, lifecycle, timeout, + initialDelay, }; } From 6f6ffa5fe0bac5d522a5e3f8f4264d29e87413dc Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 21 Oct 2022 15:33:08 +0200 Subject: [PATCH 132/221] chore: api-report Signed-off-by: Leon --- plugins/tech-insights-backend/api-report.md | 1 + plugins/tech-insights-node/api-report.md | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md index 94ac35ad9f..8951a81abf 100644 --- a/plugins/tech-insights-backend/api-report.md +++ b/plugins/tech-insights-backend/api-report.md @@ -60,6 +60,7 @@ export type FactRetrieverRegistrationOptions = { factRetriever: FactRetriever; lifecycle?: FactLifecycle; timeout?: Duration | HumanDuration; + initialDelay?: Duration | HumanDuration; }; // @public (undocumented) diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md index a57cf3ec4a..4c3df0cc80 100644 --- a/plugins/tech-insights-node/api-report.md +++ b/plugins/tech-insights-node/api-report.md @@ -75,6 +75,7 @@ export type FactRetrieverRegistration = { cadence?: string; timeout?: Duration | HumanDuration; lifecycle?: FactLifecycle; + initialDelay?: Duration | HumanDuration; }; // @public From 5c3ff9c09c5010eee45a28ae5a0d273fd058996a Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 21 Oct 2022 15:37:38 +0200 Subject: [PATCH 133/221] chore: prettier Signed-off-by: Leon --- .../src/service/fact/FactRetrieverEngine.ts | 7 +++++-- plugins/tech-insights-node/src/facts.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts index 9078a72fba..e223604829 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts @@ -119,13 +119,16 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { await Promise.all( registrations.map(async registration => { - const { factRetriever, cadence, lifecycle, timeout, initialDelay } = registration; + const { factRetriever, cadence, lifecycle, timeout, initialDelay } = + registration; const cronExpression = cadence || this.defaultCadence || randomDailyCron(); const timeLimit = timeout || this.defaultTimeout || Duration.fromObject({ minutes: 5 }); const initialDelaySetting = - initialDelay || this.defaultInitialDelay || Duration.fromObject({ seconds: 5 }); + initialDelay || + this.defaultInitialDelay || + Duration.fromObject({ seconds: 5 }); try { await this.scheduler.scheduleTask({ id: factRetriever.id, diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts index 1eb58b08ce..0916189bc7 100644 --- a/plugins/tech-insights-node/src/facts.ts +++ b/plugins/tech-insights-node/src/facts.ts @@ -285,5 +285,5 @@ export type FactRetrieverRegistration = { * catalog backend is not yet available. Defaults to 5 seconds. * */ - initialDelay?: Duration | HumanDuration; + initialDelay?: Duration | HumanDuration; }; From a937ace35fbae3d7da0dedda3a58db7bc4e994c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 21 Oct 2022 16:49:58 +0200 Subject: [PATCH 134/221] Limit templates, to encourage better practices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- app-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app-config.yaml b/app-config.yaml index 977b07612f..69a1207365 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -216,7 +216,6 @@ catalog: - Component - API - Resource - - Template - System - Domain - Location @@ -271,6 +270,8 @@ catalog: # Backstage example templates - type: file target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml + rules: + - allow: [Template] # Backstage end-to-end tests of TechDocs - type: file target: ../../cypress/e2e-fixture.catalog.info.yaml From 2f6f81acbd4fdec878479fabb8f42f0f5e771361 Mon Sep 17 00:00:00 2001 From: Jamie Klassen Date: Fri, 21 Oct 2022 16:41:15 -0400 Subject: [PATCH 135/221] mention connectivity to graph.microsoft.com Recently I was helping somebody with a Backstage installation who was hitting this error, and it turned out they needed a firewall rule to allow this connectivity. Signed-off-by: Jamie Klassen --- docs/auth/microsoft/provider.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index ef79127223..cbd24867b7 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -46,6 +46,12 @@ The Microsoft provider is a structure with three configuration keys: - `clientSecret`: Secret, found on App Registration > Certificates & secrets - `tenantId`: Directory (tenant) ID, found on App Registration > Overview +In order to finish signing a user in from Azure, the Backstage backend must +fetch their information from graph.microsoft.com (as seen in [this source +code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)), +so ensure that your Backstage backend has connectivity to this host. +Otherwise users may see an `Authentication failed, failed to fetch user profile` error when they attempt to log in. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `microsoftAuthApiRef` reference and From 30e43717c7247caa55d998ce0981d9b5c335878f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 21 Oct 2022 17:12:36 +0200 Subject: [PATCH 136/221] move HumanDuration to @backstage/types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/lazy-planes-repair.md | 5 +++ .changeset/sharp-goats-itch.md | 5 +++ .changeset/three-houses-agree.md | 6 ++++ packages/backend-tasks/api-report.md | 26 +++++--------- packages/backend-tasks/src/deprecated.ts | 25 +++++++++++++ packages/backend-tasks/src/index.ts | 1 + packages/backend-tasks/src/tasks/index.ts | 1 - ...adTaskScheduleDefinitionFromConfig.test.ts | 2 +- .../readTaskScheduleDefinitionFromConfig.ts | 4 +-- packages/backend-tasks/src/tasks/types.ts | 16 +-------- packages/types/api-report.md | 12 +++++++ packages/types/package.json | 1 + packages/types/src/index.ts | 1 + packages/types/src/time.test.ts | 36 +++++++++++++++++++ packages/types/src/time.ts | 31 ++++++++++++++++ plugins/tech-insights-backend/api-report.md | 2 +- plugins/tech-insights-backend/package.json | 1 + .../src/service/fact/createFactRetriever.ts | 3 +- plugins/tech-insights-node/api-report.md | 2 +- plugins/tech-insights-node/src/facts.ts | 3 +- yarn.lock | 2 ++ 21 files changed, 144 insertions(+), 41 deletions(-) create mode 100644 .changeset/lazy-planes-repair.md create mode 100644 .changeset/sharp-goats-itch.md create mode 100644 .changeset/three-houses-agree.md create mode 100644 packages/backend-tasks/src/deprecated.ts create mode 100644 packages/types/src/time.test.ts create mode 100644 packages/types/src/time.ts diff --git a/.changeset/lazy-planes-repair.md b/.changeset/lazy-planes-repair.md new file mode 100644 index 0000000000..00ecfb095e --- /dev/null +++ b/.changeset/lazy-planes-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. diff --git a/.changeset/sharp-goats-itch.md b/.changeset/sharp-goats-itch.md new file mode 100644 index 0000000000..601d6a7e9d --- /dev/null +++ b/.changeset/sharp-goats-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/types': patch +--- + +Added the `HumanDuration` type, moved here from `@backstage/backend-tasks`. This type matches the `Duration.fromObject` form of `luxon`. diff --git a/.changeset/three-houses-agree.md b/.changeset/three-houses-agree.md new file mode 100644 index 0000000000..041cd471d3 --- /dev/null +++ b/.changeset/three-houses-agree.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +'@backstage/plugin-tech-insights-node': patch +--- + +Use `HumanDuration` from `@backstage/types` diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index a60f6dba37..8f53d1e5ae 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -7,19 +7,11 @@ import { AbortSignal as AbortSignal_2 } from 'node-abort-controller'; import { Config } from '@backstage/config'; import { DatabaseManager } from '@backstage/backend-common'; import { Duration } from 'luxon'; +import { HumanDuration as HumanDuration_2 } from '@backstage/types'; import { Logger } from 'winston'; -// @public -export type HumanDuration = { - years?: number; - months?: number; - weeks?: number; - days?: number; - hours?: number; - minutes?: number; - seconds?: number; - milliseconds?: number; -}; +// @public @deprecated +export type HumanDuration = HumanDuration_2; // @public export interface PluginTaskScheduler { @@ -59,10 +51,10 @@ export interface TaskScheduleDefinition { cron: string; } | Duration - | HumanDuration; - initialDelay?: Duration | HumanDuration; + | HumanDuration_2; + initialDelay?: Duration | HumanDuration_2; scope?: 'global' | 'local'; - timeout: Duration | HumanDuration; + timeout: Duration | HumanDuration_2; } // @public @@ -72,10 +64,10 @@ export interface TaskScheduleDefinitionConfig { cron: string; } | string - | HumanDuration; - initialDelay?: string | HumanDuration; + | HumanDuration_2; + initialDelay?: string | HumanDuration_2; scope?: 'global' | 'local'; - timeout: string | HumanDuration; + timeout: string | HumanDuration_2; } // @public diff --git a/packages/backend-tasks/src/deprecated.ts b/packages/backend-tasks/src/deprecated.ts new file mode 100644 index 0000000000..4d86cb1700 --- /dev/null +++ b/packages/backend-tasks/src/deprecated.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HumanDuration as TypesHumanDuration } from '@backstage/types'; + +/** + * Human friendly durations object. + * + * @public + * @deprecated Import from `@backstage/types` instead + */ +export type HumanDuration = TypesHumanDuration; diff --git a/packages/backend-tasks/src/index.ts b/packages/backend-tasks/src/index.ts index dd75aca68c..d35917056c 100644 --- a/packages/backend-tasks/src/index.ts +++ b/packages/backend-tasks/src/index.ts @@ -21,3 +21,4 @@ */ export * from './tasks'; +export * from './deprecated'; diff --git a/packages/backend-tasks/src/tasks/index.ts b/packages/backend-tasks/src/tasks/index.ts index 8f30e5c8dd..f2037ac9dd 100644 --- a/packages/backend-tasks/src/tasks/index.ts +++ b/packages/backend-tasks/src/tasks/index.ts @@ -23,5 +23,4 @@ export type { TaskRunner, TaskScheduleDefinition, TaskScheduleDefinitionConfig, - HumanDuration, } from './types'; diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts index 115ef89168..fc2a045614 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts @@ -15,9 +15,9 @@ */ import { ConfigReader } from '@backstage/config'; +import { HumanDuration } from '@backstage/types'; import { Duration } from 'luxon'; import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; -import { HumanDuration } from './types'; describe('readTaskScheduleDefinitionFromConfig', () => { it('all valid values', () => { diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts index 3267d8ef14..1448a328f8 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts @@ -15,8 +15,8 @@ */ import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; -import { HumanDuration, TaskScheduleDefinition } from './types'; +import { HumanDuration, JsonObject } from '@backstage/types'; +import { TaskScheduleDefinition } from './types'; import { Duration } from 'luxon'; const propsOfHumanDuration = [ diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index 92ec045605..dbf2595e1d 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -14,26 +14,12 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; import { CronTime } from 'cron'; import { Duration } from 'luxon'; import { AbortSignal } from 'node-abort-controller'; import { z } from 'zod'; -/** - * Human friendly durations object - * @public - */ -export type HumanDuration = { - years?: number; - months?: number; - weeks?: number; - days?: number; - hours?: number; - minutes?: number; - seconds?: number; - milliseconds?: number; -}; - /** * A function that can be called as a scheduled task. * diff --git a/packages/types/api-report.md b/packages/types/api-report.md index 43e3cfee0e..4b43f5daa6 100644 --- a/packages/types/api-report.md +++ b/packages/types/api-report.md @@ -3,6 +3,18 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +// @public +export type HumanDuration = { + years?: number; + months?: number; + weeks?: number; + days?: number; + hours?: number; + minutes?: number; + seconds?: number; + milliseconds?: number; +}; + // @public export interface JsonArray extends Array {} diff --git a/packages/types/package.json b/packages/types/package.json index b8ebeffce8..3c2bea13c0 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -34,6 +34,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/zen-observable": "^0.8.0", + "luxon": "^3.0.0", "zen-observable": "^0.8.15" }, "files": [ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 1f1b13c349..0b5c8689b3 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -22,3 +22,4 @@ export type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './json'; export type { Observable, Observer, Subscription } from './observable'; +export type { HumanDuration } from './time'; diff --git a/packages/types/src/time.test.ts b/packages/types/src/time.test.ts new file mode 100644 index 0000000000..a1175c9e9b --- /dev/null +++ b/packages/types/src/time.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { HumanDuration } from './time'; +import { Duration } from 'luxon'; + +describe('time', () => { + describe('HumanDuration', () => { + const durations: HumanDuration[] = [ + { years: 1 }, + { months: 1 }, + { weeks: 1 }, + { days: 1 }, + { hours: 1 }, + { minutes: 1 }, + { seconds: 1 }, + { milliseconds: 1 }, + ]; + it.each(durations)('successfully parsed by luxon, %p', d => { + expect(Duration.fromObject(d).toObject()).toEqual(d); + }); + }); +}); diff --git a/packages/types/src/time.ts b/packages/types/src/time.ts new file mode 100644 index 0000000000..7ab8288cea --- /dev/null +++ b/packages/types/src/time.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Human friendly durations object. + * + * @public + */ +export type HumanDuration = { + years?: number; + months?: number; + weeks?: number; + days?: number; + hours?: number; + minutes?: number; + seconds?: number; + milliseconds?: number; +}; diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md index 94ac35ad9f..dea5fe376c 100644 --- a/plugins/tech-insights-backend/api-report.md +++ b/plugins/tech-insights-backend/api-report.md @@ -13,7 +13,7 @@ import { FactLifecycle } from '@backstage/plugin-tech-insights-node'; import { FactRetriever } from '@backstage/plugin-tech-insights-node'; import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node'; import { FactSchema } from '@backstage/plugin-tech-insights-node'; -import { HumanDuration } from '@backstage/backend-tasks'; +import { HumanDuration } from '@backstage/types'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index 402cbc49d5..dd918cfc4f 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -41,6 +41,7 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-tech-insights-common": "workspace:^", "@backstage/plugin-tech-insights-node": "workspace:^", + "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "express": "^4.17.1", diff --git a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts index aaba29ecb1..6200790e6e 100644 --- a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { HumanDuration } from '@backstage/backend-tasks'; + +import { HumanDuration } from '@backstage/types'; import { FactLifecycle, FactRetriever, diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md index a57cf3ec4a..293e2fd76b 100644 --- a/plugins/tech-insights-node/api-report.md +++ b/plugins/tech-insights-node/api-report.md @@ -8,7 +8,7 @@ import { Config } from '@backstage/config'; import { DateTime } from 'luxon'; import { Duration } from 'luxon'; import { DurationLike } from 'luxon'; -import { HumanDuration } from '@backstage/backend-tasks'; +import { HumanDuration } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts index 41db302fd5..2aab9ee73f 100644 --- a/plugins/tech-insights-node/src/facts.ts +++ b/plugins/tech-insights-node/src/facts.ts @@ -15,13 +15,12 @@ */ import { DateTime, Duration, DurationLike } from 'luxon'; import { Config } from '@backstage/config'; -import { JsonValue } from '@backstage/types'; +import { HumanDuration, JsonValue } from '@backstage/types'; import { PluginEndpointDiscovery, TokenManager, } from '@backstage/backend-common'; import { Logger } from 'winston'; -import { HumanDuration } from '@backstage/backend-tasks'; /** * A container for facts. The shape of the fact records needs to correspond to the FactSchema with same `ref` value. diff --git a/yarn.lock b/yarn.lock index 359685cdd0..2a8174d11b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7111,6 +7111,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-tech-insights-common": "workspace:^" "@backstage/plugin-tech-insights-node": "workspace:^" + "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/luxon": ^3.0.0 "@types/semver": ^7.3.8 @@ -7689,6 +7690,7 @@ __metadata: dependencies: "@backstage/cli": "workspace:^" "@types/zen-observable": ^0.8.0 + luxon: ^3.0.0 zen-observable: ^0.8.15 languageName: unknown linkType: soft From cbe11d1e23299eddd74289d660ac932bd33356bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Sat, 22 Oct 2022 15:55:34 +0200 Subject: [PATCH 137/221] just getting rid of vale warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/gorgeous-balloons-sit.md | 9 ++ .github/vale/Vocab/Backstage/accept.txt | 16 +++ STYLE.md | 4 +- docs/api/utility-apis.md | 108 +++++++++--------- .../adr003-avoid-default-exports.md | 2 +- docs/architecture-decisions/index.md | 4 +- docs/auth/gitlab/provider.md | 2 +- docs/conf/defining.md | 2 +- docs/features/kubernetes/configuration.md | 10 +- docs/getting-started/app-custom-theme.md | 16 +-- docs/integrations/gerrit/locations.md | 2 +- docs/plugins/composability.md | 30 ++--- docs/plugins/structure-of-a-plugin.md | 2 +- docs/plugins/url-reader.md | 8 +- docs/releases/v1.2.0.md | 2 +- microsite/README.md | 2 +- .../plugins/plugin-a/docs/index.md | 2 +- .../plugins/plugin-b/docs/index.md | 2 +- packages/theme/src/pageTheme.ts | 4 +- plugins/auth-backend/README.md | 6 +- plugins/cost-insights/README.md | 2 +- plugins/dynatrace/README.md | 2 +- plugins/stack-overflow-backend/README.md | 2 +- plugins/techdocs/src/reader/README.md | 2 +- 24 files changed, 133 insertions(+), 108 deletions(-) create mode 100644 .changeset/gorgeous-balloons-sit.md diff --git a/.changeset/gorgeous-balloons-sit.md b/.changeset/gorgeous-balloons-sit.md new file mode 100644 index 0000000000..a83b12131b --- /dev/null +++ b/.changeset/gorgeous-balloons-sit.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-cost-insights': patch +'@backstage/plugin-dynatrace': patch +'@backstage/plugin-stack-overflow-backend': patch +'@backstage/plugin-techdocs': patch +--- + +Tweak README diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index 603d5fd9af..5ffbc95283 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -1,5 +1,6 @@ abc accessors +ACLs addon addons ADRs @@ -17,6 +18,7 @@ autoscaling Autoscaling autoselect Avro +backend's backported backporting Bigtable @@ -88,6 +90,8 @@ dockerfiles Dockerize dockerode Docusaurus +DOMPurify +don'ts dynatrace Dynatrace ecco @@ -96,6 +100,8 @@ Env elasticsearch esbuild eslint +ESModule +ESModules etag Expedia facto @@ -136,8 +142,10 @@ iLert img incentivised Indal +indexable inlined inlinehilite +integrator's interop JaCoCo JavaScript @@ -168,6 +176,7 @@ lunr Luxon magiclink mailto +maintainer's maintainership makefile md @@ -222,6 +231,7 @@ orgs pagerduty pageview parallelization +parseable Patrik Peloton performant @@ -306,6 +316,10 @@ stringify stringified subcomponent subcomponents +subfolder +subfolders +subheader +subheaders subkey subroutes subtree @@ -326,6 +340,7 @@ templater Templater templaters Templaters +TFRecord theia thumbsup todo @@ -365,6 +380,7 @@ VSCode Wayfair Weaveworks Webpack +widget's winston www WWW diff --git a/STYLE.md b/STYLE.md index c352133c47..87e0644759 100644 --- a/STYLE.md +++ b/STYLE.md @@ -11,8 +11,8 @@ Our TypeScript style is inspired by the [style guidelines](https://github.com/Mi 1. Use PascalCase for type names. 1. Do not use `I` as a prefix for interface names. 1. Use PascalCase for `enum` values. -1. Use camelCase for function names. -1. Use camelCase for property names and local variables. +1. Use `camelCase` for function names. +1. Use `camelCase` for property names and local variables. 1. Do not use `_` as a prefix for private properties. 1. Use whole words in names when possible. 1. Give type parameters names prefixed with `T`, for example `Request
`. diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 9ebcfc0945..017c5b699d 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -13,31 +13,31 @@ both with other plugins and the app itself. Backstage provides two primary methods for plugins to communicate across their boundaries in client-side code. The first one being the -[createPlugin](../reference/core-plugin-api.createplugin.md) API along with the +[`createPlugin`](../reference/core-plugin-api.createplugin.md) API along with the extensions that it can provide, and the second one being Utility APIs. While the -[createPlugin](../reference/core-plugin-api.createplugin.md) API is focused on +[`createPlugin`](../reference/core-plugin-api.createplugin.md) API is focused on the initialization plugins and the app, the Utility APIs provide ways for plugins to communicate during their entire life cycle. ## Consuming APIs -Each Utility API is tied to an [ApiRef](../reference/core-plugin-api.apiref.md) +Each Utility API is tied to an [`ApiRef`](../reference/core-plugin-api.apiref.md) instance, which is a global singleton object without any additional state or functionality, its only purpose is to reference Utility APIs. -[ApiRef](../reference/core-plugin-api.apiref.md)s are created using -[createApiRef](../reference/core-plugin-api.createapiref.md), which is exported -by [@backstage/core-plugin-api](../reference/core-plugin-api.md). There are also +[`ApiRef`](../reference/core-plugin-api.apiref.md)s are created using +[`createApiRef`](../reference/core-plugin-api.createapiref.md), which is exported +by [`@backstage/core-plugin-api`](../reference/core-plugin-api.md). There are also many predefined Utility APIs in -[@backstage/core-plugin-api](../reference/core-plugin-api.md), and they're all +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), and they're all exported with a name of the pattern `*ApiRef`, for example -[errorApiRef](../reference/core-plugin-api.errorapiref.md). +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md). To access one of the Utility APIs inside a React component, use the -[useApi](../reference/core-plugin-api.useapi.md) hook exported by -[@backstage/core-plugin-api](../reference/core-plugin-api.md), or the -[withApis](../reference/core-plugin-api.withapis.md) HOC if you prefer class +[`useApi`](../reference/core-plugin-api.useapi.md) hook exported by +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), or the +[`withApis`](../reference/core-plugin-api.withapis.md) HOC if you prefer class components. For example, the -[ErrorApi](../reference/core-plugin-api.errorapi.md) can be accessed like this: +[`ErrorApi`](../reference/core-plugin-api.errorapi.md) can be accessed like this: ```tsx import React from 'react'; @@ -56,14 +56,14 @@ export const MyComponent = () => { ``` Note that there is no explicit type given for -[ErrorApi](../reference/core-plugin-api.errorapi.md). This is because the -[errorApiRef](../reference/core-plugin-api.errorapiref.md) has the type -embedded, and [useApi](../reference/core-plugin-api.useapi.md) is able to infer +[`ErrorApi`](../reference/core-plugin-api.errorapi.md). This is because the +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) has the type +embedded, and [`useApi`](../reference/core-plugin-api.useapi.md) is able to infer the type. Also note that consuming Utility APIs is not limited to plugins, it can be done from any component inside Backstage, including the ones in -[@backstage/core-plugin-api](../reference/core-plugin-api.md). The only +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). The only requirement is that they are beneath the `AppProvider` in the react tree. ## Supplying APIs @@ -71,15 +71,15 @@ requirement is that they are beneath the `AppProvider` in the react tree. ### API Factories APIs are registered in the form of -[ApiFactories](../reference/core-plugin-api.apifactory.md), which encapsulate +[`ApiFactory`](../reference/core-plugin-api.apifactory.md) instances, which encapsulate the process of instantiating an API. It is a collection of three things: the -[ApiRef](../reference/core-plugin-api.apiref.md) of the API to instantiate, a +[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API to instantiate, a list of all required dependencies, and a factory function that returns a new API instance. For example, this is the default -[ApiFactory](../reference/core-plugin-api.apifactory.md) for the -[ErrorApi](../reference/core-plugin-api.errorapi.md): +[`ApiFactory`](../reference/core-plugin-api.apifactory.md) for the +[`ErrorApi`](../reference/core-plugin-api.errorapi.md): ```ts createApiFactory({ @@ -93,25 +93,25 @@ createApiFactory({ }); ``` -In this example the [errorApiRef](../reference/core-plugin-api.errorapiref.md) +In this example the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) is our API, which encapsulates the -[ErrorApi](../reference/core-plugin-api.errorapi.md) type. The -[alertApiRef](../reference/core-plugin-api.alertapiref.md) is our single +[`ErrorApi`](../reference/core-plugin-api.errorapi.md) type. The +[`alertApiRef`](../reference/core-plugin-api.alertapiref.md) is our single dependency, which we give the name `alertApi`, and is then passed on to the factory function, which returns an implementation of the -[ErrorApi](../reference/core-plugin-api.errorapi.md). +[`ErrorApi`](../reference/core-plugin-api.errorapi.md). -The [createApiFactory](../reference/core-plugin-api.createapifactory.md) +The [`createApiFactory`](../reference/core-plugin-api.createapifactory.md) function is a thin wrapper that enables TypeScript type inference. You may notice that there are no type annotations in the above example, and that is because we're able to infer all types from the -[ApiRef](../reference/core-plugin-api.apiref.md)s. TypeScript will make sure +[`ApiRef`](../reference/core-plugin-api.apiref.md)s. TypeScript will make sure that the return value of the `factory` function matches the type embedded in -`api`'s [ApiRef](../reference/core-plugin-api.apiref.md), in this case the -[ErrorApi](../reference/core-plugin-api.errorapi.md). It will also match the +`api`'s [`ApiRef`](../reference/core-plugin-api.apiref.md), in this case the +[`ErrorApi`](../reference/core-plugin-api.errorapi.md). It will also match the types between the `deps` and the parameters of the `factory` function, again using the type embedded within the -[ApiRef](../reference/core-plugin-api.apiref.md)s. +[`ApiRef`](../reference/core-plugin-api.apiref.md)s. ## Registering API Factories @@ -123,13 +123,13 @@ app, and the app itself. Starting with the Backstage core library, it provides implementations for all of the core APIs. The core APIs are the ones exported by -[@backstage/core-plugin-api](../reference/core-plugin-api.md), such as the -[errorApiRef](../reference/core-plugin-api.errorapiref.md) and -[configApiRef](../reference/core-plugin-api.configapiref.md). +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), such as the +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) and +[`configApiRef`](../reference/core-plugin-api.configapiref.md). The core APIs are loaded for any app created with -[createApp](../reference/app-defaults.createapp.md) from -[@backstage/core-plugin-api](../reference/app-defaults.md), which means that +[`createApp`](../reference/app-defaults.createapp.md) from +[`@backstage/core-plugin-api`](../reference/app-defaults.md), which means that there is no step that needs to be taken to include these APIs in an app. ### Plugin APIs @@ -137,13 +137,13 @@ there is no step that needs to be taken to include these APIs in an app. In addition to the core APIs, plugins can define and export their own APIs. While doing so they should usually also provide default implementations of their own APIs, for example, the `catalog` plugin exports `catalogApiRef`, and also -supplies a default [ApiFactory](../reference/core-plugin-api.apifactory.md) of +supplies a default [`ApiFactory`](../reference/core-plugin-api.apifactory.md) of that API using the `CatalogClient`. There is one restriction to plugin-provided API Factories: plugins may not supply factories for core APIs, trying to do so will cause the app to refuse to start. Plugins supply their APIs through the `apis` option of -[createPlugin](../reference/core-plugin-api.createplugin.md), for example: +[`createPlugin`](../reference/core-plugin-api.createplugin.md), for example: ```ts export const techdocsPlugin = createPlugin({ @@ -168,7 +168,7 @@ Lastly, the app itself is the final point where APIs can be added, and what has the final say in what APIs will be loaded at runtime. The app may override the factories for any of the core or plugin APIs, with the exception of the config, app theme, and identity APIs. These are static APIs that are tied into the -[createApp](../reference/app-defaults.createapp.md) implementation, and +[`createApp`](../reference/app-defaults.createapp.md) implementation, and therefore not possible to override. Overriding APIs is useful for apps that want to switch out behavior to tailor it @@ -231,19 +231,19 @@ const app = createApp({ ``` Note that the above line will cause an error if `IgnoreErrorApi` does not fully -implement the [ErrorApi](../reference/core-plugin-api.errorapi.md), as it is +implement the [`ErrorApi`](../reference/core-plugin-api.errorapi.md), as it is checked by the type embedded in the -[errorApiRef](../reference/core-plugin-api.errorapiref.md) at compile time. +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) at compile time. ## Defining custom Utility APIs Plugins are free to define their own Utility APIs. Simply define the TypeScript interface for the API, and create an -[ApiRef](../reference/core-plugin-api.apiref.md) using -[createApiRef](../reference/core-plugin-api.createapiref.md) exported from -[@backstage/core-plugin-api](../reference/core-plugin-api.md). Also be sure to +[`ApiRef`](../reference/core-plugin-api.apiref.md) using +[`createApiRef`](../reference/core-plugin-api.createapiref.md) exported from +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also be sure to provide at least one implementation of the API, and to declare a default factory -for the API in [createPlugin](../reference/core-plugin-api.createplugin.md). +for the API in [`createPlugin`](../reference/core-plugin-api.createplugin.md). Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's @@ -255,16 +255,16 @@ backwards compatibility of public APIs, as you may otherwise break apps that are using your plugin. To make an API public, simply export the -[ApiRef](../reference/core-plugin-api.apiref.md) of the API, and any associated +[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API, and any associated types. To make an API private, just avoid exporting the -[ApiRef](../reference/core-plugin-api.apiref.md), but still be sure to supply a -default factory to [createPlugin](../reference/core-plugin-api.createplugin.md). +[`ApiRef`](../reference/core-plugin-api.apiref.md), but still be sure to supply a +default factory to [`createPlugin`](../reference/core-plugin-api.createplugin.md). Private APIs are useful for plugins that want to depend on other APIs outside of React components, but not have to expose an entire API surface to maintain. When using private APIs, it is fine to use the `typeof` of an implementing class as the type parameter passed to -[createApiRef](../reference/core-plugin-api.createapiref.md), while public APIs +[`createApiRef`](../reference/core-plugin-api.createapiref.md), while public APIs should always define a separate TypeScript interface type. Plugins may depend on APIs from other plugins, both in React components and as @@ -273,13 +273,13 @@ dependencies between plugins. ## Architecture -The [ApiRef](../reference/core-plugin-api.apiref.md) instances mentioned above +The [`ApiRef`](../reference/core-plugin-api.apiref.md) instances mentioned above provide a point of indirection between consumers and producers of Utility APIs. It allows for plugins and components to depend on APIs in a type-safe way, without having a direct reference to a concrete implementation of the APIs. The Apps are also given a lot of flexibility in what implementations to provide. As long as they adhere to the contract established by an -[ApiRef](../reference/core-plugin-api.apiref.md), they are free to choose any +[`ApiRef`](../reference/core-plugin-api.apiref.md), they are free to choose any implementation they want. The figure below shows the relationship between @@ -304,16 +304,16 @@ The indirection provided by Utility APIs also makes it straightforward to test components that depend on APIs, and to provide a standard common development environment for plugins. A proper test wrapper with mocked API implementations is not yet ready, but it will be provided as a part of -[@backstage/test-utils](../reference/test-utils.md). It will provide mocked +[`@backstage/test-utils`](../reference/test-utils.md). It will provide mocked variants of APIs, with additional methods for asserting a component's interaction with the API. The common development environment for plugins is included in -[@backstage/dev-utils](../reference/dev-utils.md), where the exported -[createDevApp](../reference/dev-utils.createdevapp.md) function creates an +[`@backstage/dev-utils`](../reference/dev-utils.md), where the exported +[`createDevApp`](../reference/dev-utils.createdevapp.md) function creates an application with implementations for all core APIs already present. Contrary to the method for wiring up Utility API implementations in an app created with -[createApp](../reference/app-defaults.createapp.md), -[createDevApp](../reference/dev-utils.createdevapp.md) uses automatic dependency +[`createApp`](../reference/app-defaults.createapp.md), +[`createDevApp`](../reference/dev-utils.createdevapp.md) uses automatic dependency injection. This is to make it possible to replace any API implementation, and having that be reflected in dependents of that API. diff --git a/docs/architecture-decisions/adr003-avoid-default-exports.md b/docs/architecture-decisions/adr003-avoid-default-exports.md index 34e5cd5411..8ab79a451f 100644 --- a/docs/architecture-decisions/adr003-avoid-default-exports.md +++ b/docs/architecture-decisions/adr003-avoid-default-exports.md @@ -15,7 +15,7 @@ thing well". The module would be consumed (`const localName = require('the-module');`) without having to know the internal structure. -Now, ESModules are the primary authoring format. They have numerous benefits, +Now, `ESModules` are the primary authoring format. They have numerous benefits, such as compile-time verification of exports, and standards-defined semantics. They have a similar mechanism known as "default exports", which allows for a consumer to `import localName from 'the-module';`. This is implicitly the same diff --git a/docs/architecture-decisions/index.md b/docs/architecture-decisions/index.md index 15d1b0e9d5..615d737fe1 100644 --- a/docs/architecture-decisions/index.md +++ b/docs/architecture-decisions/index.md @@ -34,5 +34,5 @@ Records should be stored under the `architecture-decisions` directory. ## Superseding an ADR -If an ADR supersedes an older ADR then the older ADR's status is changed to -superseded by ADR-XXXX and links to the new ADR. +If an ADR supersedes an older ADR then the status of the older ADR is changed to +"superseded by ADR-XXXX", and links to the new ADR. diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index 90939a1f03..c587f60aa9 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -18,7 +18,7 @@ Settings for local development: - Name: Backstage (or your custom app name) - Redirect URI: `http://localhost:7007/api/auth/gitlab/handler/frame` -- Scopes: read_user +- Scopes: `read_user` ## Configuration diff --git a/docs/conf/defining.md b/docs/conf/defining.md index 29964dbcbc..9e13442f7b 100644 --- a/docs/conf/defining.md +++ b/docs/conf/defining.md @@ -138,7 +138,7 @@ may need to pass in all files using one or multiple `--config ` options. > to change for different deployment environments should be static > configuration, while it should otherwise be avoided. -When defining configuration for your plugin, keep keys camelCased and stick to +When defining configuration for your plugin, keep keys on `camelCase` form and stick to existing casing conventions such as `baseUrl` rather than `baseURL`. It is also usually best to prefer objects over arrays, as it makes it possible diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 7dc0e326fa..55843f3b24 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -217,11 +217,11 @@ for some dashboards, such as GKE. ###### required parameters for GKE -| Name | Description | -| ----------- | ------------------------------------------------------------------------ | -| projectId | the ID of the GCP project containing your Kubernetes clusters | -| region | the region of GCP containing your Kubernetes clusters | -| clusterName | the name of your kubernetes cluster, within your `projectId` GCP project | +| Name | Description | +| ------------- | ------------------------------------------------------------------------ | +| `projectId` | the ID of the GCP project containing your Kubernetes clusters | +| `region` | the region of GCP containing your Kubernetes clusters | +| `clusterName` | the name of your kubernetes cluster, within your `projectId` GCP project | Note that the GKE cluster locator can automatically provide the values for the `dashboardApp` and `dashboardParameters` options if you set the diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index 64109385a0..146f160d71 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -6,7 +6,7 @@ description: Documentation on Customizing look and feel of the App Backstage ships with a default theme with a light and dark mode variant. The themes are provided as a part of the -[@backstage/theme](https://www.npmjs.com/package/@backstage/theme) package, +[`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package, which also includes utilities for customizing the default theme, or creating completely new themes. @@ -14,7 +14,7 @@ completely new themes. The easiest way to create a new theme is to use the `createTheme` function exported by the -[@backstage/theme](https://www.npmjs.com/package/@backstage/theme) package. You +[`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package. You can use it to override some basic parameters of the default theme such as the color palette and font. @@ -33,16 +33,16 @@ const myTheme = createTheme({ If you want more control over the theme, and for example customize font sizes and margins, you can use the lower-level `createThemeOverrides` function -exported by [@backstage/theme](https://www.npmjs.com/package/@backstage/theme) +exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) in combination with -[createTheme](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme) -from [@material-ui/core](https://www.npmjs.com/package/@material-ui/core). See +[`createTheme`](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme) +from [`@material-ui/core`](https://www.npmjs.com/package/@material-ui/core). See the "Overriding Backstage and Material UI css rules" section below. You can also create a theme from scratch that matches the `BackstageTheme` type -exported by [@backstage/theme](https://www.npmjs.com/package/@backstage/theme). +exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). See the -[material-ui docs on theming](https://material-ui.com/customization/theming/) +[Material-UI docs on theming](https://material-ui.com/customization/theming/) for more information about how that can be done. ## Using your Custom Theme @@ -79,7 +79,7 @@ const app = createApp({ Note that your list of custom themes overrides the default themes. If you still want to use the default themes, they are exported as `lightTheme` and `darkTheme` from -[@backstage/theme](https://www.npmjs.com/package/@backstage/theme). +[`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). ## Example of a custom theme diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index 9bff2385e3..08dad94ecf 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -38,7 +38,7 @@ a structure with up to six elements: not set. The address used to clone a repo is the `cloneUrl` plus the repo name. - `gitilesBaseUrl` (optional): This is needed for creating a valid user-friendly URL that can be used for browsing the content of the provider. If not set a default - value will be created in the same way as the "baseUrl" option. There is no + value will be created in the same way as the `baseUrl` option. There is no requirement to have Gitiles for the Backstage Gerrit integration but without it some links in the Backstage UI will be broken. - `username` (optional): The Gerrit username to use in API requests. If diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index 2ec9d7a6e5..1c64fd3f68 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -196,14 +196,14 @@ const App = () => ( There are a couple of naming patterns to adhere to as you build plugins, which helps clarify the intent and usage of the exports. -| Description | Pattern | Examples | -| --------------------- | --------------- | ---------------------------------------------- | -| Top-level Pages | \*Page | CatalogIndexPage, SettingsPage, LighthousePage | -| Entity Tab Content | Entity\*Content | EntityJenkinsContent, EntityKubernetesContent | -| Entity Overview Card | Entity\*Card | EntitySentryCard, EntityPagerDutyCard | -| Entity Conditional | is\*Available | isPagerDutyAvailable, isJenkinsAvailable | -| Plugin Instance | \*Plugin | jenkinsPlugin, catalogPlugin | -| Utility API Reference | \*ApiRef | configApiRef, catalogApiRef | +| Description | Pattern | Examples | +| --------------------- | ----------------- | ---------------------------------------------------- | +| Top-level Pages | `\*Page` | `CatalogIndexPage`, `SettingsPage`, `LighthousePage` | +| Entity Tab Content | `Entity\*Content` | `EntityJenkinsContent`, `EntityKubernetesContent` | +| Entity Overview Card | `Entity\*Card` | `EntitySentryCard`, `EntityPagerDutyCard` | +| Entity Conditional | `is\*Available` | `isPagerDutyAvailable`, `isJenkinsAvailable` | +| Plugin Instance | `\*Plugin` | `jenkinsPlugin`, `catalogPlugin` | +| Utility API Reference | `\*ApiRef` | `configApiRef`, `catalogApiRef` | ### Routing System @@ -515,10 +515,10 @@ deprecated while making the new additions, to then be removed at a later point. Many export naming patterns have been changed to avoid import aliases and to clarify intent. Refer to the following table to formulate the new name: -| Description | Existing Pattern | New Pattern | Examples | -| -------------------- | -------------------------- | --------------- | ---------------------------------------------- | -| Top-level Pages | Router | \*Page | CatalogIndexPage, SettingsPage, LighthousePage | -| Entity Tab Content | Router | Entity\*Content | EntityJenkinsContent, EntityKubernetesContent | -| Entity Overview Card | \*Card | Entity\*Card | EntitySentryCard, EntityPagerDutyCard | -| Entity Conditional | isPluginApplicableToEntity | is\*Available | isPagerDutyAvailable, isJenkinsAvailable | -| Plugin Instance | plugin | \*Plugin | jenkinsPlugin, catalogPlugin | +| Description | Existing Pattern | New Pattern | Examples | +| -------------------- | ---------------------------- | ----------------- | ---------------------------------------------------- | +| Top-level Pages | `Router` | `\*Page` | `CatalogIndexPage`, `SettingsPage`, `LighthousePage` | +| Entity Tab Content | `Router` | `Entity\*Content` | `EntityJenkinsContent`, `EntityKubernetesContent` | +| Entity Overview Card | `\*Card` | `Entity\*Card` | `EntitySentryCard`, `EntityPagerDutyCard` | +| Entity Conditional | `isPluginApplicableToEntity` | `is\*Available` | `isPagerDutyAvailable`, `isJenkinsAvailable` | +| Plugin Instance | `plugin` | `\*Plugin` | `jenkinsPlugin`, `catalogPlugin` | diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index 9e8b67710a..c71b27b448 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -83,7 +83,7 @@ export const ExamplePage = examplePlugin.provide( This is where the plugin is created and where it creates and exports extensions that can be imported and used the app. See reference docs for -[createPlugin](../reference/core-plugin-api.createplugin.md) or introduction to +[`createPlugin`](../reference/core-plugin-api.createplugin.md) or introduction to the new [Composability System](./composability.md). ## Components diff --git a/docs/plugins/url-reader.md b/docs/plugins/url-reader.md index 4c6938ade2..8788737f17 100644 --- a/docs/plugins/url-reader.md +++ b/docs/plugins/url-reader.md @@ -174,14 +174,14 @@ which can be used to request the provider's API. `read` then makes an authenticated request to the provider API and returns the file's content. -#### readUrl +#### `readUrl` `readUrl` is a new interface that allows complex response objects and is intended to replace the `read` method. This new method is currently optional to implement which allows for a soft migration to `readUrl` instead of `read` in the future. -#### readTree +#### `readTree` `readTree` method also expects user-friendly URLs similar to `read` but the URL should point to a tree (could be the root of a repository or even a @@ -241,8 +241,8 @@ without an `etag`, the response contains an ETag of the resource (should ideally forward the ETag returned by the provider). If the method is called with an `etag`, it first compares the ETag and returns a `NotModifiedError` in case the resource has not been modified. This approach is very similar to the actual -[ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and -[If-None-Match](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) +[`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and +[`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) HTTP headers. ### 6. Debugging diff --git a/docs/releases/v1.2.0.md b/docs/releases/v1.2.0.md index dba36bfa2c..b2d50a09a2 100644 --- a/docs/releases/v1.2.0.md +++ b/docs/releases/v1.2.0.md @@ -38,7 +38,7 @@ Server-to-server authentication tokens issued from a TokenManager (specifically, ### Kubernetes -Added support for `oidc` as authProvider for kubernetes authentication and added an optional `oidcTokenProvider` config value. This will allow users to authenticate to kubernetes clusters using ID tokens obtained from the configured auth provider in their Backstage instance. Contributed by @dbravovmw(https://github.com/dbravovmw). #11328(https://github.com/backstage/backstage/pull/11328) +Added support for `oidc` as an auth provider for kubernetes authentication and added an optional `oidcTokenProvider` config value. This will allow users to authenticate to kubernetes clusters using ID tokens obtained from the configured auth provider in their Backstage instance. Contributed by @dbravovmw(https://github.com/dbravovmw). #11328(https://github.com/backstage/backstage/pull/11328) ### Misc diff --git a/microsite/README.md b/microsite/README.md index 285622f1f8..723baa1a54 100644 --- a/microsite/README.md +++ b/microsite/README.md @@ -167,7 +167,7 @@ For more information about blog posts, click [here](https://docusaurus.io/docs/e ### Adding items to your site's top navigation bar -1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: +1. Add links to docs, custom pages or external links by editing the `headerLinks` field of `website/siteConfig.js`: `website/siteConfig.js` diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md index bd8bc3a4ea..47bc1547c6 100644 --- a/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md @@ -1,4 +1,4 @@ # Plugin A -This is a description of Plugin A. This file exists to prove that glob'd +This is a description of Plugin A. This file exists to prove that glob formed includes using the `*include` syntax work as expected. diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md index 93e545252f..2c9571add6 100644 --- a/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md @@ -1,4 +1,4 @@ # Plugin B -This is a description of Plugin B. This file exists to prove that glob'd +This is a description of Plugin B. This file exists to prove that glob formed includes using the `*include` syntax work as expected. diff --git a/packages/theme/src/pageTheme.ts b/packages/theme/src/pageTheme.ts index 5b5eb24be9..dd36d7641b 100644 --- a/packages/theme/src/pageTheme.ts +++ b/packages/theme/src/pageTheme.ts @@ -23,7 +23,7 @@ import { PageTheme } from './types'; * * How to add a shape: * - * 1. Get the svg shape from figma, should be ~1400 wide, ~400 high + * 1. Get the SVG shape from figma, should be ~1400 wide, ~400 high * and only the white-to-transparent mask, no colors. * 2. Run it through https://jakearchibald.github.io/svgomg/ * 3. Run that through https://github.com/tigt/mini-svg-data-uri @@ -62,7 +62,7 @@ export const colorVariants: Record = { * @remarks * * As the background shapes and colors are decorative, we place them onto the - * page as a css background-image instead of an html element of its own. + * page as a CSS `background-image` instead of an HTML element of its own. */ export function genPageTheme(props: { colors: string[]; diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md index fe2cd05c22..d13a5e5a20 100644 --- a/plugins/auth-backend/README.md +++ b/plugins/auth-backend/README.md @@ -91,9 +91,9 @@ export AUTH_GITLAB_CLIENT_SECRET=x Add a new Okta application using the following URI conventions: -Login redirect URI's: `http://localhost:7007/api/auth/okta/handler/frame` -Logout redirect URI's: `http://localhost:7007/api/auth/okta/logout` -Initiate login URI's: `http://localhost:7007/api/auth/okta/start` +Login redirect URIs: `http://localhost:7007/api/auth/okta/handler/frame` +Logout redirect URIs: `http://localhost:7007/api/auth/okta/logout` +Initiate login URIs: `http://localhost:7007/api/auth/okta/start` Then configure the following environment variables to be used in the `app-config.yaml` file: diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index 2b20da2f26..cc90b4d7a2 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -136,7 +136,7 @@ For showing cost breakdowns you can define a map of cloud products. They must be You can optionally supply a product `icon` to display in Cost Insights navigation. See the [type file](https://github.com/backstage/backstage/blob/master/plugins/cost-insights/src/types/Icon.ts) for supported types and Material UI icon [mappings](https://github.com/backstage/backstage/blob/master/plugins/cost-insights/src/utils/navigation.tsx). -**Note:** Product keys should be unique and camelCased. Backstage does not support underscores in configuration keys. +**Note:** Product keys should be unique and on `camelCase` form. Backstage does not support underscores in configuration keys. ```yaml ## ./app-config.yaml diff --git a/plugins/dynatrace/README.md b/plugins/dynatrace/README.md index 8ed72ebb5d..e5684b2017 100644 --- a/plugins/dynatrace/README.md +++ b/plugins/dynatrace/README.md @@ -29,7 +29,7 @@ proxy: Authorization: 'Api-Token ${DYNATRACE_ACCESS_TOKEN}' ``` -It also requires a baseUrl for rendering links to problems in the table like so: +It also requires a `baseUrl` for rendering links to problems in the table like so: ```yaml dynatrace: diff --git a/plugins/stack-overflow-backend/README.md b/plugins/stack-overflow-backend/README.md index 62115b889c..acff784cc6 100644 --- a/plugins/stack-overflow-backend/README.md +++ b/plugins/stack-overflow-backend/README.md @@ -37,7 +37,7 @@ Before you are able to start index stack overflow questions to search, you need When you have your `packages/backend/src/plugins/search.ts` file ready to make modifications, add the following code snippet to add the `StackOverflowQuestionsCollatorFactory`. Note that you can modify the `requestParams`. -> Note: if your baseUrl is set to the external stack overflow api `https://api.stackexchange.com/2.2`, you can find optional and required parameters under the official API documentation under [`Usage of /questions GET`](https://api.stackexchange.com/docs/questions) +> Note: if your `baseUrl` is set to the external stack overflow api `https://api.stackexchange.com/2.2`, you can find optional and required parameters under the official API documentation under [`Usage of /questions GET`](https://api.stackexchange.com/docs/questions) ```ts indexBuilder.addCollator({ diff --git a/plugins/techdocs/src/reader/README.md b/plugins/techdocs/src/reader/README.md index 63464dfa81..fe1d1ff488 100644 --- a/plugins/techdocs/src/reader/README.md +++ b/plugins/techdocs/src/reader/README.md @@ -17,4 +17,4 @@ export const updateH1Text = (): Transformer => { }; ``` -The transformers are then registered in the Reader.tsx file. They are registered in two places, one place that runs before it's attached to the actual browser DOM (preTransformers) and once after (postTransfomers). Doing modifications is faster before it's attached, but doesn't allow us to do some things, such as attaching event listeners. +The transformers are then registered in the Reader.tsx file. They are registered in two places, one place that runs before it's attached to the actual browser DOM (`preTransformer`s) and once after (`postTransformer`s). Doing modifications is faster before it's attached, but doesn't allow us to do some things, such as attaching event listeners. From 03c9e3aea6c8821254a732b8c1f70602598e22ba Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Sat, 22 Oct 2022 17:38:10 -0600 Subject: [PATCH 138/221] Removing forced capitalization for Entity types in the sidebar. Signed-off-by: Josh Maxwell --- .../src/components/EntityTypePicker/EntityTypePicker.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx index a0770ef30a..b90349949e 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx @@ -54,10 +54,10 @@ export const EntityTypePicker = (props: EntityTypePickerProps) => { if (availableTypes.length === 0 || error) return null; const items = [ - { value: 'all', label: 'All' }, + { value: 'all', label: 'all' }, ...availableTypes.map((type: string) => ({ value: type, - label: capitalize(type), + label: type, })), ]; From 83d7dacc4caa4032968dd234d55bde37faa8ebbd Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Sat, 22 Oct 2022 19:19:38 -0600 Subject: [PATCH 139/221] Removing unused capitalization import Signed-off-by: Josh Maxwell --- .../src/components/EntityTypePicker/EntityTypePicker.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx index b90349949e..3e641e8e37 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx @@ -15,7 +15,6 @@ */ import React, { useEffect } from 'react'; -import capitalize from 'lodash/capitalize'; import { Box } from '@material-ui/core'; import { useEntityTypeFilter } from '../../hooks/useEntityTypeFilter'; From 3c072cae169fd4c2c29b4adf777dcea1929b0998 Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Sat, 22 Oct 2022 19:43:21 -0600 Subject: [PATCH 140/221] Updating tests to check for lowercase type names Signed-off-by: Josh Maxwell --- .../EntityTypePicker/EntityTypePicker.test.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx index 4623cf5d9e..9992322f61 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; -import { capitalize } from 'lodash'; import { Entity } from '@backstage/catalog-model'; import { EntityTypePicker } from './EntityTypePicker'; import { MockEntityListContextProvider } from '../../testUtils/providers'; @@ -99,11 +98,11 @@ describe('', () => { const input = rendered.getByTestId('select'); fireEvent.click(input); - await waitFor(() => rendered.getByText('Service')); + await waitFor(() => rendered.getByText('service')); entities.forEach(entity => { expect( - rendered.getByText(capitalize(entity.spec!.type as string)), + rendered.getByText(entity.spec!.type as string), ).toBeInTheDocument(); }); }); @@ -125,8 +124,8 @@ describe('', () => { const input = rendered.getByTestId('select'); fireEvent.click(input); - await waitFor(() => rendered.getByText('Service')); - fireEvent.click(rendered.getByText('Service')); + await waitFor(() => rendered.getByText('service')); + fireEvent.click(rendered.getByText('service')); expect(updateFilters).toHaveBeenLastCalledWith({ type: new EntityTypeFilter(['service']), From 395c13c6ae851858ac8333ca2afae5d1d4342095 Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Sat, 22 Oct 2022 20:36:29 -0600 Subject: [PATCH 141/221] Updating tests to check for lowercase all type Signed-off-by: Josh Maxwell --- .../src/components/EntityTypePicker/EntityTypePicker.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx index 9992322f61..525c757dea 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx @@ -132,7 +132,7 @@ describe('', () => { }); fireEvent.click(input); - fireEvent.click(rendered.getByText('All')); + fireEvent.click(rendered.getByText('all')); expect(updateFilters).toHaveBeenLastCalledWith({ type: undefined }); }); From e47f466f8090d69691126feaf596bcd3e5a81f04 Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Sat, 22 Oct 2022 20:59:18 -0600 Subject: [PATCH 142/221] Adding changeset summary Signed-off-by: Josh Maxwell --- .changeset/happy-avocados-tan.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/happy-avocados-tan.md diff --git a/.changeset/happy-avocados-tan.md b/.changeset/happy-avocados-tan.md new file mode 100644 index 0000000000..9f087930d5 --- /dev/null +++ b/.changeset/happy-avocados-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Removed forced capitalization for Entity types in the catalog sidebar. From a83eb55d1c4e6f9b55bed4c4076872bb7f0a0201 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:32:58 +0200 Subject: [PATCH 143/221] docs/api/backend: updates to fit current system and reuse existing docs Signed-off-by: Patrik Oldsberg --- docs/api/backend.md | 102 +++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 45 deletions(-) diff --git a/docs/api/backend.md b/docs/api/backend.md index 5f04d38214..5c2b4c4245 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -1,38 +1,43 @@ --- id: backend -title: Backend -description: About Backend +title: New Backend System +description: Details of the upcoming backend system --- -## Backend System +> **DISCLAMER: The new backend system is under active development and is not considered stable** -**DISCLAMER: The new backend system is under active development and is not considered stable** +## Overview This is an example of how you create, start and add existing plugins to your backend. ```ts import { createBackend } from '@backstage/backend-defaults'; +import { catalogPlugin } from '@backstage/plugin-catalog-backend'; +// Create your backend instance const backend = createBackend(); -// backend.add(catalogPlugin()); +// Install all desired features +backend.add(catalogPlugin()); + +// Start up the backend await backend.start(); ``` -### Overview +## Backend Services -The default backend provides several _services_ out of the box which includes access to config, logging, scheduling and more. -Service are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. +The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. +Services are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. -### Service Refs +### Service References -A serviceRef is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. +A `ServiceRef` is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database. On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them. ServiceRefs contain a scope which is used to determine if the serviceFactory creating the service will create a new instance scoped per plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin/module and `root` scoped services will be created once per backend instance. -#### Defining a ServiceRef +#### Defining a Service ```ts import { @@ -61,51 +66,59 @@ export const exampleServiceRef = createServiceRef({ }, // Logger is available directly in the factory as it's a root scoped service and will be created once per backend instance. async factory({ logger }) { - // plugin is available as it's a plugin scoped service and will be created once per plugin. return async ({ plugin }) => { - // This block will be executed once per plugin depending on this serviceRef - logger.info(`Creating example service for for plugin ${plugin.id}`); - return new ExampleImpl({logger}); + // This block will be executed once for every plugin that depends on this service + logger.info('Initializing example service plugin instance'); + return new ExampleImpl({ logger }); }; }, }), -}), +}); ``` -### Overriding services +### Overriding Services -In this example replace the default log implementation with a custom logger. +In this example replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service. ```ts import { createServiceFactory, - loggerServiceRef, + rootLoggerServiceRef, + LoggerService, } from '@backstage/backend-plugin-api'; -export const gcpLoggerFactory = createServiceFactory({ - service: loggerServiceRef, - deps: {}, - async factory({}) { - return async ({}) => { - // This custom implementation conform with the type of the loggerServiceRef + +// This custom implementation would typically live separately from +// the backend setup code, either nearby such as in +// packages/backend/src/services/logger/GoogleCloudLogger.ts +// Or you can let it live in its own library package. +class GoogleCloudLogger implements LoggerService { + static factory = createServiceFactory({ + service: rootLoggerServiceRef, + deps: {}, + async factory() { return new GoogleCloudLogger(); - }; - }, -}); + }, + }); + // custom implementation here ... +} // packages/backend/src/index.ts const backend = createBackend({ services: [ - // supplies additional/replacement services to the backend - gcpLoggerFactory, + // supplies additional or replacement services to the backend + GoogleCloudLogger.factory(), ], -}) +}); ``` ## Writing Plugins ```ts -import { configServiceRef, createBackendPlugin } from '@backstage/backend-plugin-api'; +import { + configServiceRef, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; // export type ExamplePluginOptions = { exampleOption: boolean }; export const examplePlugin = createBackendPlugin({ @@ -134,6 +147,7 @@ backend.add(examplePlugin()); // Options can be passed to the plugin // backend.add(examplePlugin({ exampleOption: true})); ``` + ## Writing Modules Some facts about modules @@ -142,9 +156,9 @@ Some facts about modules - A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. - A module is always initialized before the plugin it extends. -A module depend on the extensionPoint exported by the plugins library package(eg `catalog-node`, `scaffolder-backend`) and does not directly declare a dependency on the plugin package itself. +A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. -Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint` +Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: ```ts import { createBackendModule } from '@backstage/backend-plugin-api'; @@ -177,7 +191,7 @@ Modules depend on extension points just as a regular dependency but specifying i import { createExtensionPoint } from '@backstage/backend-plugin-api'; export interface ScaffolderActionsExtensionPoint { - addAction(action: ScaffolderAction): void; + addAction(action: ScaffolderAction): void; } export const ScaffolderActionsExtensionPoint = @@ -190,8 +204,7 @@ export const ScaffolderActionsExtensionPoint = Extension points are registered by a plugin and extended by modules. - -### Testing +## Testing Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. @@ -206,18 +219,17 @@ describe('Example', () => { // plugins and modules for testing features: [testModule()], }); - // assertions + + // assertions }); }); ``` - ## Package structure -The package relationship between plugins, modules and extension are illustrated in the following diagram. +A detailed explanation of the package architecture can be found in the [Backstage Architecture Overview](../overview/architecture-overview.md#package-architecture). The most important packages to consider for this system are `backend`, `plugin--backend`, `plugin--node`, and `plugin--backend-module-`. -Taken with an artificial foobar backend plugin. - -- `plugin-foobar-backend` houses the plugin and registers the extension points into the backend system. -- `plugin-foobar-common` houses the shared types including the Extension Point registered by the backend. -- `plugin-foobar-XYZ-module` houses the modules that extend the foobar backend with extension points imported from `plugin-foobar-common` +- `plugin--backend` houses the implementation of the plugins themselves. +- `plugin--node` houses the extension points and any other utilities that modules or other plugins might need. +- `plugin--backend-module-` houses the modules that extend the plugins via the extension points. +- `backend` is the backend itself that wires everything together to something that you can deploy. From 7c7a124563120e1ea50c3ff242903daea5ce9546 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:34:54 +0200 Subject: [PATCH 144/221] docs/api/backend: move to docs/plugins/new-backend-system and add to sidebar Signed-off-by: Patrik Oldsberg --- docs/{api/backend.md => plugins/new-backend-system.md} | 2 +- microsite/sidebars.json | 3 ++- mkdocs.yml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) rename docs/{api/backend.md => plugins/new-backend-system.md} (99%) diff --git a/docs/api/backend.md b/docs/plugins/new-backend-system.md similarity index 99% rename from docs/api/backend.md rename to docs/plugins/new-backend-system.md index 5c2b4c4245..5ce1b1b81a 100644 --- a/docs/api/backend.md +++ b/docs/plugins/new-backend-system.md @@ -1,5 +1,5 @@ --- -id: backend +id: new-backend-system title: New Backend System description: Details of the upcoming backend system --- diff --git a/microsite/sidebars.json b/microsite/sidebars.json index f00b7b2ee1..3cfa1aa2f6 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -233,7 +233,8 @@ "plugins/proxying", "plugins/backend-plugin", "plugins/call-existing-api", - "plugins/url-reader" + "plugins/url-reader", + "plugins/new-backend-system" ] }, { diff --git a/mkdocs.yml b/mkdocs.yml index 8c86ae095d..906e2aebeb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -137,6 +137,7 @@ nav: - Backend plugin: 'plugins/backend-plugin.md' - Call existing API: 'plugins/call-existing-api.md' - URL Reader: 'plugins/url-reader.md' + - New Backend System: 'plugins/new-backend-system.md' - Testing: - Testing with Jest: 'plugins/testing.md' - Publishing: From a6c98097e206d0f89d683044e3d6b1ce2d7a02af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:38:21 +0200 Subject: [PATCH 145/221] docs/links: added short lint to new backend system docs Signed-off-by: Patrik Oldsberg --- microsite/pages/en/link.js | 1 + 1 file changed, 1 insertion(+) diff --git a/microsite/pages/en/link.js b/microsite/pages/en/link.js index 6f56aa768c..ccda70fa54 100644 --- a/microsite/pages/en/link.js +++ b/microsite/pages/en/link.js @@ -6,6 +6,7 @@ const redirects = { 'bind-routes': '/docs/plugins/composability#binding-external-routes-in-the-app', 'scm-auth': '/docs/auth/#scaffolder-configuration-software-templates', + 'backend-system': '/docs/plugins/new-backend-system' }; const fallback = '/docs'; From fa9bbcfbdbdf1d7dbde0017fe3dd4954c6d8512a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 18:50:16 +0200 Subject: [PATCH 146/221] docs/new-backend-systen: add status section Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 5ce1b1b81a..3e7f610593 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -4,11 +4,17 @@ title: New Backend System description: Details of the upcoming backend system --- -> **DISCLAMER: The new backend system is under active development and is not considered stable** +> **DISCLAIMER: The new backend system is under active development and is not considered stable** + +## Status + +The new backend system is under active development, and only a small number of plugins and services have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet. + +You can find an example backend setup at https://github.com/backstage/backstage/tree/master/packages/backend-next. ## Overview -This is an example of how you create, start and add existing plugins to your backend. +This is an example of how you create, add plugins, and start up your backend. ```ts import { createBackend } from '@backstage/backend-defaults'; From 69837a3302d96de723a85a0fe29c24101da826f3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 19:32:18 +0200 Subject: [PATCH 147/221] docs/new-backend-systen: add building blocks section Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 38 +++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 3e7f610593..db7a812ecd 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -12,7 +12,43 @@ The new backend system is under active development, and only a small number of p You can find an example backend setup at https://github.com/backstage/backstage/tree/master/packages/backend-next. -## Overview +## Building Blocks + +This section introduces the high-level building blocks upon which this new system is built. These are all concepts that exist in our current system in one way or another, but the have all been lifted up to be first class concerns in the new system. + +### Backend + +This is the backend instance itself, which you can think of at the unity of deployment. It does not have any functionality in itself, but is simply responsible for wiring things together. + +It is up to you to decide how many different backends you want to deploy. You can have all features in a single one, or split things out into multiple smaller deployments. All depending on your need to scale and isolate individual features. + +### Plugins + +Plugins provide the actual features, just like in our existing system. They operate completely independently of each other. If plugins what to communicate with each other, they must do so over the wire. There can be no direct communication between plugins through code. Because of this constraints, each plugins can be considered to be its own microservice. + +### Services + +Services provide utilities to help make it simpler to implement plugins, so that each plugin doesn't need to implement everything from scratch. There are both many built-in services, like the ones for logging, database access, and reading configuration, but you can also import third-party services, or create your own. + +Services are also a customization point for individual backend installations. You can both override services with your own implementations, as well as make smaller customizations to existing services. + +### Extension Points + +Many plugins have ways in which you can extend them, for example entity providers for the Catalog, or custom actions for the Scaffolder. These extension patterns are now encoded into Extension Points. + +Extension Points look a little bit like services, since you depended on them just like you would a service. A key difference is that extension points are registered and provided by plugins themselves, based on what customizations each individual plugin wants to expose. + +Extension Points are also exported separately from the plugin instance itself, and a single plugin can also expose multiple different extension points at once. This makes it easier to evolve and deprecated individual Extension Points over time, rather than dealing with a single large API surface. + +### Modules + +Modules use the plugin Extension Points to add new features for plugins. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. Modules basically plugins for plugins. + +Each module may only extend a single plugin, and the module must be deployed together with that plugin in the same backend instance. Modules may however only communicate with their plugin through its registered extension points. + +Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations. + +## API Overview This is an example of how you create, add plugins, and start up your backend. From 84df1e4d5ffce01a65399637cc0151f9f3256cc8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 19:51:33 +0200 Subject: [PATCH 148/221] docs/new-backend-systen: more reasoning in the overview section and move it back up to the top Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 40 ++++++++++++++++-------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index db7a812ecd..d1306c3431 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -12,6 +12,28 @@ The new backend system is under active development, and only a small number of p You can find an example backend setup at https://github.com/backstage/backstage/tree/master/packages/backend-next. +## Overview + +The new Backstage backend system is being built to help make it simpler to install backend plugins and keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/TODO). + +One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { catalogPlugin } from '@backstage/plugin-catalog-backend'; + +// Create your backend instance +const backend = createBackend(); + +// Install all desired features +backend.add(catalogPlugin()); + +// Start up the backend +await backend.start(); +``` + +One notable change that helped achieve this much slimmer backend setup is the introduction of dependency injection, with a system that is very similar to the one in the Backstage frontend. + ## Building Blocks This section introduces the high-level building blocks upon which this new system is built. These are all concepts that exist in our current system in one way or another, but the have all been lifted up to be first class concerns in the new system. @@ -48,24 +70,6 @@ Each module may only extend a single plugin, and the module must be deployed tog Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations. -## API Overview - -This is an example of how you create, add plugins, and start up your backend. - -```ts -import { createBackend } from '@backstage/backend-defaults'; -import { catalogPlugin } from '@backstage/plugin-catalog-backend'; - -// Create your backend instance -const backend = createBackend(); - -// Install all desired features -backend.add(catalogPlugin()); - -// Start up the backend -await backend.start(); -``` - ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. From 2b54e0f780a32a4fabf961d9b809db73e7fa686a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 20:35:06 +0200 Subject: [PATCH 149/221] docs/new-backend-system: reorganize to prioritize plugin and module creation + tweaks Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 208 ++++++++++++++++------------- 1 file changed, 116 insertions(+), 92 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index d1306c3431..988086ff23 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -70,6 +70,122 @@ Each module may only extend a single plugin, and the module must be deployed tog Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations. +## Creating Plugins + +Plugins are created using the `createBackendPlugin` function. All plugins must have an ID and a register method. Plugins may also accept an options object, which can be either optional or required. The options are passed to the second parameter of the register method, and the options type is inferred and forwarded to the returned plugin factory function. + +```ts +import { + configServiceRef, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +// export type ExamplePluginOptions = { exampleOption: boolean }; +export const examplePlugin = createBackendPlugin({ + // unique id for the plugin + id: 'example', + // It's possible to provide options to the plugin + // register(env, options: ExamplePluginOptions) { + register(env) { + env.registerInit({ + deps: { + logger: loggerServiceRef, + }, + // logger is provided by the backend based on the dependency on loggerServiceRef above. + async init({ logger }) { + logger.info('Hello from example plugin'); + }, + }); + }, +}); +``` + +The plugin can then be installed in the backend using the returned plugin factory function: + +```ts +backend.add(examplePlugin()); +``` + +If we wanted our plugin to accept options as well, we'd accept the options as the second parameter of the register method: + +```ts +export const examplePlugin = createBackendPlugin({ + id: 'example', + register(env, options?: { silent?: boolean }) { + env.registerInit({ + deps: { logger: loggerServiceRef }, + async init({ logger }) { + if (!options?.silent) { + logger.info('Hello from example plugin'); + } + }, + }); + }, +}); +``` + +Passing the option to the plugin during installation looks like this: + +```ts +backend.add(examplePlugin({ silent: true })); +``` + +## Creating Modules + +Some facts about modules + +- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin. +- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. +- A module is always initialized before the plugin it extends. + +A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. + +Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: + +```ts +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { MyCustomProcessor } from './processor'; + +export const exampleCustomProcessorCatalogModule = createBackendModule({ + moduleId: 'exampleCustomProcessor', + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addProcessor(new MyCustomProcessor()); + }, + }); + }, +}); +``` + +### Extension Points + +Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. + +#### Defining an Extension Point + +```ts +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +export interface ScaffolderActionsExtensionPoint { + addAction(action: ScaffolderAction): void; +} + +export const scaffolderActionsExtensionPoint = + createExtensionPoint({ + id: 'scaffolder.actions', + }); +``` + +#### Registering an Extension Point + +Extension points are registered by a plugin and extended by modules. + ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. @@ -158,98 +274,6 @@ const backend = createBackend({ }); ``` -## Writing Plugins - -```ts -import { - configServiceRef, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; - -// export type ExamplePluginOptions = { exampleOption: boolean }; -export const examplePlugin = createBackendPlugin({ - // unique id for the plugin - id: 'example', - // It's possible to provide options to the plugin - // register(env, options: ExamplePluginOptions) { - register(env) { - env.registerInit({ - deps: { - logger: loggerServiceRef, - }, - // logger is provided by the backend based on the dependency on loggerServiceRef above. - async init({ logger }) { - logger.info('Hello from example plugin'); - }, - }); - }, -}); -``` - -The plugin can then be installed to the backend using - -```ts -backend.add(examplePlugin()); -// Options can be passed to the plugin -// backend.add(examplePlugin({ exampleOption: true})); -``` - -## Writing Modules - -Some facts about modules - -- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin. -- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. -- A module is always initialized before the plugin it extends. - -A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. - -Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: - -```ts -import { createBackendModule } from '@backstage/backend-plugin-api'; -import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; -import { MyCustomProcessor } from './processor'; - -export const exampleCustomProcessorCatalogModule = createBackendModule({ - moduleId: 'exampleCustomProcessor', - pluginId: 'catalog', - register(env) { - env.registerInit({ - deps: { - catalog: catalogProcessingExtensionPoint, - }, - async init({ catalog }) { - catalog.addProcessor(new MyCustomProcessor()); - }, - }); - }, -}); -``` - -### Extension Points - -Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. - -#### Defining an Extension Point - -```ts -import { createExtensionPoint } from '@backstage/backend-plugin-api'; - -export interface ScaffolderActionsExtensionPoint { - addAction(action: ScaffolderAction): void; -} - -export const ScaffolderActionsExtensionPoint = - createExtensionPoint({ - id: 'scaffolder.actions', - }); -``` - -#### Registering an Extension Point - -Extension points are registered by a plugin and extended by modules. - ## Testing Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. From d05e1841ce5d22bb535d0ef3e985830471ae2dc5 Mon Sep 17 00:00:00 2001 From: Pedro Cardona <1724279+atoko@users.noreply.github.com> Date: Sun, 9 Oct 2022 19:42:27 -0400 Subject: [PATCH 150/221] Adds gitea to supported set of SCM integrations Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> GiteaUrlReader readUrl implementation. Currently relies on content being base64 encoded Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Prettify changed files Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Changeset for gitea integration Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Include gitea in Vale vocabulary, run Lint / Prettier Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Update api-reports Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Lint packages/integration/* Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Fix getGiteaFileContentsUrl, split changesets The previous file contents url code was missing a segment, (/branch), and was causing the loader to 404. (The intended functionality is to use the same URL you would use to view the file in gitea) Changesets for the integration and backend-common packages were split This commit also adds documentation relevant to the gitea integration Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> --- .changeset/fresh-cooks-sing.md | 5 + .changeset/fresh-weeks-share.md | 5 + .github/vale/Vocab/Backstage/accept.txt | 2 + docs/integrations/gitea/locations.md | 38 ++++ packages/backend-common/api-report.md | 18 ++ .../src/reading/GiteaUrlReader.test.ts | 204 ++++++++++++++++++ .../src/reading/GiteaUrlReader.ts | 125 +++++++++++ .../backend-common/src/reading/UrlReaders.ts | 2 + packages/backend-common/src/reading/index.ts | 1 + packages/integration/api-report.md | 49 +++++ packages/integration/config.d.ts | 26 +++ .../integration/src/ScmIntegrations.test.ts | 10 + packages/integration/src/ScmIntegrations.ts | 7 + .../src/gitea/GiteaIntegration.test.ts | 128 +++++++++++ .../integration/src/gitea/GiteaIntegration.ts | 58 +++++ packages/integration/src/gitea/config.test.ts | 108 ++++++++++ packages/integration/src/gitea/config.ts | 79 +++++++ packages/integration/src/gitea/core.test.ts | 93 ++++++++ packages/integration/src/gitea/core.ts | 106 +++++++++ packages/integration/src/gitea/index.ts | 19 ++ packages/integration/src/index.ts | 1 + packages/integration/src/registry.ts | 3 +- 22 files changed, 1086 insertions(+), 1 deletion(-) create mode 100644 .changeset/fresh-cooks-sing.md create mode 100644 .changeset/fresh-weeks-share.md create mode 100644 docs/integrations/gitea/locations.md create mode 100644 packages/backend-common/src/reading/GiteaUrlReader.test.ts create mode 100644 packages/backend-common/src/reading/GiteaUrlReader.ts create mode 100644 packages/integration/src/gitea/GiteaIntegration.test.ts create mode 100644 packages/integration/src/gitea/GiteaIntegration.ts create mode 100644 packages/integration/src/gitea/config.test.ts create mode 100644 packages/integration/src/gitea/config.ts create mode 100644 packages/integration/src/gitea/core.test.ts create mode 100644 packages/integration/src/gitea/core.ts create mode 100644 packages/integration/src/gitea/index.ts diff --git a/.changeset/fresh-cooks-sing.md b/.changeset/fresh-cooks-sing.md new file mode 100644 index 0000000000..31f5ff0f22 --- /dev/null +++ b/.changeset/fresh-cooks-sing.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +This patch brings Gitea as a valid integration: target, via the ScmIntegration interface. It adds gitea to the relevant static properties (get integration by name, get integration by type) for plugins to be able to reference the same Gitea server. diff --git a/.changeset/fresh-weeks-share.md b/.changeset/fresh-weeks-share.md new file mode 100644 index 0000000000..aced6e910c --- /dev/null +++ b/.changeset/fresh-weeks-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +This patch adds GiteaURLReader to the available classes. It currently only reads single files via gitea's public repos api diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index 5ffbc95283..d21d0f9f25 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -121,6 +121,8 @@ github Gitiles gitlab GitLab +gitea +Gitea Gource Grafana graphql diff --git a/docs/integrations/gitea/locations.md b/docs/integrations/gitea/locations.md new file mode 100644 index 0000000000..c58703da3a --- /dev/null +++ b/docs/integrations/gitea/locations.md @@ -0,0 +1,38 @@ +--- +id: locations +title: Gitea Locations +sidebar_label: Locations +description: Integrating source code stored in Gitea into the Backstage catalog +--- + +The Gitea integration supports loading catalog entities from a hosted repository. Entities can be added to +[static catalog configuration](../../features/software-catalog/configuration.md), +registered with the +[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) +plugin. + +## Configuration + +To use this integration, add configuration to your root `app-config.yaml`: + +```yaml +integrations: + gitea: + - host: gitea.example.com + password: ${GITEA_TOKEN} + - host: gitea.example.com + username: ${GITEA_USERNAME} + password: ${GITEA_PASSWORD} +``` + +Directly under the `gitea` key is a list of provider configurations, where you +can list the Gitea instances you want to be able to fetch +data from. Each entry is a structure with up to four elements: + +- `host`: The host of the gitea instance that you want to match on. +- `baseUrl` (optional): Needed if the Gitea instance is not reachable at + the base of the `host` option (e.g. `https://git.company.com/gitea`). This is the address that you would open in a browser. +- `username` (optional): The gitea username to use in API requests. +- `password` (optional): The password or api token to authenticate with. + +You may supply only the `password` field, if authenticating via API access tokens (generated in Settings > Applications). diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 687139a023..e8dbd8d150 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -21,6 +21,7 @@ import { Duration } from 'luxon'; import { ErrorRequestHandler } from 'express'; import express from 'express'; import { GerritIntegration } from '@backstage/integration'; +import { GiteaIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GithubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; @@ -406,6 +407,23 @@ export class Git { resolveRef(options: { dir: string; ref: string }): Promise; } +// @public +export class GiteaUrlReader implements UrlReader { + constructor(integration: GiteaIntegration); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(): Promise; + // (undocumented) + readUrl(url: string, options?: ReadUrlOptions): Promise; + // (undocumented) + search(): Promise; + // (undocumented) + toString(): string; +} + // @public export class GithubUrlReader implements UrlReader { constructor( diff --git a/packages/backend-common/src/reading/GiteaUrlReader.test.ts b/packages/backend-common/src/reading/GiteaUrlReader.test.ts new file mode 100644 index 0000000000..6c8722d2e5 --- /dev/null +++ b/packages/backend-common/src/reading/GiteaUrlReader.test.ts @@ -0,0 +1,204 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { GiteaIntegration, readGiteaConfig } from '@backstage/integration'; +import { JsonObject } from '@backstage/types'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { getVoidLogger } from '../logging'; +import { UrlReaderPredicateTuple } from './types'; +import { DefaultReadTreeResponseFactory } from './tree'; +import getRawBody from 'raw-body'; +import { GiteaUrlReader } from './GiteaUrlReader'; +import { NotFoundError } from '@backstage/errors'; + +const treeResponseFactory = DefaultReadTreeResponseFactory.create({ + config: new ConfigReader({}), +}); + +jest.mock('../scm', () => ({ + Git: { + fromAuth: () => ({ + clone: jest.fn(() => Promise.resolve({})), + }), + }, +})); + +const giteaProcessor = new GiteaUrlReader( + new GiteaIntegration( + readGiteaConfig( + new ConfigReader({ + host: 'gitea.com', + }), + ), + ), +); + +const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { + return GiteaUrlReader.factory({ + config: new ConfigReader(config), + logger: getVoidLogger(), + treeResponseFactory, + }); +}; + +describe('GiteaUrlReader', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + afterAll(() => { + jest.clearAllMocks(); + }); + + describe('reader factory', () => { + it('creates a reader.', () => { + const readers = createReader({ + integrations: { + gitea: [{ host: 'gitea.com' }], + }, + }); + expect(readers).toHaveLength(1); + }); + + it('should not create a default entry.', () => { + const readers = createReader({ + integrations: {}, + }); + expect(readers).toHaveLength(0); + }); + }); + + describe('predicates', () => { + it('returns true for the configured host', () => { + const readers = createReader({ + integrations: { + gitea: [{ host: 'gitea.com' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://gitea.com/path'))).toBe(true); + }); + + it('returns false for a different host.', () => { + const readers = createReader({ + integrations: { + gitea: [{ host: 'gitea.com' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://github.com/path'))).toBe(false); + }); + }); + + describe('readUrl', () => { + const responseBuffer = Buffer.from('Apache License'); + const giteaApiResponse = (content: any) => { + return JSON.stringify({ + encoding: 'base64', + content: Buffer.from(content).toString('base64'), + }); + }; + + it('should be able to read file contents as buffer', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (req, res, ctx) => { + // Test utils prefers matching URL directly but it is part of Gitea's API + if (req.url.searchParams.get('ref') === 'branch2') { + return res( + ctx.status(200), + ctx.body(giteaApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ); + const buffer = await result.buffer(); + expect(buffer.toString()).toBe(responseBuffer.toString()); + }); + + it('should be able to read file contents as stream', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (req, res, ctx) => { + if (req.url.searchParams.get('ref') === 'branch2') { + return res( + ctx.status(200), + ctx.body(giteaApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ); + const fromStream = await getRawBody(result.stream!()); + expect(fromStream.toString()).toBe(responseBuffer.toString()); + }); + + it('should raise NotFoundError on 404.', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (_, res, ctx) => { + return res(ctx.status(404, 'File not found.')); + }, + ), + ); + + await expect( + giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ), + ).rejects.toThrow(NotFoundError); + }); + + it('should throw an error on non 404 errors.', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (_, res, ctx) => { + return res(ctx.status(500, 'Error!!!')); + }, + ), + ); + + await expect( + giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ), + ).rejects.toThrow( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE could not be read as https://gitea.com/api/v1/repos/owner/project/contents/LICENSE?ref=branch2, 500 Error!!!', + ); + }); + }); +}); diff --git a/packages/backend-common/src/reading/GiteaUrlReader.ts b/packages/backend-common/src/reading/GiteaUrlReader.ts new file mode 100644 index 0000000000..da6b791949 --- /dev/null +++ b/packages/backend-common/src/reading/GiteaUrlReader.ts @@ -0,0 +1,125 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + getGiteaRequestOptions, + getGiteaFileContentsUrl, + GiteaIntegration, + ScmIntegrations, +} from '@backstage/integration'; +import { ReadUrlOptions, ReadUrlResponse } from './types'; +import { + ReaderFactory, + ReadTreeResponse, + SearchResponse, + UrlReader, +} from './types'; +import fetch, { Response } from 'node-fetch'; +import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; +import { + AuthenticationError, + NotFoundError, + NotModifiedError, +} from '@backstage/errors'; +import { Readable } from 'stream'; + +/** + * Implements a {@link UrlReader} for the Gitea v1 api. + * + * @public + */ +export class GiteaUrlReader implements UrlReader { + static factory: ReaderFactory = ({ config }) => { + return ScmIntegrations.fromConfig(config) + .gitea.list() + .map(integration => { + const reader = new GiteaUrlReader(integration); + const predicate = (url: URL) => { + return url.host === integration.config.host; + }; + return { reader, predicate }; + }); + }; + + constructor(private readonly integration: GiteaIntegration) {} + + async read(url: string): Promise { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { + let response: Response; + const blobUrl = getGiteaFileContentsUrl(this.integration.config, url); + + try { + response = await fetch(blobUrl, { + method: 'GET', + ...getGiteaRequestOptions(this.integration.config), + signal: options?.signal as any, + }); + } catch (e) { + throw new Error(`Unable to read ${blobUrl}, ${e}`); + } + + if (response.ok) { + // Gitea returns an object with the file contents encoded, not the file itself + const { encoding, content } = await response.json(); + + if (encoding === 'base64') { + return ReadUrlResponseFactory.fromReadable( + Readable.from(Buffer.from(content, 'base64')), + { + etag: response.headers.get('ETag') ?? undefined, + }, + ); + } + + throw new Error(`Unknown encoding: ${encoding}`); + } + + const message = `${url} could not be read as ${blobUrl}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + throw new NotFoundError(message); + } + + if (response.status === 304) { + throw new NotModifiedError(); + } + + if (response.status === 403) { + throw new AuthenticationError(); + } + + throw new Error(message); + } + + readTree(): Promise { + throw new Error('GiteaUrlReader readTree not implemented.'); + } + search(): Promise { + throw new Error('GiteaUrlReader search not implemented.'); + } + + toString() { + const { host } = this.integration.config; + return `gitea{host=${host},authed=${Boolean( + this.integration.config.password, + )}}`; + } +} diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index 1e9d7ba5f4..9fb403e7e9 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -29,6 +29,7 @@ import { DefaultReadTreeResponseFactory } from './tree'; import { FetchUrlReader } from './FetchUrlReader'; import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; import { AwsS3UrlReader } from './AwsS3UrlReader'; +import { GiteaUrlReader } from './GiteaUrlReader'; /** * Creation options for {@link UrlReaders}. @@ -89,6 +90,7 @@ export class UrlReaders { BitbucketUrlReader.factory, GerritUrlReader.factory, GithubUrlReader.factory, + GiteaUrlReader.factory, GitlabUrlReader.factory, GoogleGcsUrlReader.factory, AwsS3UrlReader.factory, diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index 9ee86ef3f2..21e82da9b5 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -21,6 +21,7 @@ export { BitbucketServerUrlReader } from './BitbucketServerUrlReader'; export { GerritUrlReader } from './GerritUrlReader'; export { GithubUrlReader } from './GithubUrlReader'; export { GitlabUrlReader } from './GitlabUrlReader'; +export { GiteaUrlReader } from './GiteaUrlReader'; export { AwsS3UrlReader } from './AwsS3UrlReader'; export { FetchUrlReader } from './FetchUrlReader'; export { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 0f24ded066..d45c8ca06b 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -323,6 +323,17 @@ export function getGerritRequestOptions(config: GerritIntegrationConfig): { headers?: Record; }; +// @public +export function getGiteaFileContentsUrl( + config: GiteaIntegrationConfig, + url: string, +): string; + +// @public +export function getGiteaRequestOptions(config: GiteaIntegrationConfig): { + headers?: Record; +}; + // @public @deprecated (undocumented) export const getGitHubFileFetchUrl: typeof getGithubFileFetchUrl; @@ -357,6 +368,35 @@ export function getGitLabRequestOptions(config: GitLabIntegrationConfig): { headers: Record; }; +// @public +export class GiteaIntegration implements ScmIntegration { + constructor(config: GiteaIntegrationConfig); + // (undocumented) + readonly config: GiteaIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory; + // (undocumented) + resolveEditUrl(url: string): string; + // (undocumented) + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string; + // (undocumented) + get title(): string; + // (undocumented) + get type(): string; +} + +// @public +export type GiteaIntegrationConfig = { + host: string; + baseUrl?: string; + username?: string; + password?: string; +}; + // @public export type GithubAppConfig = { appId: number; @@ -488,6 +528,8 @@ export interface IntegrationsByType { // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) + gitea: ScmIntegrationsGroup; + // (undocumented) github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; @@ -566,6 +608,9 @@ export function readGerritIntegrationConfigs( configs: Config[], ): GerritIntegrationConfig[]; +// @public +export function readGiteaConfig(config: Config): GiteaIntegrationConfig; + // @public @deprecated (undocumented) export const readGitHubIntegrationConfig: typeof readGithubIntegrationConfig; @@ -640,6 +685,8 @@ export interface ScmIntegrationRegistry // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) + gitea: ScmIntegrationsGroup; + // (undocumented) github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; @@ -673,6 +720,8 @@ export class ScmIntegrations implements ScmIntegrationRegistry { // (undocumented) get gerrit(): ScmIntegrationsGroup; // (undocumented) + get gitea(): ScmIntegrationsGroup; + // (undocumented) get github(): ScmIntegrationsGroup; // (undocumented) get gitlab(): ScmIntegrationsGroup; diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index dc68643bdd..49a121aff3 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -281,5 +281,31 @@ export interface Config { */ externalId?: string; }>; + + /** Integration configuration for Gitea */ + gitea?: Array<{ + /** + * The hostname of the given Gitea instance + * @visibility frontend + */ + host: string; + /** + * The base url for the Gitea instance. + * @visibility frontend + */ + baseUrl?: string; + + /** + * The username to use for authenticated requests. + * @visibility secret + */ + username?: string; + /** + * Gitea password used to authenticate requests. This can be either a password + * or a generated access token. + * @visibility secret + */ + password?: string; + }>; }; } diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index f5fd480609..4c64b2f0de 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -35,6 +35,7 @@ import { GitLabIntegrationConfig } from './gitlab'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { basicIntegrations } from './helpers'; import { ScmIntegrations } from './ScmIntegrations'; +import { GiteaIntegration, GiteaIntegrationConfig } from './gitea'; describe('ScmIntegrations', () => { const awsS3 = new AwsS3Integration({ @@ -69,6 +70,10 @@ describe('ScmIntegrations', () => { host: 'gitlab.local', } as GitLabIntegrationConfig); + const gitea = new GiteaIntegration({ + host: 'gitea.local', + } as GiteaIntegrationConfig); + const i = new ScmIntegrations({ awsS3: basicIntegrations([awsS3], item => item.config.host), azure: basicIntegrations([azure], item => item.config.host), @@ -81,6 +86,7 @@ describe('ScmIntegrations', () => { gerrit: basicIntegrations([gerrit], item => item.config.host), github: basicIntegrations([github], item => item.config.host), gitlab: basicIntegrations([gitlab], item => item.config.host), + gitea: basicIntegrations([gitea], item => item.config.host), }); it('can get the specifics', () => { @@ -96,6 +102,7 @@ describe('ScmIntegrations', () => { expect(i.gerrit.byUrl('https://gerrit.local')).toBe(gerrit); expect(i.github.byUrl('https://github.local')).toBe(github); expect(i.gitlab.byUrl('https://gitlab.local')).toBe(gitlab); + expect(i.gitea.byUrl('https://gitea.local')).toBe(gitea); }); it('can list', () => { @@ -109,6 +116,7 @@ describe('ScmIntegrations', () => { gerrit, github, gitlab, + gitea, ]), ); }); @@ -122,6 +130,7 @@ describe('ScmIntegrations', () => { expect(i.byUrl('https://gerrit.local')).toBe(gerrit); expect(i.byUrl('https://github.local')).toBe(github); expect(i.byUrl('https://gitlab.local')).toBe(gitlab); + expect(i.byUrl('https://gitea.local')).toBe(gitea); expect(i.byHost('awss3.local')).toBe(awsS3); expect(i.byHost('azure.local')).toBe(azure); @@ -131,6 +140,7 @@ describe('ScmIntegrations', () => { expect(i.byHost('gerrit.local')).toBe(gerrit); expect(i.byHost('github.local')).toBe(github); expect(i.byHost('gitlab.local')).toBe(gitlab); + expect(i.byHost('gitea.local')).toBe(gitea); }); it('can resolveUrl using fallback', () => { diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index f5decebc66..24c28ce25e 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -26,6 +26,7 @@ import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { defaultScmResolveUrl } from './helpers'; import { ScmIntegration, ScmIntegrationsGroup } from './types'; import { ScmIntegrationRegistry } from './registry'; +import { GiteaIntegration } from './gitea'; /** * The set of supported integrations. @@ -44,6 +45,7 @@ export interface IntegrationsByType { gerrit: ScmIntegrationsGroup; github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; + gitea: ScmIntegrationsGroup; } /** @@ -64,6 +66,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { gerrit: GerritIntegration.factory({ config }), github: GithubIntegration.factory({ config }), gitlab: GitLabIntegration.factory({ config }), + gitea: GiteaIntegration.factory({ config }), }); } @@ -106,6 +109,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gitlab; } + get gitea(): ScmIntegrationsGroup { + return this.byType.gitea; + } + list(): ScmIntegration[] { return Object.values(this.byType).flatMap( i => i.list() as ScmIntegration[], diff --git a/packages/integration/src/gitea/GiteaIntegration.test.ts b/packages/integration/src/gitea/GiteaIntegration.test.ts new file mode 100644 index 0000000000..1c8169f417 --- /dev/null +++ b/packages/integration/src/gitea/GiteaIntegration.test.ts @@ -0,0 +1,128 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { GiteaIntegration } from './GiteaIntegration'; + +describe('GiteaIntegration', () => { + it('has a working factory', () => { + const integrations = GiteaIntegration.factory({ + config: new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.example.com', + username: 'git', + baseUrl: 'https://gitea.example.com/route', + password: '1234', + }, + ], + }, + }), + }); + expect(integrations.list().length).toBe(1); + expect(integrations.list()[0].config.host).toBe('gitea.example.com'); + expect(integrations.list()[0].config.baseUrl).toBe( + 'https://gitea.example.com/route', + ); + }); + + it('returns the basics', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + expect(integration.type).toBe('gitea'); + expect(integration.title).toBe('gitea.example.com'); + }); + + describe('resolveUrl', () => { + it('works for valid urls, ignoring line number', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: 'https://gitea.example.com/catalog-info.yaml', + base: 'https://gitea.example.com/catalog-info.yaml', + lineNumber: 9, + }), + ).toBe('https://gitea.example.com/catalog-info.yaml'); + }); + + it('handles line numbers', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: '', + base: 'https://gitea.example.com/catalog-info.yaml#4', + lineNumber: 9, + }), + ).toBe('https://gitea.example.com/catalog-info.yaml#L9'); + }); + }); + + describe('resolves with a relative url', () => { + it('works for valid urls', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: './skeleton', + base: 'https://gitea.example.com/git/plugins/repo/+/refs/heads/master/template.yaml', + }), + ).toBe( + 'https://gitea.example.com/git/plugins/repo/+/refs/heads/master/skeleton', + ); + }); + }); + + describe('resolves with an absolute url', () => { + it('works for valid urls', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: '/catalog-info.yaml', + base: 'https://gitea.example.com/git/repo/+/refs/heads/master/', + }), + ).toBe( + 'https://gitea.example.com/git/repo/+/refs/heads/master/catalog-info.yaml', + ); + }); + }); + + it('resolve edit URL', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveEditUrl( + 'https://gitea.example.com/owner/repo/src/branch/branch_name/path/to/c.yaml', + ), + ).toBe( + 'https://gitea.example.com/owner/repo/_edit/branch_name/path/to/c.yaml', + ); + }); +}); diff --git a/packages/integration/src/gitea/GiteaIntegration.ts b/packages/integration/src/gitea/GiteaIntegration.ts new file mode 100644 index 0000000000..25144dcca9 --- /dev/null +++ b/packages/integration/src/gitea/GiteaIntegration.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; +import { ScmIntegration, ScmIntegrationsFactory } from '../types'; +import { GiteaIntegrationConfig, readGiteaConfig } from './config'; +import { getGiteaEditContentsUrl } from './core'; + +/** + * A Gitea based integration. + * + * @public + */ +export class GiteaIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory = ({ config }) => { + const configs = config.getOptionalConfigArray('integrations.gitea') ?? []; + const giteaConfigs = configs.map(c => readGiteaConfig(c)); + + return basicIntegrations( + giteaConfigs.map(c => new GiteaIntegration(c)), + (gitea: GiteaIntegration) => gitea.config.host, + ); + }; + + constructor(readonly config: GiteaIntegrationConfig) {} + + get type(): string { + return 'gitea'; + } + + get title(): string { + return this.config.host; + } + + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string { + return defaultScmResolveUrl(options); + } + + resolveEditUrl(url: string): string { + return getGiteaEditContentsUrl(this.config, url); + } +} diff --git a/packages/integration/src/gitea/config.test.ts b/packages/integration/src/gitea/config.test.ts new file mode 100644 index 0000000000..cdcc7e3f3e --- /dev/null +++ b/packages/integration/src/gitea/config.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config, ConfigReader } from '@backstage/config'; +import { loadConfigSchema } from '@backstage/config-loader'; +import { GiteaIntegrationConfig, readGiteaConfig } from './config'; + +describe('readGiteaConfig', () => { + function buildConfig(data: Partial): Config { + return new ConfigReader(data); + } + + async function buildFrontendConfig( + data: Partial, + ): Promise { + const fullSchema = await loadConfigSchema({ + dependencies: ['@backstage/integration'], + }); + const serializedSchema = fullSchema.serialize() as { + schemas: { value: { properties?: { integrations?: object } } }[]; + }; + const schema = await loadConfigSchema({ + serialized: { + ...serializedSchema, // only include schemas that apply to integrations + schemas: serializedSchema.schemas.filter( + s => s.value?.properties?.integrations, + ), + }, + }); + const processed = schema.process( + [{ data: { integrations: { gitea: [data] } }, context: 'app' }], + { visibility: ['frontend'] }, + ); + return new ConfigReader((processed[0].data as any).integrations.gitea[0]); + } + + it('reads all values', () => { + const output = readGiteaConfig( + buildConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + password: 'p', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + password: 'p', + }); + }); + + it('can create a default value if the API base URL is missing', () => { + const output = readGiteaConfig( + buildConfig({ + host: 'a.com', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com', + username: undefined, + password: undefined, + }); + }); + + it('rejects funky configs', () => { + const valid: any = { + host: 'a.com', + }; + expect(() => readGiteaConfig(buildConfig({ ...valid, host: 2 }))).toThrow( + /host/, + ); + expect(() => + readGiteaConfig(buildConfig({ ...valid, baseUrl: 2 })), + ).toThrow(/baseUrl/); + }); + + it('works on the frontend', async () => { + expect( + readGiteaConfig( + await buildFrontendConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route', + username: 'u', + password: 'p', + }), + ), + ).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route', + }); + }); +}); diff --git a/packages/integration/src/gitea/config.ts b/packages/integration/src/gitea/config.ts new file mode 100644 index 0000000000..99c5b5c1f7 --- /dev/null +++ b/packages/integration/src/gitea/config.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { trimEnd } from 'lodash'; +import { isValidHost } from '../helpers'; + +/** + * The configuration for a single Gitea integration. + * + * @public + */ +export type GiteaIntegrationConfig = { + /** + * The host of the target that this matches on, e.g. "gitea.website.com" + */ + host: string; + /** + * The optional base URL of the Gitea instance. It is assumed that https + * is used and that the base path is "/" on the host. If that is not the + * case set the complete base url to the gitea instance, e.g. + * "https://gitea.website.com/". This is the url that you would open + * in a browser. + */ + baseUrl?: string; + /** + * The username to use for requests to gitea. + */ + username?: string; + + /** + * The password or http token to use for authentication. + */ + password?: string; +}; + +/** + * Parses a location config block for use in GiteaIntegration + * + * @public + */ +export function readGiteaConfig(config: Config): GiteaIntegrationConfig { + const host = config.getString('host'); + let baseUrl = config.getOptionalString('baseUrl'); + const username = config.getOptionalString('username'); + const password = config.getOptionalString('password'); + + if (!isValidHost(host)) { + throw new Error( + `Invalid Gitea integration config, '${host}' is not a valid host`, + ); + } + + if (baseUrl) { + baseUrl = trimEnd(baseUrl, '/'); + } else { + baseUrl = `https://${host}`; + } + + return { + host, + baseUrl, + username, + password, + }; +} diff --git a/packages/integration/src/gitea/core.test.ts b/packages/integration/src/gitea/core.test.ts new file mode 100644 index 0000000000..520b0f549d --- /dev/null +++ b/packages/integration/src/gitea/core.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { GiteaIntegrationConfig } from './config'; +import { + getGiteaEditContentsUrl, + getGiteaFileContentsUrl, + getGiteaRequestOptions, +} from './core'; + +describe('gitea core', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + describe('getGiteaFileContentsUrl', () => { + it('can create an url from arguments', () => { + const config: GiteaIntegrationConfig = { + host: 'gitea.com', + }; + expect( + getGiteaFileContentsUrl( + config, + 'https://gitea.com/a/b/src/branch/branch_name/path/to/c.yaml', + ), + ).toEqual( + 'https://gitea.com/api/v1/repos/a/b/contents/path/to/c.yaml?ref=branch_name', + ); + }); + }); + + describe('getGiteaEditContentsUrl', () => { + it('can create an url from arguments', () => { + const config: GiteaIntegrationConfig = { + host: 'gitea.example.com', + }; + expect( + getGiteaEditContentsUrl( + config, + 'https://gitea.example.com/owner/repo/src/branch/branch_name/path/to/c.yaml', + ), + ).toEqual( + 'https://gitea.example.com/owner/repo/_edit/branch_name/path/to/c.yaml', + ); + }); + }); + + describe('getGerritRequestOptions', () => { + it('adds token header when only a password is specified', () => { + const authRequest: GiteaIntegrationConfig = { + host: 'gerrit.com', + password: 'P', + }; + const anonymousRequest: GiteaIntegrationConfig = { + host: 'gerrit.com', + }; + expect( + (getGiteaRequestOptions(authRequest).headers as any).Authorization, + ).toEqual('token P'); + expect(getGiteaRequestOptions(anonymousRequest).headers).toBeUndefined(); + }); + + it('adds basic auth when username and password are specified', () => { + const authRequest: GiteaIntegrationConfig = { + host: 'gerrit.com', + username: 'username', + password: 'P', + }; + + const basicAuthentication = `basic ${Buffer.from( + `${authRequest.username}:${authRequest.password}`, + ).toString('base64')}`; + + expect( + (getGiteaRequestOptions(authRequest).headers as any).Authorization, + ).toEqual(basicAuthentication); + }); + }); +}); diff --git a/packages/integration/src/gitea/core.ts b/packages/integration/src/gitea/core.ts new file mode 100644 index 0000000000..ee3fec12fc --- /dev/null +++ b/packages/integration/src/gitea/core.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { GiteaIntegrationConfig } from './config'; + +/** + * Given a URL pointing to a file, returns a URL + * for editing the contents of the data. + * + * @remarks + * + * Converts + * from: https://gitea.com/a/b/src/branchname/path/to/c.yaml + * or: https://gitea.com/a/b/_edit/branchname/path/to/c.yaml + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getGiteaEditContentsUrl( + config: GiteaIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [_blank, owner, name, _src, _branch, ref, ...path] = url + .replace(baseUrl, '') + .split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + return `${baseUrl}/${owner}/${name}/_edit/${ref}/${pathWithoutSlash}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Given a URL pointing to a file, returns an api URL + * for fetching the contents of the data. + * + * @remarks + * + * Converts + * from: https://gitea.com/a/b/src/branch/branchname/path/to/c.yaml + * to: https://gitea.com/api/v1/repos/a/b/contents/path/to/c.yaml?ref=branchname + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getGiteaFileContentsUrl( + config: GiteaIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [_blank, owner, name, _src, _branch, ref, ...path] = url + .replace(baseUrl, '') + .split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + + return `${baseUrl}/api/v1/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Return request headers for a Gitea provider. + * + * @param config - A Gitea provider config + * @public + */ +export function getGiteaRequestOptions(config: GiteaIntegrationConfig): { + headers?: Record; +} { + const headers: Record = {}; + const { username, password } = config; + + if (!password) { + return headers; + } + + if (username) { + headers.Authorization = `basic ${Buffer.from( + `${username}:${password}`, + ).toString('base64')}`; + } else { + headers.Authorization = `token ${password}`; + } + + return { + headers, + }; +} diff --git a/packages/integration/src/gitea/index.ts b/packages/integration/src/gitea/index.ts new file mode 100644 index 0000000000..6b951b1fb0 --- /dev/null +++ b/packages/integration/src/gitea/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { GiteaIntegration } from './GiteaIntegration'; +export { getGiteaRequestOptions, getGiteaFileContentsUrl } from './core'; +export { readGiteaConfig } from './config'; +export type { GiteaIntegrationConfig } from './config'; diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index 388b874275..5738da3da0 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -26,6 +26,7 @@ export * from './bitbucket'; export * from './bitbucketCloud'; export * from './bitbucketServer'; export * from './gerrit'; +export * from './gitea'; export * from './github'; export * from './gitlab'; export * from './googleGcs'; diff --git a/packages/integration/src/registry.ts b/packages/integration/src/registry.ts index debb819156..7058ca1b5c 100644 --- a/packages/integration/src/registry.ts +++ b/packages/integration/src/registry.ts @@ -23,6 +23,7 @@ import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerInt import { GerritIntegration } from './gerrit/GerritIntegration'; import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; +import { GiteaIntegration } from './gitea/GiteaIntegration'; /** * Holds all registered SCM integrations, of all types. @@ -42,7 +43,7 @@ export interface ScmIntegrationRegistry gerrit: ScmIntegrationsGroup; github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; - + gitea: ScmIntegrationsGroup; /** * Resolves an absolute or relative URL in relation to a base URL. * From 3449322ac2a653ba70fa7010ccb422c0d61a0e2f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:43:52 -0400 Subject: [PATCH 151/221] Apply suggestions from code review Co-authored-by: Phil Kuang Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 988086ff23..4d688289c9 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -14,7 +14,7 @@ You can find an example backend setup at https://github.com/backstage/backstage/ ## Overview -The new Backstage backend system is being built to help make it simpler to install backend plugins and keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/TODO). +The new Backstage backend system is being built to help make it simpler to install backend plugins and keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611). One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system: @@ -40,7 +40,7 @@ This section introduces the high-level building blocks upon which this new syste ### Backend -This is the backend instance itself, which you can think of at the unity of deployment. It does not have any functionality in itself, but is simply responsible for wiring things together. +This is the backend instance itself, which you can think of as the unit of deployment. It does not have any functionality in itself, but is simply responsible for wiring things together. It is up to you to decide how many different backends you want to deploy. You can have all features in a single one, or split things out into multiple smaller deployments. All depending on your need to scale and isolate individual features. @@ -64,7 +64,7 @@ Extension Points are also exported separately from the plugin instance itself, a ### Modules -Modules use the plugin Extension Points to add new features for plugins. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. Modules basically plugins for plugins. +Modules use the plugin Extension Points to add new features for plugins. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. Modules are basically plugins for plugins. Each module may only extend a single plugin, and the module must be deployed together with that plugin in the same backend instance. Modules may however only communicate with their plugin through its registered extension points. @@ -138,7 +138,7 @@ Some facts about modules - A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. - A module is always initialized before the plugin it extends. -A module depend on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. +A module depends on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: @@ -165,7 +165,7 @@ export const exampleCustomProcessorCatalogModule = createBackendModule({ ### Extension Points -Modules depend on extension points just as a regular dependency but specifying it in the `deps` section. +Modules depend on extension points just as a regular dependency by specifying it in the `deps` section. #### Defining an Extension Point @@ -189,7 +189,7 @@ Extension points are registered by a plugin and extended by modules. ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. -Services are declared using their _serviceRef_ in the `deps` section of plugin or module requiring them and are then available in the `init` method of the plugin or module. +Services are declared using their _serviceRef_ in the `deps` section of the plugin or module requiring them and are then available in the `init` method of the plugin or module. ### Service References @@ -241,7 +241,7 @@ export const exampleServiceRef = createServiceRef({ ### Overriding Services -In this example replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service. +In this example we replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service. ```ts import { From 398df81469a9a4edd11546d247c68e14179465a6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:45:11 -0400 Subject: [PATCH 152/221] docs/new-backend-system: fix for plugin meta service being unused in example Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 4d688289c9..478f9b8609 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -232,7 +232,7 @@ export const exampleServiceRef = createServiceRef({ return async ({ plugin }) => { // This block will be executed once for every plugin that depends on this service logger.info('Initializing example service plugin instance'); - return new ExampleImpl({ logger }); + return new ExampleImpl({ logger, plugin }); }; }, }), From 7b90049030df19b1f76cae6148556be3787d2146 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:46:41 -0400 Subject: [PATCH 153/221] microsite/pages: prettify links page Signed-off-by: Patrik Oldsberg --- microsite/pages/en/link.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/pages/en/link.js b/microsite/pages/en/link.js index ccda70fa54..fadb4f50d0 100644 --- a/microsite/pages/en/link.js +++ b/microsite/pages/en/link.js @@ -6,7 +6,7 @@ const redirects = { 'bind-routes': '/docs/plugins/composability#binding-external-routes-in-the-app', 'scm-auth': '/docs/auth/#scaffolder-configuration-software-templates', - 'backend-system': '/docs/plugins/new-backend-system' + 'backend-system': '/docs/plugins/new-backend-system', }; const fallback = '/docs'; From 3a9133e3ad6f292b51dd0f8f835b0204b7593f30 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 23 Oct 2022 22:57:24 -0400 Subject: [PATCH 154/221] microsite/pages: vale tweak Signed-off-by: Patrik Oldsberg --- docs/plugins/new-backend-system.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index 478f9b8609..e5c318d5c2 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -189,7 +189,7 @@ Extension points are registered by a plugin and extended by modules. ## Backend Services The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. -Services are declared using their _serviceRef_ in the `deps` section of the plugin or module requiring them and are then available in the `init` method of the plugin or module. +Service dependencies are declared using their `ServiceRef`s in the `deps` section of the plugin or module, and the implementations are then forwarded to the `init` method of the plugin or module. ### Service References From 8b7d96e6b913e256ce6eefd78eb2d92e071500dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 Oct 2022 09:19:17 +0200 Subject: [PATCH 155/221] small correction in the permissions docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/permissions/custom-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 9407dc407a..d82836b9ae 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -11,7 +11,7 @@ For some use cases, you may want to define custom [rules](./concepts.md#resource Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule in `packages/backend/src/plugins/permission.ts`, but you can put it anywhere that's accessible by your `backend` package. ```typescript -import type { Entity } from '@backstage/plugin-catalog-model'; +import type { Entity } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; import { createConditionFactory } from '@backstage/plugin-permission-node'; import { z } from 'zod'; From 59bfef78a3f95e281cfe9740473683883faf5bc3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 02:15:55 +0000 Subject: [PATCH 156/221] Update dependency @keyv/redis to v2.5.2 Signed-off-by: Renovate Bot --- packages/backend-common/src/cache/NoStore.ts | 6 ++- yarn.lock | 54 +++++++------------- 2 files changed, 22 insertions(+), 38 deletions(-) diff --git a/packages/backend-common/src/cache/NoStore.ts b/packages/backend-common/src/cache/NoStore.ts index 3bb28afc71..85f789d21e 100644 --- a/packages/backend-common/src/cache/NoStore.ts +++ b/packages/backend-common/src/cache/NoStore.ts @@ -14,11 +14,13 @@ * limitations under the License. */ +import { Store } from 'keyv'; + /** * Storage class compatible with Keyv which always results in a no-op. This is * used when no cache store is configured in a Backstage backend instance. */ -export class NoStore extends Map { +export class NoStore implements Store { clear(): void { return; } @@ -28,7 +30,7 @@ export class NoStore extends Map { } get(_key: string) { - return; + return undefined; } has(_key: string): boolean { diff --git a/yarn.lock b/yarn.lock index a661d39384..3e8c7ca314 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10271,11 +10271,11 @@ __metadata: linkType: hard "@keyv/redis@npm:^2.2.3": - version: 2.3.8 - resolution: "@keyv/redis@npm:2.3.8" + version: 2.5.2 + resolution: "@keyv/redis@npm:2.5.2" dependencies: - ioredis: ^5.1.0 - checksum: 7927d870094161db5bf90677008f6a584235162a0981f875a8616d244d2ba1da525858f656f434aea2a923fb39c5ce0cf88fd9720061e5f025c9db59146e6f48 + ioredis: ^5.2.3 + checksum: 2439e5097c6d4bf14e316b7a19d532287d43c5860f0614569f125a3c0a0a8c322610ed26b86b941b9aa9243ebf1e265c1d3f99a43da42d2150d02fc8fd2d94dd languageName: node linkType: hard @@ -13771,13 +13771,6 @@ __metadata: languageName: node linkType: hard -"@types/json-buffer@npm:~3.0.0": - version: 3.0.0 - resolution: "@types/json-buffer@npm:3.0.0" - checksum: 6b0a371dd603f0eec9d00874574bae195382570e832560dadf2193ee0d1062b8e0694bbae9798bc758632361c227b1e3b19e3bd914043b498640470a2da38b77 - languageName: node - linkType: hard - "@types/json-schema-merge-allof@npm:^0.6.0": version: 0.6.1 resolution: "@types/json-schema-merge-allof@npm:0.6.1" @@ -13827,11 +13820,11 @@ __metadata: linkType: hard "@types/keyv@npm:*": - version: 3.1.1 - resolution: "@types/keyv@npm:3.1.1" + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" dependencies: "@types/node": "*" - checksum: ee0d098693bf4af44be756eed02daf95f5d0fd4b5b02da952a5952e08842baddf6a986a9ea5f9e460729782f1a0a47848c892ad96ea188b66a363feb49a1536f + checksum: e009a2bfb50e90ca9b7c6e8f648f8464067271fd99116f881073fa6fa76dc8d0133181dd65e6614d5fb1220d671d67b0124aef7d97dc02d7e342ab143a47779d languageName: node linkType: hard @@ -18485,16 +18478,6 @@ __metadata: languageName: node linkType: hard -"compress-brotli@npm:^1.3.8": - version: 1.3.8 - resolution: "compress-brotli@npm:1.3.8" - dependencies: - "@types/json-buffer": ~3.0.0 - json-buffer: ~3.0.1 - checksum: de7589d692d40eb362f6c91070b5e51bc10b05a89eabb4a7c76c1aa21b625756f8c101c6999e4df0c4dc6199c5ca2e1353573bfdcca5615810f27485394162a5 - languageName: node - linkType: hard - "compress-commons@npm:^4.1.0": version: 4.1.0 resolution: "compress-commons@npm:4.1.0" @@ -25131,9 +25114,9 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.1.0": - version: 5.1.0 - resolution: "ioredis@npm:5.1.0" +"ioredis@npm:^5.2.3": + version: 5.2.3 + resolution: "ioredis@npm:5.2.3" dependencies: "@ioredis/commands": ^1.1.1 cluster-key-slot: ^1.1.0 @@ -25144,7 +25127,7 @@ __metadata: redis-errors: ^1.2.0 redis-parser: ^3.0.0 standard-as-callback: ^2.1.0 - checksum: 7b1c137836ee136a634926df4ec68cc2393772a32ecda8e30dd305d7b9182af02d600456e661f96a6ceada17560ce7b6458948fd09b8adc277a1d287325777dd + checksum: 2cb7f0f4217e6774accad3620af1b7114722721c1d1824be2c9f0c2a77ab9629f2e0848d18b1a7208bc37796ae1207cb3e0898fce61900cfe797da0382724ad1 languageName: node linkType: hard @@ -27038,7 +27021,7 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.1, json-buffer@npm:^3.0.1, json-buffer@npm:~3.0.1": +"json-buffer@npm:3.0.1, json-buffer@npm:^3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" checksum: 9026b03edc2847eefa2e37646c579300a1f3a4586cfb62bf857832b60c852042d0d6ae55d1afb8926163fa54c2b01d83ae24705f34990348bdac6273a29d4581 @@ -27091,15 +27074,15 @@ __metadata: linkType: hard "json-schema-library@npm:^7.0.0": - version: 7.0.0 - resolution: "json-schema-library@npm:7.0.0" + version: 7.2.0 + resolution: "json-schema-library@npm:7.2.0" dependencies: deepmerge: ^4.2.2 fast-deep-equal: ^3.1.3 gson-pointer: ^4.1.1 gson-query: ^5.1.0 valid-url: ^1.0.9 - checksum: 4e6b129b836c84d8080d46a02fef4886ffec8114182ea015a80536f6b8609c5a11354a5e2a98a15b0734bfb25ec60b6a0f1e7230172396d026c679cb385b8f21 + checksum: 6c5086899c2f89c1b488145f1067f00a5901729e1d13513bb48551971d45be5dc60810fa4bcec65345e4d1520aa08743348dabdf59f74122b5b95c840b4edfda languageName: node linkType: hard @@ -27553,12 +27536,11 @@ __metadata: linkType: hard "keyv@npm:^4.0.0, keyv@npm:^4.0.3": - version: 4.3.2 - resolution: "keyv@npm:4.3.2" + version: 4.5.0 + resolution: "keyv@npm:4.5.0" dependencies: - compress-brotli: ^1.3.8 json-buffer: 3.0.1 - checksum: 237952f5faa2ed08da36677d7a3faae48b7e3c305264698cbf4480443f293a2f0c6c63c1d05f5cd4a842ee864dbb395745e6636fecd07489565776a22de7b8d6 + checksum: d294873cf88ec8f691e5edeb7b4b884f886c5f021a01902a0e243c362449db2b55419d7fb7187d059add747b7398321e39e44d391b65f94935174ce13452714d languageName: node linkType: hard From 210a3b56689d134fe77098ff8acd64f3dc21d985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 Oct 2022 11:03:49 +0200 Subject: [PATCH 157/221] add missed changeset for #14209 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/lucky-cats-peel.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lucky-cats-peel.md diff --git a/.changeset/lucky-cats-peel.md b/.changeset/lucky-cats-peel.md new file mode 100644 index 0000000000..dfe1047b7c --- /dev/null +++ b/.changeset/lucky-cats-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Small update to fix compatibility with newer versions of the `keyv` library From 35795a42285ca5ec26d1cfe7271d322704a2c3f4 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 24 Oct 2022 11:12:05 +0200 Subject: [PATCH 158/221] workflow/snyk: increase memory size to to 7gb Signed-off-by: Johan Haals --- .github/workflows/sync_snyk-github-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 089474e5b8..12285563c7 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -14,7 +14,7 @@ jobs: node-version: [14.x] env: - NODE_OPTIONS: --max-old-space-size=4096 + NODE_OPTIONS: --max-old-space-size=7168 steps: - uses: actions/checkout@v3 From 5505d65386f9d9d31c7b71e17e6a0dca7bfb37c1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 24 Oct 2022 11:17:23 +0200 Subject: [PATCH 159/221] actions: add workflow dispatch option Signed-off-by: Johan Haals --- .github/workflows/sync_snyk-github-issues.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 12285563c7..10b3b24aed 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -1,5 +1,6 @@ name: Sync Snyk GitHub issues on: + workflow_dispatch: schedule: - cron: '0 */4 * * *' From 03595bc877989609f81703b993dee9886966d881 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 09:44:36 +0000 Subject: [PATCH 160/221] Update dependency @octokit/webhooks to v10.3.1 Signed-off-by: Renovate Bot --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 30ca83a819..586a8ddc12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11599,22 +11599,22 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-types@npm:6.5.0": - version: 6.5.0 - resolution: "@octokit/webhooks-types@npm:6.5.0" - checksum: 747e4d277061ddced1b215d9fb3c3fb279e41069d90e703b4f9df6feb723769ba899c107f62aaa2db4cc7ac988de807e4455ab3b6049d8b853164e64c723a680 +"@octokit/webhooks-types@npm:6.6.0": + version: 6.6.0 + resolution: "@octokit/webhooks-types@npm:6.6.0" + checksum: 27d5ebb43bbcdaeacb34c1f8d5c5a8c1dfab8ca3b7173e3016c18365c5c832f3f53a1611fbc86371a8159ef2bc030b7c22c2901ec819134a71855d66e52762f9 languageName: node linkType: hard "@octokit/webhooks@npm:^10.0.0": - version: 10.3.0 - resolution: "@octokit/webhooks@npm:10.3.0" + version: 10.3.1 + resolution: "@octokit/webhooks@npm:10.3.1" dependencies: "@octokit/request-error": ^3.0.0 "@octokit/webhooks-methods": ^3.0.0 - "@octokit/webhooks-types": 6.5.0 + "@octokit/webhooks-types": 6.6.0 aggregate-error: ^3.1.0 - checksum: 23c61cc139f1db145f74deab637efcb47f213b45ce85f107b9b9a80d96c9a566e5f763dfc9f3d75594e31ca3408c1b5ced48e735bb367886948135f3bb490210 + checksum: 2601528d67d9de25fa8d6b9c7b88a75de7887862f0f36569ce15592a35a02281f7f35870fcc3ff2ca2b9933905c676be3ada747efb88a1084cf3c2cdab1518b6 languageName: node linkType: hard From 1e7b6405186d6a1a3453b4c5c6a0458fb622d336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 Oct 2022 14:28:48 +0200 Subject: [PATCH 161/221] Get rid of warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/grumpy-pigs-reflect.md | 5 +++++ .../components/EntityListComponent/EntityListComponent.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/grumpy-pigs-reflect.md diff --git a/.changeset/grumpy-pigs-reflect.md b/.changeset/grumpy-pigs-reflect.md new file mode 100644 index 0000000000..797aefcba0 --- /dev/null +++ b/.changeset/grumpy-pigs-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Get rid of `this-is-undefined-in-esm` warning diff --git a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx index 47f0139e5e..dac83a4817 100644 --- a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx +++ b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx @@ -98,7 +98,7 @@ export const EntityListComponent = (props: EntityListComponentProps) => { onItemClick?.call(this, r.target)} + onClick={() => onItemClick?.(r.target)} > {locationListItemIcon(r.target)} From f37d8e8e3cdb26d4088b90d34d40fd18cd96ebe2 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Mon, 24 Oct 2022 14:55:41 +0200 Subject: [PATCH 162/221] microsite: Add Gitea to sidebar Signed-off-by: Johan Haals --- microsite/sidebars.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 1f171e1832..2660e2fae6 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -200,6 +200,11 @@ "integrations/gitlab/discovery" ] }, + { + "type": "subcategory", + "label": "Gitea", + "ids": ["integrations/gitea/locations"] + }, { "type": "subcategory", "label": "Google GCS", From 445460e2d19c4aec84037654be05d9c82c6c5f61 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Wed, 19 Oct 2022 08:34:58 -0500 Subject: [PATCH 163/221] Updates to the To Do List permission tutorial Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- docs/permissions/getting-started.md | 2 - .../02-adding-a-basic-permission-check.md | 126 +++++++++++++++++- .../05-frontend-authorization.md | 24 +++- 3 files changed, 144 insertions(+), 8 deletions(-) diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index 6e6bcd2d03..d2c467ff08 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -53,7 +53,6 @@ $ yarn workspace backend add @backstage/plugin-permission-backend 2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. ```typescript -import { IdentityClient } from '@backstage/plugin-auth-node'; import { createRouter } from '@backstage/plugin-permission-backend'; import { AuthorizeResult, @@ -119,7 +118,6 @@ permission: 2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: ```diff - import { IdentityClient } from '@backstage/plugin-auth-node'; import { createRouter } from '@backstage/plugin-permission-backend'; import { AuthorizeResult, diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 124605cb66..785b0bfdda 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -48,7 +48,9 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: ... - import { InputError } from '@backstage/errors'; +- import { IdentityApi } from '@backstage/plugin-auth-node'; + import { InputError, NotAllowedError } from '@backstage/errors'; ++ import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; + import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; + import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; @@ -56,7 +58,7 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: export interface RouterOptions { logger: Logger; - identity: IdentityClient; + identity: IdentityApi; + permissions: PermissionEvaluator; } @@ -69,11 +71,13 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: ... router.post('/todos', async (req, res) => { - const token = IdentityClient.getBearerToken(req.header('authorization')); let author: string | undefined = undefined; - const user = token ? await identity.authenticate(token) : undefined; + const user = await identity.authenticate(token) : undefined; author = user?.identity.userEntityRef; ++ const token = getBearerTokenFromAuthorizationHeader( ++ req.header('authorization'), ++ ); + const decision = ( + await permissions.authorize([{ permission: todoListCreatePermission }], { + token, @@ -128,10 +132,8 @@ In order to test the logic above, the integrators of your backstage instance nee ```diff // packages/backend/src/plugins/permission.ts -- import { IdentityClient } from '@backstage/plugin-auth-node'; + import { + BackstageIdentityResponse, -+ IdentityClient + } from '@backstage/plugin-auth-node'; import { PermissionPolicy, @@ -170,3 +172,117 @@ Let's flip the result back to `ALLOW` before moving on. }; } ``` + +At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. + +First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: + +```diff +import { getVoidLogger } from '@backstage/backend-common'; +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; ++ import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import express from 'express'; +import request from 'supertest'; + +import { createRouter } from './router'; + ++ const mockedAuthorize: jest.MockedFunction = ++ jest.fn(); ++ const mockedPermissionQuery: jest.MockedFunction< ++ PermissionEvaluator['authorizeConditional'] ++> = jest.fn(); + ++ const permissionEvaluator: PermissionEvaluator = { ++ authorize: mockedAuthorize, ++ authorizeConditional: mockedPermissionQuery, ++}; + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + identity: {} as DefaultIdentityClient, ++ permissions: toPermissionEvaluator, + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); +}); + +``` + +Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`, first we need to add the `@backstage/plugin-permission-node` package to `plugins/todo-list-backend/package.json` and then we can make the following edits: + +```diff +import { + createServiceBuilder, + loadBackendConfig, + SingleHostDiscovery, ++ ServerTokenManager, +} from '@backstage/backend-common'; +import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'todo-list-backend' }); + logger.debug('Starting application server...'); + const config = await loadBackendConfig({ logger, argv: process.argv }); + const discovery = SingleHostDiscovery.fromConfig(config); ++ const tokenManager = ServerTokenManager.fromConfig(config, { ++ logger, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + const router = await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), ++ permissions, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/todo-list', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); +``` + +Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index dc4df7839c..c3bdd26583 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -118,7 +118,7 @@ Providing a disabled state can be a helpful signal to users, but there may be ca - - - -+ ++ }> + + + @@ -165,3 +165,25 @@ Providing a disabled state can be a helpful signal to users, but there may be ca ``` Now you should find that the component for adding a todo list item does not render at all. Success! + +You can also use `RequirePermission` to prevent access to routes as well, here's how that would look in your `packages/app/src/App.tsx`: + +```diff ++ import { RequirePermission } from '@backstage/plugin-permission-react'; ++ import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; + +... + + }> + {searchPage} + + } /> ++ ++ } /> ++ + +``` + +Now if you try to navigate to `https://localhost:3000/todo-list` you'll get and error page if you do not have permission. From 093ef4c5b340a2520cb1335e185a938d7a879a80 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Date: Mon, 24 Oct 2022 08:48:24 -0500 Subject: [PATCH 164/221] Improved formatting and content Signed-off-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> --- .../02-adding-a-basic-permission-check.md | 162 +++++++++--------- .../05-frontend-authorization.md | 4 +- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 785b0bfdda..241b175987 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -73,7 +73,7 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: router.post('/todos', async (req, res) => { let author: string | undefined = undefined; - const user = await identity.authenticate(token) : undefined; + const user = await identity.getIdentity({ request: req }); author = user?.identity.userEntityRef; + const token = getBearerTokenFromAuthorizationHeader( + req.header('authorization'), @@ -178,111 +178,111 @@ At this point everything is working but if you run `yarn tsc` you'll get some er First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: ```diff -import { getVoidLogger } from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + import { getVoidLogger } from '@backstage/backend-common'; + import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import express from 'express'; -import request from 'supertest'; + import express from 'express'; + import request from 'supertest'; -import { createRouter } from './router'; + import { createRouter } from './router'; + const mockedAuthorize: jest.MockedFunction = -+ jest.fn(); ++ jest.fn(); + const mockedPermissionQuery: jest.MockedFunction< -+ PermissionEvaluator['authorizeConditional'] -+> = jest.fn(); ++ PermissionEvaluator['authorizeConditional'] ++ > = jest.fn(); + const permissionEvaluator: PermissionEvaluator = { -+ authorize: mockedAuthorize, -+ authorizeConditional: mockedPermissionQuery, -+}; ++ authorize: mockedAuthorize, ++ authorizeConditional: mockedPermissionQuery, ++ }; -describe('createRouter', () => { - let app: express.Express; + describe('createRouter', () => { + let app: express.Express; - beforeAll(async () => { - const router = await createRouter({ - logger: getVoidLogger(), - identity: {} as DefaultIdentityClient, -+ permissions: toPermissionEvaluator, + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + identity: {} as DefaultIdentityClient, ++ permissions: toPermissionEvaluator, + }); + app = express().use(router); }); - app = express().use(router); - }); - beforeEach(() => { - jest.resetAllMocks(); - }); + beforeEach(() => { + jest.resetAllMocks(); + }); - describe('GET /health', () => { - it('returns ok', async () => { - const response = await request(app).get('/health'); + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); - expect(response.status).toEqual(200); - expect(response.body).toEqual({ status: 'ok' }); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); }); }); -}); ``` Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`, first we need to add the `@backstage/plugin-permission-node` package to `plugins/todo-list-backend/package.json` and then we can make the following edits: ```diff -import { - createServiceBuilder, - loadBackendConfig, - SingleHostDiscovery, -+ ServerTokenManager, -} from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; + import { + createServiceBuilder, + loadBackendConfig, + SingleHostDiscovery, ++ ServerTokenManager, + } from '@backstage/backend-common'; + import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; + import { Server } from 'http'; + import { Logger } from 'winston'; + import { createRouter } from './router'; -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'todo-list-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = SingleHostDiscovery.fromConfig(config); -+ const tokenManager = ServerTokenManager.fromConfig(config, { -+ logger, -+ }); -+ const permissions = ServerPermissionClient.fromConfig(config, { -+ discovery, -+ tokenManager, -+ }); - const router = await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), -+ permissions, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/todo-list', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); + export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; } - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} + export async function startStandaloneServer( + options: ServerOptions, + ): Promise { + const logger = options.logger.child({ service: 'todo-list-backend' }); + logger.debug('Starting application server...'); + const config = await loadBackendConfig({ logger, argv: process.argv }); + const discovery = SingleHostDiscovery.fromConfig(config); ++ const tokenManager = ServerTokenManager.fromConfig(config, { ++ logger, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + const router = await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), ++ permissions, + }); -module.hot?.accept(); + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/todo-list', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); + } + + module.hot?.accept(); ``` Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index c3bdd26583..d494ea60ba 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -166,7 +166,7 @@ Providing a disabled state can be a helpful signal to users, but there may be ca Now you should find that the component for adding a todo list item does not render at all. Success! -You can also use `RequirePermission` to prevent access to routes as well, here's how that would look in your `packages/app/src/App.tsx`: +You can also use `RequirePermission` to prevent access to routes as well. Here's how that would look in your `packages/app/src/App.tsx`: ```diff + import { RequirePermission } from '@backstage/plugin-permission-react'; @@ -181,7 +181,7 @@ You can also use `RequirePermission` to prevent access to routes as well, here's + -+ } /> ++ + ``` From ead285b9e4ef451613207d8b4e0efdaf2018c7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 Oct 2022 15:41:14 +0200 Subject: [PATCH 165/221] lowercase h in github, in plugins/github-issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/shaggy-birds-happen.md | 5 +++ plugins/github-issues/api-report.md | 24 +++++++------- plugins/github-issues/dev/index.tsx | 16 +++++----- ...uesApi.test.ts => githubIssuesApi.test.ts} | 31 ++++++++++--------- ...{gitHubIssuesApi.ts => githubIssuesApi.ts} | 19 ++++++------ plugins/github-issues/src/api/index.ts | 3 +- .../GithubIssues.test.tsx} | 25 +++++++-------- .../GithubIssues.tsx} | 20 ++++++------ .../IssueCard/Assignees.tsx | 1 + .../IssueCard/CommentsCount.tsx | 1 + .../IssueCard/IssueCard.tsx | 3 +- .../IssueCard/index.ts | 1 + .../IssuesList/Filters/Filters.tsx | 3 +- .../IssuesList/Filters/index.ts | 1 + .../IssuesList/IssuesList.tsx | 9 +++--- .../IssuesList/index.tsx | 1 + .../NoRepositoriesInfo/NoRepositoriesInfo.tsx | 2 +- .../NoRepositoriesInfo/index.tsx | 1 + .../{GitHubIssues => GithubIssues}/index.ts | 3 +- ...ries.ts => useEntityGithubRepositories.ts} | 2 +- ...Hub.ts => useGetIssuesByRepoFromGithub.ts} | 14 ++++----- plugins/github-issues/src/index.ts | 16 +++++----- plugins/github-issues/src/plugin.test.ts | 5 +-- plugins/github-issues/src/plugin.ts | 22 ++++++------- 24 files changed, 120 insertions(+), 108 deletions(-) create mode 100644 .changeset/shaggy-birds-happen.md rename plugins/github-issues/src/api/{gitHubIssuesApi.test.ts => githubIssuesApi.test.ts} (93%) rename plugins/github-issues/src/api/{gitHubIssuesApi.ts => githubIssuesApi.ts} (93%) rename plugins/github-issues/src/components/{GitHubIssues/GitHubIssues.test.tsx => GithubIssues/GithubIssues.test.tsx} (90%) rename plugins/github-issues/src/components/{GitHubIssues/GitHubIssues.tsx => GithubIssues/GithubIssues.tsx} (79%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssueCard/Assignees.tsx (99%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssueCard/CommentsCount.tsx (99%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssueCard/IssueCard.tsx (99%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssueCard/index.ts (99%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssuesList/Filters/Filters.tsx (98%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssuesList/Filters/index.ts (99%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssuesList/IssuesList.tsx (97%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/IssuesList/index.tsx (99%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/NoRepositoriesInfo/NoRepositoriesInfo.tsx (100%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/NoRepositoriesInfo/index.tsx (99%) rename plugins/github-issues/src/components/{GitHubIssues => GithubIssues}/index.ts (94%) rename plugins/github-issues/src/hooks/{useEntityGitHubRepositories.ts => useEntityGithubRepositories.ts} (97%) rename plugins/github-issues/src/hooks/{useGetIssuesByRepoFromGitHub.ts => useGetIssuesByRepoFromGithub.ts} (75%) diff --git a/.changeset/shaggy-birds-happen.md b/.changeset/shaggy-birds-happen.md new file mode 100644 index 0000000000..049a3cb3aa --- /dev/null +++ b/.changeset/shaggy-birds-happen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-issues': minor +--- + +BREAKING: Changed the casing of all exported types to have a lowercase "h" in "github". E.g. "GitHubIssuesPage" was renamed to "GithubIssuesPage". Please rename your imports where necessary. diff --git a/plugins/github-issues/api-report.md b/plugins/github-issues/api-report.md index 41b4f87064..4e57eeda46 100644 --- a/plugins/github-issues/api-report.md +++ b/plugins/github-issues/api-report.md @@ -9,7 +9,15 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) -export const GitHubIssuesCard: (props: GitHubIssuesProps) => JSX.Element; +export interface GithubIssuesByRepoOptions { + // (undocumented) + filterBy?: GithubIssuesFilters; + // (undocumented) + orderBy?: GithubIssuesOrdering; +} + +// @public (undocumented) +export const GithubIssuesCard: (props: GithubIssuesProps) => JSX.Element; // @public (undocumented) export interface GithubIssuesFilters { @@ -36,10 +44,10 @@ export interface GithubIssuesOrdering { } // @public (undocumented) -export const GitHubIssuesPage: (props: GitHubIssuesProps) => JSX.Element; +export const GithubIssuesPage: (props: GithubIssuesProps) => JSX.Element; // @public (undocumented) -export const gitHubIssuesPlugin: BackstagePlugin< +export const githubIssuesPlugin: BackstagePlugin< { root: RouteRef; }, @@ -48,20 +56,12 @@ export const gitHubIssuesPlugin: BackstagePlugin< >; // @public (undocumented) -export type GitHubIssuesProps = { +export type GithubIssuesProps = { itemsPerPage?: number; itemsPerRepo?: number; filterBy?: GithubIssuesFilters; orderBy?: GithubIssuesOrdering; }; -// @public (undocumented) -export interface GitubIssuesByRepoOptions { - // (undocumented) - filterBy?: GithubIssuesFilters; - // (undocumented) - orderBy?: GithubIssuesOrdering; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/github-issues/dev/index.tsx b/plugins/github-issues/dev/index.tsx index a5f20ab9b9..05592cfa63 100644 --- a/plugins/github-issues/dev/index.tsx +++ b/plugins/github-issues/dev/index.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; import { @@ -20,21 +21,20 @@ import { catalogApiRef, EntityProvider, } from '@backstage/plugin-catalog-react'; - -import { gitHubIssuesPlugin, GitHubIssuesPage } from '../src'; -import { GitHubIssuesApi, gitHubIssuesApiRef } from '../src/api'; +import { githubIssuesPlugin, GithubIssuesPage } from '../src'; +import { GithubIssuesApi, githubIssuesApiRef } from '../src/api'; import testData from './__fixtures__/component-issues-data.json'; createDevApp() - .registerPlugin(gitHubIssuesPlugin) + .registerPlugin(githubIssuesPlugin) .registerApi({ - api: gitHubIssuesApiRef, + api: githubIssuesApiRef, deps: {}, factory: () => ({ - fetchIssuesByRepoFromGitHub: async () => testData, - } as GitHubIssuesApi), + fetchIssuesByRepoFromGithub: async () => testData, + } as GithubIssuesApi), }) .registerApi({ api: catalogApiRef, @@ -59,7 +59,7 @@ createDevApp() kind: 'Component', }} > - + ), }) diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.test.ts b/plugins/github-issues/src/api/githubIssuesApi.test.ts similarity index 93% rename from plugins/github-issues/src/api/gitHubIssuesApi.test.ts rename to plugins/github-issues/src/api/githubIssuesApi.test.ts index b2be54a2d1..e960d3123b 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.test.ts +++ b/plugins/github-issues/src/api/githubIssuesApi.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + const mockGraphQLQuery = jest.fn(() => ({})); jest.mock('octokit', () => ({ Octokit: jest.fn(() => ({ graphql: mockGraphQLQuery })), @@ -20,8 +21,8 @@ jest.mock('octokit', () => ({ import { ConfigApi, ErrorApi } from '@backstage/core-plugin-api'; import { ForwardedError } from '@backstage/errors'; -import { createFilterByClause, gitHubIssuesApi } from './gitHubIssuesApi'; -import type { GithubIssuesFilters } from './gitHubIssuesApi'; +import { createFilterByClause, githubIssuesApi } from './githubIssuesApi'; +import type { GithubIssuesFilters } from './githubIssuesApi'; function getFragment( filterBy = '', @@ -84,21 +85,21 @@ function getFragment( ' ...issues\n' + ' }\n' + ' \n' + - ' } \n' + + ' }\n' + ' ' ); } -describe('gitHubIssuesApi', () => { - describe('fetchIssuesByRepoFromGitHub', () => { - let api: ReturnType; +describe('githubIssuesApi', () => { + describe('fetchIssuesByRepoFromGithub', () => { + let api: ReturnType; afterEach(() => { jest.clearAllMocks(); }); beforeEach(() => { - api = gitHubIssuesApi( + api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -108,7 +109,7 @@ describe('gitHubIssuesApi', () => { }); it('should call GitHub API with correct query with fragment for each repo', async () => { - await api.fetchIssuesByRepoFromGitHub( + await api.fetchIssuesByRepoFromGithub( ['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10, ); @@ -118,7 +119,7 @@ describe('gitHubIssuesApi', () => { }); it('should call Github API with the correct filterBy and orderBy clauses', async () => { - await api.fetchIssuesByRepoFromGitHub( + await api.fetchIssuesByRepoFromGithub( ['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10, { @@ -221,7 +222,7 @@ describe('gitHubIssuesApi', () => { }), ); - const api = gitHubIssuesApi( + const api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -229,7 +230,7 @@ describe('gitHubIssuesApi', () => { { post: jest.fn() } as unknown as ErrorApi, ); - const data = await api.fetchIssuesByRepoFromGitHub( + const data = await api.fetchIssuesByRepoFromGithub( ['mrwolny/yo-yo', 'mrwolny/notfound'], 10, ); @@ -291,7 +292,7 @@ describe('gitHubIssuesApi', () => { }), ); - const api = gitHubIssuesApi( + const api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -299,7 +300,7 @@ describe('gitHubIssuesApi', () => { { post: jest.fn() } as unknown as ErrorApi, ); - const data = await api.fetchIssuesByRepoFromGitHub( + const data = await api.fetchIssuesByRepoFromGithub( ['mrwolny/notfound'], 10, ); @@ -332,7 +333,7 @@ describe('gitHubIssuesApi', () => { const mockErrorApi = { post: jest.fn() }; - const api = gitHubIssuesApi( + const api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -340,7 +341,7 @@ describe('gitHubIssuesApi', () => { mockErrorApi as unknown as ErrorApi, ); - await api.fetchIssuesByRepoFromGitHub(['mrwolny/notfound'], 10); + await api.fetchIssuesByRepoFromGithub(['mrwolny/notfound'], 10); expect(mockErrorApi.post).toHaveBeenCalledTimes(1); expect(mockErrorApi.post).toHaveBeenCalledWith( diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.ts b/plugins/github-issues/src/api/githubIssuesApi.ts similarity index 93% rename from plugins/github-issues/src/api/gitHubIssuesApi.ts rename to plugins/github-issues/src/api/githubIssuesApi.ts index bef1b20441..32331ef6b1 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.ts +++ b/plugins/github-issues/src/api/githubIssuesApi.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Octokit } from 'octokit'; import { createApiRef, @@ -71,7 +72,7 @@ export type RepoIssues = { export type IssuesByRepo = Record; /** @internal */ -export type GitHubIssuesApi = ReturnType; +export type GithubIssuesApi = ReturnType; /** * @public @@ -96,18 +97,18 @@ export interface GithubIssuesOrdering { /** * @public */ -export interface GitubIssuesByRepoOptions { +export interface GithubIssuesByRepoOptions { filterBy?: GithubIssuesFilters; orderBy?: GithubIssuesOrdering; } /** @internal */ -export const gitHubIssuesApiRef = createApiRef({ +export const githubIssuesApiRef = createApiRef({ id: 'plugin.githubissues.service', }); /** @internal */ -export const gitHubIssuesApi = ( +export const githubIssuesApi = ( githubAuthApi: OAuthApi, configApi: ConfigApi, errorApi: ErrorApi, @@ -128,7 +129,7 @@ export const gitHubIssuesApi = ( return octokit.graphql; }; - const fetchIssuesByRepoFromGitHub = async ( + const fetchIssuesByRepoFromGithub = async ( repos: Array, itemsPerRepo: number, { @@ -137,7 +138,7 @@ export const gitHubIssuesApi = ( field: 'UPDATED_AT', direction: 'DESC', }, - }: GitubIssuesByRepoOptions = {}, + }: GithubIssuesByRepoOptions = {}, ): Promise => { const graphql = await getOctokit(); const safeNames: Array = []; @@ -186,7 +187,7 @@ export const gitHubIssuesApi = ( }, {} as IssuesByRepo); }; - return { fetchIssuesByRepoFromGitHub }; + return { fetchIssuesByRepoFromGithub }; }; function formatFilterValue( @@ -224,7 +225,7 @@ function createIssueByRepoQuery( owner: string; }>, itemsPerRepo: number, - { filterBy, orderBy }: GitubIssuesByRepoOptions, + { filterBy, orderBy }: GithubIssuesByRepoOptions, ): string { const fragment = ` fragment issues on Repository { @@ -278,7 +279,7 @@ function createIssueByRepoQuery( } `, )} - } + } `; return query; diff --git a/plugins/github-issues/src/api/index.ts b/plugins/github-issues/src/api/index.ts index 4cc3372c45..91f058f399 100644 --- a/plugins/github-issues/src/api/index.ts +++ b/plugins/github-issues/src/api/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './gitHubIssuesApi'; + +export * from './githubIssuesApi'; diff --git a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.test.tsx b/plugins/github-issues/src/components/GithubIssues/GithubIssues.test.tsx similarity index 90% rename from plugins/github-issues/src/components/GitHubIssues/GitHubIssues.test.tsx rename to plugins/github-issues/src/components/GithubIssues/GithubIssues.test.tsx index 33eec79359..3f9e829de9 100644 --- a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.test.tsx +++ b/plugins/github-issues/src/components/GithubIssues/GithubIssues.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { @@ -21,10 +22,8 @@ import { CatalogApi, } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; - -import { GitHubIssuesApi, gitHubIssuesApiRef, Issue } from '../../api'; - -import { GitHubIssues } from './GitHubIssues'; +import { GithubIssuesApi, githubIssuesApiRef, Issue } from '../../api'; +import { GithubIssues } from './GithubIssues'; const getTestIssue = (overwrites: Partial = {}): { node: Issue } => ({ node: { @@ -80,10 +79,10 @@ const mockCatalogApi = { getEntities: () => ({}), } as CatalogApi; -describe('GitHubIssues', () => { +describe('GithubIssues', () => { it('should render correctly when there are no issues in GitHub', async () => { const mockApi = { - fetchIssuesByRepoFromGitHub: async () => ({ + fetchIssuesByRepoFromGithub: async () => ({ backstage: { issues: { totalCount: 0, @@ -91,17 +90,17 @@ describe('GitHubIssues', () => { }, }, }), - } as GitHubIssuesApi; + } as GithubIssuesApi; const apis = [ - [gitHubIssuesApiRef, mockApi], + [githubIssuesApiRef, mockApi], [catalogApiRef, mockCatalogApi], ] as const; const { getByTestId } = await renderInTestApp( - + , ); @@ -118,7 +117,7 @@ describe('GitHubIssues', () => { }); const mockApi = { - fetchIssuesByRepoFromGitHub: async () => ({ + fetchIssuesByRepoFromGithub: async () => ({ backstage: { issues: { totalCount: 1, @@ -126,16 +125,16 @@ describe('GitHubIssues', () => { }, }, }), - } as GitHubIssuesApi; + } as GithubIssuesApi; const apis = [ - [gitHubIssuesApiRef, mockApi], + [githubIssuesApiRef, mockApi], [catalogApiRef, mockCatalogApi], ] as const; const { getByText, getByTestId } = await renderInTestApp( - + , ); diff --git a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx b/plugins/github-issues/src/components/GithubIssues/GithubIssues.tsx similarity index 79% rename from plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx rename to plugins/github-issues/src/components/GithubIssues/GithubIssues.tsx index cb6639c83c..faae7fd3a5 100644 --- a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx +++ b/plugins/github-issues/src/components/GithubIssues/GithubIssues.tsx @@ -13,41 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { Box, IconButton, Typography } from '@material-ui/core'; import { InfoCard, Progress } from '@backstage/core-components'; import RefreshIcon from '@material-ui/icons/Refresh'; - -import { useEntityGitHubRepositories } from '../../hooks/useEntityGitHubRepositories'; -import { useGetIssuesByRepoFromGitHub } from '../../hooks/useGetIssuesByRepoFromGitHub'; - +import { useEntityGithubRepositories } from '../../hooks/useEntityGithubRepositories'; +import { useGetIssuesByRepoFromGithub } from '../../hooks/useGetIssuesByRepoFromGithub'; import { IssuesList } from './IssuesList'; import { NoRepositoriesInfo } from './NoRepositoriesInfo'; import type { GithubIssuesFilters, GithubIssuesOrdering, -} from '../../api/gitHubIssuesApi'; +} from '../../api/githubIssuesApi'; /** * @public */ -export type GitHubIssuesProps = { +export type GithubIssuesProps = { itemsPerPage?: number; itemsPerRepo?: number; filterBy?: GithubIssuesFilters; orderBy?: GithubIssuesOrdering; }; -export const GitHubIssues = (props: GitHubIssuesProps) => { +export const GithubIssues = (props: GithubIssuesProps) => { const { itemsPerPage = 10, itemsPerRepo = 40, filterBy, orderBy } = props; - const { repositories } = useEntityGitHubRepositories(); + const { repositories } = useEntityGithubRepositories(); const { isLoading, - gitHubIssuesByRepo: issuesByRepository, + githubIssuesByRepo: issuesByRepository, retry, - } = useGetIssuesByRepoFromGitHub(repositories, itemsPerRepo, { + } = useGetIssuesByRepoFromGithub(repositories, itemsPerRepo, { filterBy, orderBy, }); diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/Assignees.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/Assignees.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/Assignees.tsx rename to plugins/github-issues/src/components/GithubIssues/IssueCard/Assignees.tsx index 173c8d9439..4b66048039 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/Assignees.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/Assignees.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { Typography, Box, Avatar, makeStyles } from '@material-ui/core'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/CommentsCount.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/CommentsCount.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/CommentsCount.tsx rename to plugins/github-issues/src/components/GithubIssues/IssueCard/CommentsCount.tsx index 80a365cbbb..35352d10d2 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/CommentsCount.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/CommentsCount.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { ChatIcon } from '@backstage/core-components'; import { Box, Badge } from '@material-ui/core'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/IssueCard.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/IssueCard.tsx rename to plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx index ccf30a9a84..1094bc561f 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/IssueCard.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { DateTime } from 'luxon'; - import { Box, Paper, @@ -25,7 +25,6 @@ import { } from '@material-ui/core'; import { Assignees } from './Assignees'; import { CommentsCount } from './CommentsCount'; - import Divider from '@material-ui/core/Divider'; type IssueCardProps = { diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/index.ts b/plugins/github-issues/src/components/GithubIssues/IssueCard/index.ts similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/index.ts rename to plugins/github-issues/src/components/GithubIssues/IssueCard/index.ts index 5c60abf6ab..635bcd89f4 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/index.ts +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { IssueCard } from './IssueCard'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/Filters.tsx b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/Filters.tsx similarity index 98% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/Filters.tsx rename to plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/Filters.tsx index de071ab5bb..f9e22980db 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/Filters.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/Filters.tsx @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { Select, SelectedItems, SelectItem } from '@backstage/core-components'; import { makeStyles, Box, Typography } from '@material-ui/core'; type RepositoryFiltersProps = { items: Array; - totalIssuesInGitHub: number; + totalIssuesInGithub: number; placeholder: string; onChange: (active: Array) => void; }; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/index.ts b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/index.ts similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/index.ts rename to plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/index.ts index 331d017455..a9e4f87642 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/index.ts +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './Filters'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/IssuesList.tsx b/plugins/github-issues/src/components/GithubIssues/IssuesList/IssuesList.tsx similarity index 97% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/IssuesList.tsx rename to plugins/github-issues/src/components/GithubIssues/IssuesList/IssuesList.tsx index b46eab22b6..27f6375308 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/IssuesList.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/IssuesList.tsx @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { Box } from '@material-ui/core'; import { Pagination } from '@material-ui/lab'; - import { IssueCard } from '../IssueCard'; import { IssuesByRepo } from '../../../api'; import { RepositoryFilters } from './Filters'; @@ -60,7 +59,7 @@ export const IssuesList = ({ [issuesByRepository], ); - const totalIssuesInGitHub = React.useMemo( + const totalIssuesInGithub = React.useMemo( () => issuesByRepository ? Object.values(issuesByRepository).reduce( @@ -113,12 +112,12 @@ export const IssuesList = ({ {issues.length > 0 && ( )} diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/index.tsx b/plugins/github-issues/src/components/GithubIssues/IssuesList/index.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/index.tsx rename to plugins/github-issues/src/components/GithubIssues/IssuesList/index.tsx index 0b7514c720..ca6da2e0bd 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/index.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/index.tsx @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './IssuesList'; diff --git a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx similarity index 100% rename from plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx rename to plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx index dca4b872ae..07fb4019dc 100644 --- a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx +++ b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { EmptyState } from '@backstage/core-components'; export const NoRepositoriesInfo = () => { diff --git a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/index.tsx b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/index.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/index.tsx rename to plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/index.tsx index 577bb475e3..ea4cef40a6 100644 --- a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/index.tsx +++ b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/index.tsx @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './NoRepositoriesInfo'; diff --git a/plugins/github-issues/src/components/GitHubIssues/index.ts b/plugins/github-issues/src/components/GithubIssues/index.ts similarity index 94% rename from plugins/github-issues/src/components/GitHubIssues/index.ts rename to plugins/github-issues/src/components/GithubIssues/index.ts index 6fa55e3358..865168b3e2 100644 --- a/plugins/github-issues/src/components/GitHubIssues/index.ts +++ b/plugins/github-issues/src/components/GithubIssues/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './GitHubIssues'; + +export * from './GithubIssues'; diff --git a/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts b/plugins/github-issues/src/hooks/useEntityGithubRepositories.ts similarity index 97% rename from plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts rename to plugins/github-issues/src/hooks/useEntityGithubRepositories.ts index d7b60575ad..f3fab72090 100644 --- a/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts +++ b/plugins/github-issues/src/hooks/useEntityGithubRepositories.ts @@ -25,7 +25,7 @@ export const getProjectNameFromEntity = (entity: Entity): string => { return entity?.metadata.annotations?.[GITHUB_PROJECT_SLUG_ANNOTATION] ?? ''; }; -export function useEntityGitHubRepositories() { +export function useEntityGithubRepositories() { const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); diff --git a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGithub.ts similarity index 75% rename from plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts rename to plugins/github-issues/src/hooks/useGetIssuesByRepoFromGithub.ts index 693be9e4d9..8ec5f3ec65 100644 --- a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts +++ b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGithub.ts @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { useApi } from '@backstage/core-plugin-api'; - import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { gitHubIssuesApiRef, GitubIssuesByRepoOptions } from '../api'; +import { githubIssuesApiRef, GithubIssuesByRepoOptions } from '../api'; -export const useGetIssuesByRepoFromGitHub = ( +export const useGetIssuesByRepoFromGithub = ( repos: Array, itemsPerRepo: number, - options?: GitubIssuesByRepoOptions, + options?: GithubIssuesByRepoOptions, ) => { - const gitHubIssuesApi = useApi(gitHubIssuesApiRef); + const githubIssuesApi = useApi(githubIssuesApiRef); const { value: issues, @@ -31,7 +31,7 @@ export const useGetIssuesByRepoFromGitHub = ( retry, } = useAsyncRetry(async () => { if (repos.length > 0) { - return await gitHubIssuesApi.fetchIssuesByRepoFromGitHub( + return await githubIssuesApi.fetchIssuesByRepoFromGithub( repos, itemsPerRepo, options, @@ -41,5 +41,5 @@ export const useGetIssuesByRepoFromGitHub = ( return {}; }, [repos]); - return { isLoading, gitHubIssuesByRepo: issues, retry }; + return { isLoading, githubIssuesByRepo: issues, retry }; }; diff --git a/plugins/github-issues/src/index.ts b/plugins/github-issues/src/index.ts index 7717ba5f33..b44c1e8773 100644 --- a/plugins/github-issues/src/index.ts +++ b/plugins/github-issues/src/index.ts @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { - gitHubIssuesPlugin, - GitHubIssuesPage, - GitHubIssuesCard, -} from './plugin'; -export type { GitHubIssuesProps } from './components/GitHubIssues'; +export { + githubIssuesPlugin, + GithubIssuesPage, + GithubIssuesCard, +} from './plugin'; +export type { GithubIssuesProps } from './components/GithubIssues'; export type { GithubIssuesFilters, GithubIssuesOrdering, - GitubIssuesByRepoOptions, -} from './api/gitHubIssuesApi'; + GithubIssuesByRepoOptions, +} from './api/githubIssuesApi'; diff --git a/plugins/github-issues/src/plugin.test.ts b/plugins/github-issues/src/plugin.test.ts index e6749dd970..f611e1b1e2 100644 --- a/plugins/github-issues/src/plugin.test.ts +++ b/plugins/github-issues/src/plugin.test.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { gitHubIssuesPlugin } from './plugin'; + +import { githubIssuesPlugin } from './plugin'; describe('github-issues', () => { it('should export plugin', () => { - expect(gitHubIssuesPlugin).toBeDefined(); + expect(githubIssuesPlugin).toBeDefined(); }); }); diff --git a/plugins/github-issues/src/plugin.ts b/plugins/github-issues/src/plugin.ts index 3d845f4adf..52446efe87 100644 --- a/plugins/github-issues/src/plugin.ts +++ b/plugins/github-issues/src/plugin.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createPlugin, createApiFactory, @@ -22,23 +23,22 @@ import { errorApiRef, githubAuthApiRef, } from '@backstage/core-plugin-api'; -import { gitHubIssuesApi, gitHubIssuesApiRef } from './api'; - +import { githubIssuesApi, githubIssuesApiRef } from './api'; import { rootRouteRef } from './routes'; /** @public */ -export const gitHubIssuesPlugin = createPlugin({ +export const githubIssuesPlugin = createPlugin({ id: 'github-issues', apis: [ createApiFactory({ - api: gitHubIssuesApiRef, + api: githubIssuesApiRef, deps: { configApi: configApiRef, githubAuthApi: githubAuthApiRef, errorApi: errorApiRef, }, factory: ({ configApi, githubAuthApi, errorApi }) => - gitHubIssuesApi(githubAuthApi, configApi, errorApi), + githubIssuesApi(githubAuthApi, configApi, errorApi), }), ], routes: { @@ -47,21 +47,21 @@ export const gitHubIssuesPlugin = createPlugin({ }); /** @public */ -export const GitHubIssuesCard = gitHubIssuesPlugin.provide( +export const GithubIssuesCard = githubIssuesPlugin.provide( createComponentExtension({ - name: 'GitHubIssuesCard', + name: 'GithubIssuesCard', component: { - lazy: () => import('./components/GitHubIssues').then(m => m.GitHubIssues), + lazy: () => import('./components/GithubIssues').then(m => m.GithubIssues), }, }), ); /** @public */ -export const GitHubIssuesPage = gitHubIssuesPlugin.provide( +export const GithubIssuesPage = githubIssuesPlugin.provide( createRoutableExtension({ - name: 'GitHubIssuesPage', + name: 'GithubIssuesPage', component: () => - import('./components/GitHubIssues').then(m => m.GitHubIssues), + import('./components/GithubIssues').then(m => m.GithubIssues), mountPoint: rootRouteRef, }), ); From 858bcaf69b54ac99708818fbb51fdc314d0b33ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 24 Oct 2022 16:47:18 +0200 Subject: [PATCH 166/221] fix the end-to-end tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b576b380d7..e9f94623ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27079,15 +27079,15 @@ __metadata: linkType: hard "json-schema-library@npm:^7.0.0": - version: 7.2.0 - resolution: "json-schema-library@npm:7.2.0" + version: 7.2.1 + resolution: "json-schema-library@npm:7.2.1" dependencies: deepmerge: ^4.2.2 fast-deep-equal: ^3.1.3 gson-pointer: ^4.1.1 gson-query: ^5.1.0 valid-url: ^1.0.9 - checksum: 6c5086899c2f89c1b488145f1067f00a5901729e1d13513bb48551971d45be5dc60810fa4bcec65345e4d1520aa08743348dabdf59f74122b5b95c840b4edfda + checksum: 65bc4014cdfe22c4f46d6cb0a0c059d94045128fb96189c4823dff653b6788c2dbab8d5d2e8475bbdc65fd47abf19cedcc3f02df608452c5413fea5d6e675003 languageName: node linkType: hard From 80bfac5266ea31e1c95420921dc0fdf1ff9e70af Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 21 Oct 2022 17:42:49 +0200 Subject: [PATCH 167/221] create-app: refactor git init Signed-off-by: Patrik Oldsberg --- .changeset/shiny-beers-relax.md | 5 + packages/create-app/package.json | 1 - packages/create-app/src/createApp.test.ts | 12 +-- packages/create-app/src/createApp.ts | 13 ++- packages/create-app/src/lib/tasks.test.ts | 119 +++++++++++----------- packages/create-app/src/lib/tasks.ts | 79 ++++++-------- yarn.lock | 1 - 7 files changed, 112 insertions(+), 118 deletions(-) create mode 100644 .changeset/shiny-beers-relax.md diff --git a/.changeset/shiny-beers-relax.md b/.changeset/shiny-beers-relax.md new file mode 100644 index 0000000000..be8e9b6fb7 --- /dev/null +++ b/.changeset/shiny-beers-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Updated the create-app command to no longer require Git to be installed and configured. A git repository will only be initialized if possible and if not already in an git repository. diff --git a/packages/create-app/package.json b/packages/create-app/package.json index a6a62fbbf0..61384b5d0b 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -34,7 +34,6 @@ "dependencies": { "@backstage/cli-common": "workspace:^", "chalk": "^4.0.0", - "command-exists": "^1.2.9", "commander": "^9.1.0", "fs-extra": "10.1.0", "handlebars": "^4.7.3", diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index ec8d15ead1..f4c94d337a 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -32,7 +32,7 @@ const promptMock = jest.spyOn(inquirer, 'prompt'); const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask'); const templatingMock = jest.spyOn(tasks, 'templatingTask'); const checkAppExistsMock = jest.spyOn(tasks, 'checkAppExistsTask'); -const initGitRepositoryMock = jest.spyOn(tasks, 'initGitRepository'); +const tryInitGitRepositoryMock = jest.spyOn(tasks, 'tryInitGitRepository'); const readGitConfig = jest.spyOn(tasks, 'readGitConfig'); const createTemporaryAppFolderMock = jest.spyOn( tasks, @@ -59,8 +59,6 @@ describe('command entrypoint', () => { dbType: 'PostgreSQL', }); readGitConfig.mockResolvedValue({ - name: 'git-user', - email: 'git-email', defaultBranch: 'git-default-branch', }); }); @@ -74,7 +72,7 @@ describe('command entrypoint', () => { await createApp(cmd); expect(checkAppExistsMock).toHaveBeenCalled(); expect(createTemporaryAppFolderMock).toHaveBeenCalled(); - expect(initGitRepositoryMock).toHaveBeenCalled(); + expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(moveAppMock).toHaveBeenCalled(); expect(buildAppMock).toHaveBeenCalled(); @@ -84,7 +82,7 @@ describe('command entrypoint', () => { const cmd = { path: 'myDirectory' } as unknown as Command; await createApp(cmd); expect(checkPathExistsMock).toHaveBeenCalled(); - expect(initGitRepositoryMock).toHaveBeenCalled(); + expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(buildAppMock).toHaveBeenCalled(); }); @@ -97,8 +95,8 @@ describe('command entrypoint', () => { it('should not call `initGitRepository` when `gitConfig` is undefined', async () => { const cmd = {} as unknown as Command; - readGitConfig.mockResolvedValue({}); + readGitConfig.mockResolvedValue(undefined); await createApp(cmd); - expect(initGitRepositoryMock).not.toHaveBeenCalled(); + expect(tryInitGitRepositoryMock).not.toHaveBeenCalled(); }); }); diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index 182e043565..6267081c8b 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -28,7 +28,7 @@ import { createTemporaryAppFolderTask, moveAppTask, templatingTask, - initGitRepository, + tryInitGitRepository, readGitConfig, } from './lib/tasks'; @@ -109,13 +109,16 @@ export default async (opts: OptionValues): Promise => { await moveAppTask(tempDir, appDir, answers.name); } - if (gitConfig?.name && gitConfig?.email) { - Task.section('Initializing git repository'); - await initGitRepository(appDir); + if (gitConfig) { + if (await tryInitGitRepository(appDir)) { + // Since we don't know whether we were able to init git before we + // try, we can't track the actual task execution + Task.forItem('init', 'git repository', async () => {}); + } } if (!opts.skipInstall) { - Task.section('Building the app'); + Task.section('Installing dependencies'); await buildAppTask(appDir); } diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index b918fc0180..8dc351ac97 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -27,12 +27,10 @@ import { createTemporaryAppFolderTask, moveAppTask, templatingTask, - initGitRepository, + tryInitGitRepository, readGitConfig, } from './tasks'; -const commandExists = jest.fn(); - jest.spyOn(Task, 'log').mockReturnValue(undefined); jest.spyOn(Task, 'error').mockReturnValue(undefined); jest.spyOn(Task, 'section').mockReturnValue(undefined); @@ -41,12 +39,6 @@ jest .mockImplementation((_a, _b, taskFunc) => taskFunc()); jest.mock('child_process'); -jest.mock( - 'command-exists', - () => - (...args: any[]) => - commandExists(...args), -); // By mocking this the filesystem mocks won't mess with reading all of the package.jsons jest.mock('./versions', () => ({ @@ -102,7 +94,10 @@ describe('tasks', () => { ( command: string, options: any, - callback: (error: null, stdout: any, stderr: any) => void, + callback: ( + error: Error | null, + result: { stdout: string; stderr: string }, + ) => void, ) => void >; @@ -293,29 +288,15 @@ describe('tasks', () => { it('should return git config if git package is installed and git credentials are set', async () => { mockExec.mockImplementation((_command, _options, callback) => { - callback(null, { stdout: 'main' }, 'standard error'); + callback(null, { stdout: 'main', stderr: '' }); }); - commandExists.mockResolvedValue(true); - const gitConfig = await readGitConfig(); - expect(gitConfig).toBeTruthy(); expect(gitConfig).toEqual({ - name: 'main', - email: 'main', defaultBranch: 'main', }); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.name', - { cwd: tmpDir }, - expect.any(Function), - ); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.email', - { cwd: tmpDir }, - expect.any(Function), - ); + expect(mockExec).toHaveBeenCalledTimes(3); expect(mockExec).toHaveBeenCalledWith( 'git init', { cwd: tmpDir }, @@ -326,58 +307,82 @@ describe('tasks', () => { { cwd: tmpDir }, expect.any(Function), ); + expect(mockExec).toHaveBeenCalledWith( + 'git branch --format="%(refname:short)"', + { cwd: tmpDir }, + expect.any(Function), + ); }); - it('should return false if git package is not installed', async () => { - commandExists.mockResolvedValue(false); - - const gitConfig = await readGitConfig(); - - expect(gitConfig).toEqual({}); - }); - - it('should return false if git package is installed but git credentials are not set', async () => { + it('should return false if git config is invalid', async () => { mockExec.mockImplementation((_command, _options, callback) => { - callback(null, { stdout: null }, 'standard error'); + callback(null, { stdout: '', stderr: '' }); }); - commandExists.mockResolvedValue(true); - const gitConfig = await readGitConfig(); - expect(gitConfig).toEqual({}); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.name', - { cwd: tmpDir }, - expect.any(Function), - ); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.email', - { cwd: tmpDir }, - expect.any(Function), - ); + expect(gitConfig).toEqual({ + defaultBranch: undefined, + }); + expect(mockExec).toHaveBeenCalledTimes(3); }); }); - describe('initGitRepository', () => { + describe('tryInitGitRepository', () => { it('should initialize a git repository at the given path', async () => { - const destinationDir = 'tmp/mockApp/'; + const destinationDir = 'tmp/mockApp'; - mockExec.mockImplementation((_command, callback) => { - callback(null, { stdout: 'main' }, 'standard error'); + mockExec.mockImplementation((command, _opts, callback) => { + if (command.startsWith('git rev-parse')) { + callback(new Error('not a git repo'), { stdout: '', stderr: '' }); + } else { + callback(null, { stdout: '', stderr: '' }); + } }); - await initGitRepository(destinationDir); + await expect(tryInitGitRepository(destinationDir)).resolves.toBe(true); - expect(mockExec).toHaveBeenCalledTimes(2); + expect(mockExec).toHaveBeenCalledTimes(4); expect(mockExec).toHaveBeenNthCalledWith( 1, - 'git init', + 'git rev-parse --is-inside-work-tree', + { cwd: destinationDir }, expect.any(Function), ); expect(mockExec).toHaveBeenNthCalledWith( 2, - 'git commit --allow-empty -m "Initial commit"', + 'git init', + { cwd: destinationDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 3, + 'git add .', + { cwd: destinationDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 4, + 'git commit -m "Initial commit"', + { cwd: destinationDir }, + expect.any(Function), + ); + }); + + it('should not initialize a git repository if in one already', async () => { + const destinationDir = 'tmp/mockApp'; + + mockExec.mockImplementation((_command, _opts, callback) => { + callback(null, { stdout: '', stderr: '' }); + }); + + await expect(tryInitGitRepository(destinationDir)).resolves.toBe(false); + + expect(mockExec).toHaveBeenCalledTimes(1); + expect(mockExec).toHaveBeenNthCalledWith( + 1, + 'git rev-parse --is-inside-work-tree', + { cwd: destinationDir }, expect.any(Function), ); }); diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 1e30f9d33a..ff519912a8 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -28,15 +28,12 @@ import { import { exec as execCb } from 'child_process'; import { packageVersions } from './versions'; import { promisify } from 'util'; -import commandExists from 'command-exists'; import os from 'os'; const TASK_NAME_MAX_LENGTH = 14; const exec = promisify(execCb); export type GitConfig = { - name?: string; - email?: string; defaultBranch?: string; }; @@ -250,71 +247,59 @@ export async function moveAppTask( * * @throws if `exec` fails */ -export async function readGitConfig(): Promise { +export async function readGitConfig(): Promise { const tempDir = resolvePath(os.tmpdir(), 'git-temp-dir'); - const runCmd = (cmd: string) => - exec(cmd, { cwd: tempDir }).catch(error => { - process.stdout.write(error.stderr); - process.stdout.write(error.stdout); - throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); - }); - - const isGitAvailable = await commandExists('git').catch(() => false); - - if (!isGitAvailable) return {}; - try { await fs.mkdir(tempDir); - const [gitUsername, gitEmail] = await Promise.all([ - runCmd('git config user.name'), - runCmd('git config user.email'), - ]); + await exec('git init', { cwd: tempDir }); + await exec('git commit --allow-empty -m "Initial commit"', { + cwd: tempDir, + }); - const gitCredentials = Boolean( - gitUsername.stdout?.trim() && gitEmail.stdout?.trim(), - ); - - if (!gitCredentials) return {}; - - await runCmd('git init'); - await runCmd('git commit --allow-empty -m "Initial commit"'); - - const gitDefaultBranch = await runCmd( + const getDefaultBranch = await exec( 'git branch --format="%(refname:short)"', + { cwd: tempDir }, ); return { - name: gitUsername.stdout?.trim(), - email: gitEmail.stdout?.trim(), - defaultBranch: gitDefaultBranch.stdout?.trim(), + defaultBranch: getDefaultBranch.stdout?.trim() || undefined, }; } catch (error) { - throw new Error(`Failed to read git config, ${error}`); + return undefined; } finally { await fs.rm(tempDir, { recursive: true }); } } /** - * Initializes a git repository in the destination folder + * Initializes a git repository in the destination folder if possible * * @param dir - source path to initialize git repository in - * @throws if `exec` fails + * @returns true if git repository was initialized */ -export async function initGitRepository(dir: string) { - const runCmd = (cmd: string) => - exec(cmd).catch(error => { - process.stdout.write(error.stderr); - process.stdout.write(error.stdout); - throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); - }); +export async function tryInitGitRepository(dir: string) { + try { + // Check if we're already in a git repo + await exec('git rev-parse --is-inside-work-tree', { cwd: dir }); + return false; + } catch { + /* ignored */ + } - await Task.forItem('init', 'git repository', async () => { - process.chdir(dir); + try { + await exec('git init', { cwd: dir }); + await exec('git add .', { cwd: dir }); + await exec('git commit -m "Initial commit"', { cwd: dir }); + return true; + } catch (error) { + try { + await fs.rm(resolvePath(dir, '.git'), { recursive: true, force: true }); + } catch { + throw new Error('Failed to remove .git folder'); + } - await runCmd('git init'); - await runCmd('git commit --allow-empty -m "Initial commit"'); - }); + return false; + } } diff --git a/yarn.lock b/yarn.lock index e9f94623ae..71a2ade870 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3674,7 +3674,6 @@ __metadata: "@types/node": ^16.11.26 "@types/recursive-readdir": ^2.2.0 chalk: ^4.0.0 - command-exists: ^1.2.9 commander: ^9.1.0 fs-extra: 10.1.0 handlebars: ^4.7.3 From 5938d1244d715cbadb82e9934fadd0041d93e801 Mon Sep 17 00:00:00 2001 From: hillmandj Date: Mon, 24 Oct 2022 12:46:36 -0400 Subject: [PATCH 168/221] Fix Authenticate API Requests Example Signed-off-by: hillmandj --- contrib/docs/tutorials/authenticate-api-requests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index c8f4083a0c..6c5fabd709 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -75,7 +75,7 @@ export const createAuthMiddleware = async ( // Authorization header may be forwarded by plugin requests req.headers.authorization = `Bearer ${token}`; } - if (token && token !== req.cookies.token) { + if (token && token !== req.cookies?.token) { setTokenCookie(res, { token, secure, From 5691baea69cbc56442f3c70164404a17d0f8e335 Mon Sep 17 00:00:00 2001 From: Simon Ninon Date: Mon, 19 Sep 2022 15:11:52 -0700 Subject: [PATCH 169/221] plugin-techdocs: add group filtering support to EntityListDocsGrid Signed-off-by: Simon Ninon --- .changeset/dull-oranges-tap.md | 25 +++ docs/features/techdocs/how-to-guides.md | 106 ++++++++- plugins/techdocs/api-report.md | 15 +- .../Grids/EntityListDocsGrid.test.tsx | 203 ++++++++++++++++++ .../components/Grids/EntityListDocsGrid.tsx | 107 ++++++++- 5 files changed, 449 insertions(+), 7 deletions(-) create mode 100644 .changeset/dull-oranges-tap.md create mode 100644 plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx diff --git a/.changeset/dull-oranges-tap.md b/.changeset/dull-oranges-tap.md new file mode 100644 index 0000000000..946656f610 --- /dev/null +++ b/.changeset/dull-oranges-tap.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Add ability to configure filters when using EntityListDocsGrid + +The following example will render two sections of cards grid: + +- One section for documentations tagged as `recommended` +- One section for documentations tagged as `runbook` + +```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } +]}} /> +``` diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 4f2dc6c348..67b25c2f86 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -111,7 +111,7 @@ in Backstage. While a default table experience, similar to the one provided by the Catalog plugin, is made available for ease-of-use, it's possible for you to provide a completely custom experience, tailored to the needs of your organization. For example, TechDocs comes with an alternative grid based layout -(``). +(``) and panel layout (`TechDocsCustomHome`). This is done in your `app` package. By default, you might see something like this in your `App.tsx`: @@ -126,18 +126,120 @@ const AppRoutes = () => { }; ``` +### Using TechDocsCustomHome + +You can easily customize the TechDocs home page using TechDocs panel layout +(``). + +Modify your `App.tsx` as follows: + +```tsx +import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; +//... + +const techDocsTabsConfig = [ + { + label: "Recommended Documentation", + panels: [ + { + title: 'Golden Path', + description: 'Documentation about standards to follow', + panelType: 'DocsCardGrid', + filterPredicate: entity => entity?.metadata?.tags?.includes('recommended') ?? false, + } + ] + } +] + +const AppRoutes = () => { + + }> + ; +}; +``` + +### Building a Custom home page + But you can replace `` with any React component, which will be rendered in its place. Most likely, you would want to create and maintain such a component in a new directory at `packages/app/src/components/techdocs`, and import and use it in `App.tsx`: +For example, you can define the following Custom home page component: + +```tsx +import React from 'react'; + +import { Content } from '@backstage/core-components'; +import { + CatalogFilterLayout, + EntityOwnerPicker, + EntityTagPicker, + UserListPicker, + EntityListProvider, +} from '@backstage/plugin-catalog-react'; +import { + TechDocsPageWrapper, + TechDocsPicker, +} from '@backstage/plugin-techdocs'; +import { Entity } from '@backstage/catalog-model'; + +import { + EntityListDocsGrid, + DocsGroupConfig, +} from '@backstage/plugin-techdocs'; + +export type CustomTechDocsHomeProps = { + groups?: Array<{ + title: React.ReactNode; + filterPredicate: (entity: Entity) => boolean; + }>; +}; + +export const CustomTechDocsHome = ({ groups }: CustomTechDocsHomeProps) => { + return ( + + + + + + + + + + + + + + + + + + ); +}; +``` + +Then you can add the following to your `App.tsx`: + ```tsx import { CustomTechDocsHome } from './components/techdocs/CustomTechDocsHome'; // ... const AppRoutes = () => { }> - + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: 'My Docs', + filterPredicate: 'ownedByUser', + }, + ]} + /> ; }; diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index b2f0331a9b..d1c05f5b8e 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -59,6 +59,12 @@ export type DocsCardGridProps = { entities: Entity[] | undefined; }; +// @public +export type DocsGroupConfig = { + title: React_2.ReactNode; + filterPredicate: ((entity: Entity) => boolean) | string; +}; + // @public export const DocsTable: { (props: DocsTableProps): JSX.Element | null; @@ -112,7 +118,14 @@ export const EmbeddedDocsRouter: ( ) => JSX.Element | null; // @public -export const EntityListDocsGrid: () => JSX.Element; +export const EntityListDocsGrid: ({ + groups, +}: EntityListDocsGridPageProps) => JSX.Element; + +// @public +export type EntityListDocsGridPageProps = { + groups?: DocsGroupConfig[]; +}; // @public export const EntityListDocsTable: { diff --git a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx new file mode 100644 index 0000000000..effd8fe0c5 --- /dev/null +++ b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx @@ -0,0 +1,203 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; +import { + ConfigApi, + configApiRef, + storageApiRef, +} from '@backstage/core-plugin-api'; +import { + CatalogApi, + catalogApiRef, + starredEntitiesApiRef, + MockEntityListContextProvider, + MockStarredEntitiesApi, +} from '@backstage/plugin-catalog-react'; +import { + MockStorageApi, + renderInTestApp, + TestApiRegistry, +} from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { rootDocsRouteRef } from '../../../routes'; +import { EntityListDocsGrid } from './EntityListDocsGrid'; + +const entities = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Documentation #1', + namespace: 'default', + }, + spec: { + type: 'documentation', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Documentation #2', + namespace: 'default', + }, + spec: { + type: 'documentation', + }, + }, +]; + +const mockCatalogApi = { + getEntityByRef: () => Promise.resolve(), + getEntities: async () => ({ + items: entities, + }), +} as Partial; + +describe('Entity List Docs Grid', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + const configApi: ConfigApi = new ConfigReader({ + organization: { + name: 'My Company', + }, + }); + + const storageApi = MockStorageApi.create(); + + const apiRegistry = TestApiRegistry.from( + [catalogApiRef, mockCatalogApi], + [configApiRef, configApi], + [storageApiRef, storageApi], + [starredEntitiesApiRef, new MockStarredEntitiesApi()], + ); + + it('should render all entitites without filtering', async () => { + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.queryByText('All Documentation')).toBeInTheDocument(); + expect(await screen.queryByText('Documentation #1')).toBeInTheDocument(); + expect(await screen.queryByText('Documentation #2')).toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument(); + }); + + it('should render only filtered entities with filtering', async () => { + await renderInTestApp( + + + + entity.metadata.name === 'Documentation #1', + }, + ]} + /> + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + await screen.queryByText('Curated Documentation'), + ).toBeInTheDocument(); + expect(await screen.queryByText('Documentation #1')).toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #2'), + ).not.toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument(); + }); + + it('should render nothing with filtering yielding no result', async () => { + await renderInTestApp( + + + + entity.metadata.name === 'Documentation #3', + }, + ]} + /> + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + await screen.queryByText('Curated Documentation'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #1'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #2'), + ).not.toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument(); + }); + + it('should render an error without any documentation and without filtering', async () => { + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + await screen.queryByText('All Documentation'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #1'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #2'), + ).not.toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx index 348104f5cc..d6b2bf3391 100644 --- a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx @@ -15,20 +15,95 @@ */ import { DocsCardGrid } from './DocsCardGrid'; +import { Entity } from '@backstage/catalog-model'; import { CodeSnippet, + Content, + ContentHeader, + Link, Progress, WarningPanel, } from '@backstage/core-components'; -import { useEntityList } from '@backstage/plugin-catalog-react'; +import { + useEntityList, + useEntityOwnership, +} from '@backstage/plugin-catalog-react'; +import { Typography } from '@material-ui/core'; import React from 'react'; +/** + * Props for {@link EntityListDocsGrid} + * + * @public + */ +export type DocsGroupConfig = { + title: React.ReactNode; + filterPredicate: ((entity: Entity) => boolean) | string; +}; + +/** + * Props for {@link EntityListDocsGrid} + * + * @public + */ +export type EntityListDocsGridPageProps = { + groups?: DocsGroupConfig[]; +}; + +const allEntitiesGroup: DocsGroupConfig = { + title: 'All Documentation', + filterPredicate: () => true, +}; + +const EntityListDocsGridGroup = ({ + entities, + group, +}: { + group: DocsGroupConfig; + entities: Entity[]; +}) => { + const { loading: loadingOwnership, isOwnedEntity } = useEntityOwnership(); + + const shownEntities = entities.filter(entity => { + if (group.filterPredicate === 'ownedByUser') { + if (loadingOwnership) { + return false; + } + return isOwnedEntity(entity); + } + + return ( + typeof group.filterPredicate === 'function' && + group.filterPredicate(entity) + ); + }); + + const titleComponent: React.ReactNode = (() => { + return typeof group.title === 'string' ? ( + + ) : ( + group.title + ); + })(); + + if (shownEntities.length === 0) { + return null; + } + + return ( + + {titleComponent} + + + ); +}; + /** * Component responsible to get entities from entity list context and pass down to DocsCardGrid * * @public */ -export const EntityListDocsGrid = () => { +export const EntityListDocsGrid = ({ groups }: EntityListDocsGridPageProps) => { const { loading, error, entities } = useEntityList(); if (error) { @@ -42,15 +117,39 @@ export const EntityListDocsGrid = () => { ); } - if (loading || !entities) { + if (loading) { return ; } + if (entities.length === 0) { + return ( +
+ + No documentation found that match your filter. Learn more about{' '} + + publishing documentation + + . + +
+ ); + } + entities.sort((a, b) => (a.metadata.title ?? a.metadata.name).localeCompare( b.metadata.title ?? b.metadata.name, ), ); - return ; + return ( + + {(groups || [allEntitiesGroup]).map((group, index: number) => ( + + ))} + + ); }; From d0256a4d4e25df7779e896c5231668b22d833eef Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 24 Oct 2022 13:34:44 -0400 Subject: [PATCH 170/221] microsite/plugins: fix authors and filename Signed-off-by: Patrik Oldsberg --- ...stage-plugin-api-linter => backstage-plugin-api-linter.yaml} | 0 microsite/data/plugins/git-release-manager.yaml | 2 +- microsite/data/plugins/prometheus.yaml | 2 +- microsite/data/plugins/tech-insights.yaml | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename microsite/data/plugins/{backstage-plugin-api-linter => backstage-plugin-api-linter.yaml} (100%) diff --git a/microsite/data/plugins/backstage-plugin-api-linter b/microsite/data/plugins/backstage-plugin-api-linter.yaml similarity index 100% rename from microsite/data/plugins/backstage-plugin-api-linter rename to microsite/data/plugins/backstage-plugin-api-linter.yaml diff --git a/microsite/data/plugins/git-release-manager.yaml b/microsite/data/plugins/git-release-manager.yaml index 0541894e47..e7000be694 100644 --- a/microsite/data/plugins/git-release-manager.yaml +++ b/microsite/data/plugins/git-release-manager.yaml @@ -1,6 +1,6 @@ --- title: GitHub Release Manager -author: '@Spotify' +author: Spotify authorUrl: https://github.com/spotify category: Release management description: Manage releases without having to juggle git commands. diff --git a/microsite/data/plugins/prometheus.yaml b/microsite/data/plugins/prometheus.yaml index 2a2fb3729c..f7a079d44c 100644 --- a/microsite/data/plugins/prometheus.yaml +++ b/microsite/data/plugins/prometheus.yaml @@ -1,6 +1,6 @@ --- title: Prometheus -author: Roadie +author: roadie.io authorUrl: https://roadie.io category: Monitoring description: Prometheus plugin provides visualization of Prometheus metrics and alerts diff --git a/microsite/data/plugins/tech-insights.yaml b/microsite/data/plugins/tech-insights.yaml index 009cbce29e..fbf96df3e6 100644 --- a/microsite/data/plugins/tech-insights.yaml +++ b/microsite/data/plugins/tech-insights.yaml @@ -1,6 +1,6 @@ --- title: Tech Insights -author: '@RoadieHQ' +author: roadie.io authorUrl: https://github.com/RoadieHQ category: Discovery description: Visualize, understand and optimize your team's tech health. From aad90cab64916303b95853aa630d6b2681531c2d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 24 Oct 2022 14:06:09 -0400 Subject: [PATCH 171/221] github-pull-requests-board: sync changelog Signed-off-by: Patrik Oldsberg --- .changeset/curvy-islands-marry.md | 5 ----- plugins/github-pull-requests-board/CHANGELOG.md | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 .changeset/curvy-islands-marry.md diff --git a/.changeset/curvy-islands-marry.md b/.changeset/curvy-islands-marry.md deleted file mode 100644 index 2d3bad31ac..0000000000 --- a/.changeset/curvy-islands-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-github-pull-requests-board': patch ---- - -Replace the momentjs dependency with luxon. diff --git a/plugins/github-pull-requests-board/CHANGELOG.md b/plugins/github-pull-requests-board/CHANGELOG.md index d5c65069dd..7da9bf1098 100644 --- a/plugins/github-pull-requests-board/CHANGELOG.md +++ b/plugins/github-pull-requests-board/CHANGELOG.md @@ -4,6 +4,7 @@ ### Patch Changes +- 80d75adf3a: Replace the momentjs dependency with luxon. - 719ccbb963: Properly filter on relations instead of the spec, when finding by owner - Updated dependencies - @backstage/catalog-model@1.1.2 From 55227712ddd858485b3af3863a65769e4c90a868 Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Fri, 14 Oct 2022 00:03:46 +0200 Subject: [PATCH 172/221] fix(backend): fix certificate validation Signed-off-by: Johannes Grumboeck --- .changeset/forty-bags-trade.md | 5 +++++ .../src/service/lib/hostFactory.ts | 18 ++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 .changeset/forty-bags-trade.md diff --git a/.changeset/forty-bags-trade.md b/.changeset/forty-bags-trade.md new file mode 100644 index 0000000000..4908134498 --- /dev/null +++ b/.changeset/forty-bags-trade.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Generated development HTTPS backend certificate is now checked for expiration date instead of file age. diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 572d8483b1..943942d255 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -22,7 +22,7 @@ import * as https from 'https'; import { Logger } from 'winston'; import { HttpsSettings } from './config'; -const ALMOST_MONTH_IN_MS = 25 * 24 * 60 * 60 * 1000; +const FIVE_DAYS_IN_MS = 5 * 24 * 60 * 60 * 1000; const IP_HOSTNAME_REGEX = /:|^\d+\.\d+\.\d+\.\d+$/; @@ -95,15 +95,21 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { } let cert = undefined; + let remainingMs = 0; if (await fs.pathExists(certPath)) { - const stat = await fs.stat(certPath); - const ageMs = Date.now() - stat.ctimeMs; - if (stat.isFile() && ageMs < ALMOST_MONTH_IN_MS) { - cert = await fs.readFile(certPath); + cert = await fs.readFile(certPath); + try { + const forge = require('node-forge') + const crt = forge.pki.certificateFromPem(cert) + const crtTimestamp = Date.parse(crt.validity.notAfter); + remainingMs = crtTimestamp - Date.now(); + } catch (error) { + logger.warn(`Unable to parse self-signed certificate. ${error}`); + remainingMs = 0 } } - if (cert) { + if (remainingMs > FIVE_DAYS_IN_MS) { logger?.info('Using existing self-signed certificate'); return { key: cert, From 88ac2dd22066e0cb3419055649b1f90cfc0756e1 Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Fri, 14 Oct 2022 00:09:27 +0200 Subject: [PATCH 173/221] fix(backend): Object is possibly 'undefined' Signed-off-by: Johannes Grumboeck --- packages/backend-common/src/service/lib/hostFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 943942d255..917458896e 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -104,7 +104,7 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { const crtTimestamp = Date.parse(crt.validity.notAfter); remainingMs = crtTimestamp - Date.now(); } catch (error) { - logger.warn(`Unable to parse self-signed certificate. ${error}`); + logger?.warn(`Unable to parse self-signed certificate. ${error}`); remainingMs = 0 } } From 7e739e6009f630ebb612d18f92925b339c482b98 Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Fri, 14 Oct 2022 00:28:47 +0200 Subject: [PATCH 174/221] fix(backend): Type is not assignable Signed-off-by: Johannes Grumboeck --- packages/backend-common/src/service/lib/hostFactory.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 917458896e..a4bd178de7 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -107,9 +107,13 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { logger?.warn(`Unable to parse self-signed certificate. ${error}`); remainingMs = 0 } + if (remainingMs < FIVE_DAYS_IN_MS) { + // Reset certificate if expiration is nearly over + cert = undefined + } } - if (remainingMs > FIVE_DAYS_IN_MS) { + if (cert) { logger?.info('Using existing self-signed certificate'); return { key: cert, From 35f292c9f7532e8a145e10141d73fdd02cb26f0e Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Fri, 14 Oct 2022 00:54:28 +0200 Subject: [PATCH 175/221] fix(backend): run prettier and yarn add node-forge Signed-off-by: Johannes Grumboeck --- packages/backend-common/package.json | 1 + packages/backend-common/src/service/lib/hostFactory.ts | 8 ++++---- yarn.lock | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 2a7e307774..b9cda38b51 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -75,6 +75,7 @@ "morgan": "^1.10.0", "node-abort-controller": "^3.0.1", "node-fetch": "^2.6.7", + "node-forge": "^1.3.1", "raw-body": "^2.4.1", "request": "^2.88.2", "selfsigned": "^2.0.0", diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index a4bd178de7..16bfc6adf6 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -99,17 +99,17 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { if (await fs.pathExists(certPath)) { cert = await fs.readFile(certPath); try { - const forge = require('node-forge') - const crt = forge.pki.certificateFromPem(cert) + const forge = require('node-forge'); + const crt = forge.pki.certificateFromPem(cert); const crtTimestamp = Date.parse(crt.validity.notAfter); remainingMs = crtTimestamp - Date.now(); } catch (error) { logger?.warn(`Unable to parse self-signed certificate. ${error}`); - remainingMs = 0 + remainingMs = 0; } if (remainingMs < FIVE_DAYS_IN_MS) { // Reset certificate if expiration is nearly over - cert = undefined + cert = undefined; } } diff --git a/yarn.lock b/yarn.lock index e9f94623ae..378e432b08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3201,6 +3201,7 @@ __metadata: mysql2: ^2.2.5 node-abort-controller: ^3.0.1 node-fetch: ^2.6.7 + node-forge: ^1.3.1 raw-body: ^2.4.1 recursive-readdir: ^2.2.2 request: ^2.88.2 From a17a7a6cc1d8d2d97b1087efa9feabb97260ef2f Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Sun, 23 Oct 2022 21:49:51 +0200 Subject: [PATCH 176/221] chore: use import for node-forge instead of require Signed-off-by: Johannes Grumboeck --- packages/backend-common/package.json | 1 + packages/backend-common/src/service/lib/hostFactory.ts | 6 +++--- yarn.lock | 10 ++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b9cda38b51..6791d7f322 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -49,6 +49,7 @@ "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", + "@types/node-forge": "^1.3.0", "@types/webpack-env": "^1.15.2", "archiver": "^5.0.2", "aws-sdk": "^2.840.0", diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 16bfc6adf6..ecd214814d 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -21,6 +21,7 @@ import * as http from 'http'; import * as https from 'https'; import { Logger } from 'winston'; import { HttpsSettings } from './config'; +import * as forge from 'node-forge'; const FIVE_DAYS_IN_MS = 5 * 24 * 60 * 60 * 1000; @@ -99,9 +100,8 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { if (await fs.pathExists(certPath)) { cert = await fs.readFile(certPath); try { - const forge = require('node-forge'); - const crt = forge.pki.certificateFromPem(cert); - const crtTimestamp = Date.parse(crt.validity.notAfter); + const crt = forge.pki.certificateFromPem(cert.toString()); + const crtTimestamp = Date.parse(crt.validity.notAfter.toString()); remainingMs = crtTimestamp - Date.now(); } catch (error) { logger?.warn(`Unable to parse self-signed certificate. ${error}`); diff --git a/yarn.lock b/yarn.lock index 378e432b08..06939594f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3164,6 +3164,7 @@ __metadata: "@types/minimist": ^1.2.0 "@types/mock-fs": ^4.13.0 "@types/morgan": ^1.9.0 + "@types/node-forge": ^1.3.0 "@types/recursive-readdir": ^2.2.0 "@types/stoppable": ^1.1.0 "@types/supertest": ^2.0.8 @@ -14014,6 +14015,15 @@ __metadata: languageName: node linkType: hard +"@types/node-forge@npm:^1.3.0": + version: 1.3.0 + resolution: "@types/node-forge@npm:1.3.0" + dependencies: + "@types/node": "*" + checksum: f811885f997fbeebb0df2db8249b9b288ad5f5573beaecf6d323b019bb9a4c12f1476a8974c934f62329862f29986c116263a50aef0008364cc3f9867672a5a0 + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": version: 17.0.25 resolution: "@types/node@npm:17.0.25" From 374e65c6798cc1f619773d4ef842a0afee686fae Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Sun, 23 Oct 2022 22:14:17 +0200 Subject: [PATCH 177/221] feat: own function for certificate expiration Signed-off-by: Johannes Grumboeck --- .../src/service/lib/hostFactory.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index ecd214814d..b725b5e648 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -83,6 +83,17 @@ export async function createHttpsServer( return https.createServer(credentials, app) as http.Server; } +function getCertificateExpiration(cert: string, logger?: Logger) { + try { + const crt = forge.pki.certificateFromPem(cert); + const crtTimestamp = Date.parse(crt.validity.notAfter.toString()); + return crtTimestamp - Date.now(); + } catch (error) { + logger?.warn(`Unable to parse self-signed certificate. ${error}`); + return 0; + } +} + async function getGeneratedCertificate(hostname: string, logger?: Logger) { const hasModules = await fs.pathExists('node_modules'); let certPath; @@ -95,25 +106,14 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { certPath = resolvePath('.dev-cert.pem'); } - let cert = undefined; + let cert = undefined let remainingMs = 0; if (await fs.pathExists(certPath)) { cert = await fs.readFile(certPath); - try { - const crt = forge.pki.certificateFromPem(cert.toString()); - const crtTimestamp = Date.parse(crt.validity.notAfter.toString()); - remainingMs = crtTimestamp - Date.now(); - } catch (error) { - logger?.warn(`Unable to parse self-signed certificate. ${error}`); - remainingMs = 0; - } - if (remainingMs < FIVE_DAYS_IN_MS) { - // Reset certificate if expiration is nearly over - cert = undefined; - } + remainingMs = getCertificateExpiration(cert.toString(), logger); } - if (cert) { + if (cert && remainingMs > FIVE_DAYS_IN_MS) { logger?.info('Using existing self-signed certificate'); return { key: cert, From 296e3ac84a4b1711690afb19886b05b982f9d997 Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Sun, 23 Oct 2022 22:20:34 +0200 Subject: [PATCH 178/221] fix: @types/node-forge needs to be dev-dependency Signed-off-by: Johannes Grumboeck --- packages/backend-common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 6791d7f322..8def408fca 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -49,7 +49,6 @@ "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", - "@types/node-forge": "^1.3.0", "@types/webpack-env": "^1.15.2", "archiver": "^5.0.2", "aws-sdk": "^2.840.0", @@ -107,6 +106,7 @@ "@types/minimist": "^1.2.0", "@types/mock-fs": "^4.13.0", "@types/morgan": "^1.9.0", + "@types/node-forge": "^1.3.0", "@types/recursive-readdir": "^2.2.0", "@types/stoppable": "^1.1.0", "@types/supertest": "^2.0.8", From 6e16a040ae358030e4bb004022bd2a9b96ce3436 Mon Sep 17 00:00:00 2001 From: Johannes Grumboeck Date: Mon, 24 Oct 2022 20:11:45 +0200 Subject: [PATCH 179/221] fix: run prettier Signed-off-by: Johannes Grumboeck --- packages/backend-common/src/service/lib/hostFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index b725b5e648..616120e868 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -106,7 +106,7 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { certPath = resolvePath('.dev-cert.pem'); } - let cert = undefined + let cert = undefined; let remainingMs = 0; if (await fs.pathExists(certPath)) { cert = await fs.readFile(certPath); From 84bb330ce9c6938169c6fb5e02e48e7b4ca7f99e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 24 Oct 2022 16:29:15 -0400 Subject: [PATCH 180/221] .github/workflows: update PR stale message to not mislead Signed-off-by: Patrik Oldsberg --- .github/workflows/automate_stale.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index 35b0a8c0a6..51bf41d61a 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -22,7 +22,8 @@ jobs: stale-pr-message: > This PR has been automatically marked as stale because it has not had recent activity from the author. It will be closed if no further activity occurs. - If you are the author and the PR has been closed, feel free to re-open the PR and continue the contribution! + If the PR was closed and you want it re-opened, let us know + and we'll re-open the PR so that you can continue the contribution! days-before-pr-stale: 7 days-before-pr-close: 5 exempt-pr-labels: after-vacations,will-fix From 52f91da67876e911e7ce05cced659252a76bbe4f Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Mon, 24 Oct 2022 22:21:12 -0600 Subject: [PATCH 181/221] Adding notes about using a custom callbackUrl with the GitLab Auth provider Signed-off-by: Josh Maxwell --- docs/auth/gitlab/provider.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index c587f60aa9..15488bc4c1 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -35,6 +35,8 @@ auth: clientSecret: ${AUTH_GITLAB_CLIENT_SECRET} ## uncomment if using self-hosted GitLab # audience: https://gitlab.company.com + ## uncomment if using a custom redirect URI + # callbackUrl: https://${BASE_URL}/api/auth/gitlab/handler/frame ``` The GitLab provider is a structure with three configuration keys: @@ -44,6 +46,9 @@ The GitLab provider is a structure with three configuration keys: - `clientSecret`: The Application secret - `audience` (optional): The base URL for the self-hosted GitLab instance, e.g. `https://gitlab.company.com` +- `callbackUrl` (optional): The URL matching the Redirect URI registered when creating your GitLab OAuth App, e.g. + `https://$backstage.acme.corp/api/auth/gitlab/handler/frame` + Note: Due to a peculiarity with GitLab OAuth, ensure there is no trailing `/` after 'frame' in the URL. ## Adding the provider to the Backstage frontend From 89e86f467f6a100c09dcbec5d4e0b2a77a5f0214 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 25 Oct 2022 10:03:16 +0200 Subject: [PATCH 182/221] chore: refactor https expiration logic Signed-off-by: Johan Haals --- .../src/service/lib/hostFactory.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 616120e868..089bf610ed 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -21,7 +21,7 @@ import * as http from 'http'; import * as https from 'https'; import { Logger } from 'winston'; import { HttpsSettings } from './config'; -import * as forge from 'node-forge'; +import forge from 'node-forge'; const FIVE_DAYS_IN_MS = 5 * 24 * 60 * 60 * 1000; @@ -106,19 +106,16 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { certPath = resolvePath('.dev-cert.pem'); } - let cert = undefined; - let remainingMs = 0; if (await fs.pathExists(certPath)) { - cert = await fs.readFile(certPath); - remainingMs = getCertificateExpiration(cert.toString(), logger); - } - - if (cert && remainingMs > FIVE_DAYS_IN_MS) { - logger?.info('Using existing self-signed certificate'); - return { - key: cert, - cert: cert, - }; + const cert = await fs.readFile(certPath); + const remainingMs = getCertificateExpiration(cert.toString(), logger); + if (remainingMs > FIVE_DAYS_IN_MS) { + logger?.info('Using existing self-signed certificate'); + return { + key: cert, + cert, + }; + } } logger?.info('Generating new self-signed certificate'); From b676e1ddfee768189557c3921cd98253f52f79b2 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 25 Oct 2022 10:34:28 +0200 Subject: [PATCH 183/221] actions/snyk chore: move old-space-size to relevant step Signed-off-by: Johan Haals --- .github/workflows/sync_snyk-github-issues.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 10b3b24aed..0e0d4c203e 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -14,9 +14,6 @@ jobs: matrix: node-version: [14.x] - env: - NODE_OPTIONS: --max-old-space-size=7168 - steps: - uses: actions/checkout@v3 @@ -42,6 +39,7 @@ jobs: json: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + NODE_OPTIONS: --max-old-space-size=7168 - name: Update Github issues run: yarn ts-node scripts/snyk-github-issue-sync.ts env: From 1808e924e70c4f2215da50b3369c70bf896c1982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 25 Oct 2022 10:44:42 +0200 Subject: [PATCH 184/221] Update .changeset/shaggy-birds-happen.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johan Haals Signed-off-by: Fredrik Adelöw --- .changeset/shaggy-birds-happen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/shaggy-birds-happen.md b/.changeset/shaggy-birds-happen.md index 049a3cb3aa..7f7a691c85 100644 --- a/.changeset/shaggy-birds-happen.md +++ b/.changeset/shaggy-birds-happen.md @@ -2,4 +2,4 @@ '@backstage/plugin-github-issues': minor --- -BREAKING: Changed the casing of all exported types to have a lowercase "h" in "github". E.g. "GitHubIssuesPage" was renamed to "GithubIssuesPage". Please rename your imports where necessary. +**BREAKING**: Changed the casing of all exported types to have a lowercase "h" in "github". E.g. "GitHubIssuesPage" was renamed to "GithubIssuesPage". Please rename your imports where necessary. From 7573b65232cd2ba80b43396e46aa353e4bbbda1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 25 Oct 2022 10:42:11 +0200 Subject: [PATCH 185/221] get rid of circular imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/orange-trees-peel.md | 14 ++++++++++++++ packages/catalog-model/src/location/helpers.ts | 3 +-- .../OverflowTooltip/OverflowTooltip.test.tsx | 3 +-- .../src/components/Table/Table.stories.tsx | 4 ++-- .../src/layout/HeaderTabs/HeaderTabs.test.tsx | 6 +++--- .../src/layout/Page/Page.stories.tsx | 18 ++++++++---------- .../src/layout/Sidebar/Bar.test.tsx | 14 +++++--------- .../src/layout/Sidebar/Items.tsx | 9 +++------ .../src/layout/Sidebar/MobileSidebar.test.tsx | 12 +++++------- .../src/layout/Sidebar/Sidebar.stories.tsx | 14 +++++++------- .../src/layout/Sidebar/SidebarGroup.test.tsx | 4 +++- .../src/layout/Sidebar/SidebarGroup.tsx | 2 +- .../src/gerrit/GerritIntegration.ts | 4 ++-- packages/integration/src/gerrit/core.test.ts | 6 +++--- packages/integration/src/gerrit/core.ts | 5 +++-- plugins/auth-node/src/DefaultIdentityClient.ts | 5 +++-- .../AzureSitesOverviewTable.test.tsx | 2 +- .../CalendarCard/HomePageCalendar.test.tsx | 8 ++++---- .../components/CalendarCard/SignInContent.tsx | 5 ++--- .../EntityPeriskopErrorsCard.tsx | 2 +- plugins/permission-common/src/types/api.ts | 3 +-- .../builtin/github/githubIssuesLabel.test.ts | 2 +- .../builtin/github/githubWebhook.test.ts | 2 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 13 +++++++------ .../src/cache/cacheMiddleware.test.ts | 3 ++- .../reader/transformers/addGitFeedbackLink.ts | 2 +- .../reader/transformers/addSidebarToggle.ts | 2 +- .../transformers/scrollIntoNavigation.test.ts | 2 +- plugins/xcmetrics/src/utils/buildData.ts | 2 +- 29 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 .changeset/orange-trees-peel.md diff --git a/.changeset/orange-trees-peel.md b/.changeset/orange-trees-peel.md new file mode 100644 index 0000000000..344dd88cb9 --- /dev/null +++ b/.changeset/orange-trees-peel.md @@ -0,0 +1,14 @@ +--- +'@backstage/catalog-model': patch +'@backstage/core-components': patch +'@backstage/integration': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-gcalendar': patch +'@backstage/plugin-periskop': patch +'@backstage/plugin-permission-common': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-xcmetrics': patch +--- + +Internal refactor of imports to avoid circular dependencies diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts index 8f8acc3b1d..6f9800c5a2 100644 --- a/packages/catalog-model/src/location/helpers.ts +++ b/packages/catalog-model/src/location/helpers.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { ANNOTATION_SOURCE_LOCATION } from '.'; import { Entity, stringifyEntityRef } from '../entity'; -import { ANNOTATION_LOCATION } from './annotation'; +import { ANNOTATION_LOCATION, ANNOTATION_SOURCE_LOCATION } from './annotation'; /** * Parses a string form location reference. diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx index c61069f810..2fea7fa5e1 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx @@ -16,8 +16,7 @@ import React from 'react'; import { render } from '@testing-library/react'; - -import { OverflowTooltip } from '.'; +import { OverflowTooltip } from './OverflowTooltip'; describe('', () => { it('renders without exploding', async () => { diff --git a/packages/core-components/src/components/Table/Table.stories.tsx b/packages/core-components/src/components/Table/Table.stories.tsx index b057d0a12c..807d58057d 100644 --- a/packages/core-components/src/components/Table/Table.stories.tsx +++ b/packages/core-components/src/components/Table/Table.stories.tsx @@ -17,8 +17,8 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { Link } from '../Link'; -import { SubvalueCell, Table, TableColumn } from '.'; -import { TableFilter } from './Table'; +import { SubvalueCell } from './SubvalueCell'; +import { Table, TableColumn, TableFilter } from './Table'; export default { title: 'Data Display/Table', diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx index f9348ffa9a..a6bfc79040 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; -import { HeaderTabs } from '.'; -import { makeStyles } from '@material-ui/core/styles'; import Badge from '@material-ui/core/Badge'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { HeaderTabs } from './HeaderTabs'; const mockTabs = [ { id: 'overview', label: 'Overview' }, diff --git a/packages/core-components/src/layout/Page/Page.stories.tsx b/packages/core-components/src/layout/Page/Page.stories.tsx index 12ddd51d04..289f0a6c0d 100644 --- a/packages/core-components/src/layout/Page/Page.stories.tsx +++ b/packages/core-components/src/layout/Page/Page.stories.tsx @@ -14,22 +14,13 @@ * limitations under the License. */ +import { wrapInTestApp } from '@backstage/test-utils'; import Box from '@material-ui/core/Box'; import Chip from '@material-ui/core/Chip'; import Grid from '@material-ui/core/Grid'; import Link from '@material-ui/core/Link'; import Typography from '@material-ui/core/Typography'; import React, { useState } from 'react'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { - Content, - ContentHeader, - Header, - HeaderLabel, - HeaderTabs, - InfoCard, - Page, -} from '..'; import { GaugeCard, StatusOK, @@ -38,6 +29,13 @@ import { TableColumn, TrendLine, } from '../../components'; +import { Content } from '../Content'; +import { ContentHeader } from '../ContentHeader'; +import { Header } from '../Header'; +import { HeaderLabel } from '../HeaderLabel'; +import { HeaderTabs } from '../HeaderTabs'; +import { InfoCard } from '../InfoCard'; +import { Page } from '../Page'; export default { title: 'Plugins/Examples', diff --git a/packages/core-components/src/layout/Sidebar/Bar.test.tsx b/packages/core-components/src/layout/Sidebar/Bar.test.tsx index 87e62f15e0..fb92e491f8 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.test.tsx @@ -22,15 +22,11 @@ import MenuBookIcon from '@material-ui/icons/MenuBook'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { - Sidebar, - SidebarExpandButton, - SidebarItem, - SidebarSearchField, - SidebarPinStateProvider, - SidebarSubmenu, - SidebarSubmenuItem, -} from '.'; +import { Sidebar } from './Bar'; +import { SidebarExpandButton, SidebarItem, SidebarSearchField } from './Items'; +import { SidebarPinStateProvider } from './SidebarPinStateContext'; +import { SidebarSubmenu } from './SidebarSubmenu'; +import { SidebarSubmenuItem } from './SidebarSubmenuItem'; async function renderScalableSidebar() { await renderInTestApp( diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 4bc972e272..fe8dede499 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -53,16 +53,13 @@ import { SidebarItemWithSubmenuContext, SidebarConfig, } from './config'; -import { - SidebarSubmenuItemProps, - SidebarSubmenuProps, - SidebarSubmenu, -} from '.'; +import { SidebarSubmenuProps, SidebarSubmenu } from './SidebarSubmenu'; import DoubleArrowLeft from './icons/DoubleArrowLeft'; import DoubleArrowRight from './icons/DoubleArrowRight'; import { isLocationMatch } from './utils'; import { Location } from 'history'; import { useSidebarOpenState } from './SidebarOpenStateContext'; +import { SidebarSubmenuItemProps } from './SidebarSubmenuItem'; /** @public */ export type SidebarItemClassKey = @@ -214,7 +211,7 @@ function useMemoStyles(sidebarConfig: SidebarConfig) { /** * Evaluates the routes of the SubmenuItems & nested DropdownItems. - * The reeveluation is only triggered, if the `locationPathname` changes, as `useElementFilter` uses memorization. + * The reevaluation is only triggered, if the `locationPathname` changes, as `useElementFilter` uses memorization. * * @param submenu SidebarSubmenu component * @param location Location diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx index 8dd06b6e2b..7012afabf1 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx @@ -21,13 +21,11 @@ import LayersIcon from '@material-ui/icons/Layers'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { - MobileSidebar, - Sidebar, - SidebarGroup, - SidebarItem, - SidebarPage, -} from '.'; +import { Sidebar } from './Bar'; +import { SidebarItem } from './Items'; +import { MobileSidebar } from './MobileSidebar'; +import { SidebarPage } from './Page'; +import { SidebarGroup } from './SidebarGroup'; const MobileSidebarWithGroups = () => ( diff --git a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx index 9b5d76a32c..8daf7b69b6 100644 --- a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx @@ -24,19 +24,19 @@ import CloudQueueIcon from '@material-ui/icons/CloudQueue'; import AcUnitIcon from '@material-ui/icons/AcUnit'; import AppsIcon from '@material-ui/icons/Apps'; import React, { ComponentType } from 'react'; +import { SidebarPage } from './Page'; +import { Sidebar } from './Bar'; +import { SidebarGroup } from './SidebarGroup'; import { - Sidebar, SidebarDivider, - SidebarGroup, SidebarExpandButton, - SidebarIntro, SidebarItem, - SidebarPage, SidebarSearchField, SidebarSpace, - SidebarSubmenu, - SidebarSubmenuItem, -} from '.'; +} from './Items'; +import { SidebarIntro } from './Intro'; +import { SidebarSubmenu } from './SidebarSubmenu'; +import { SidebarSubmenuItem } from './SidebarSubmenuItem'; const routeRef = createRouteRef({ id: 'storybook.test-route', diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx index b8f1eb5ebc..ce60b8cb8a 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx @@ -20,8 +20,10 @@ import LayersIcon from '@material-ui/icons/Layers'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import { fireEvent } from '@testing-library/react'; import React from 'react'; +import { SidebarItem } from './Items'; import { MobileSidebarContext } from './MobileSidebar'; -import { SidebarGroup, SidebarItem, SidebarPage } from '.'; +import { SidebarPage } from './Page'; +import { SidebarGroup } from './SidebarGroup'; const SidebarGroupWithItems = () => ( diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx index cb9c786ea9..5254199563 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx @@ -22,10 +22,10 @@ import BottomNavigationAction, { import { makeStyles } from '@material-ui/core/styles'; import React, { useContext } from 'react'; import { useLocation } from 'react-router-dom'; -import { useSidebarPinState } from '.'; import { Link } from '../../components'; import { SidebarConfigContext, SidebarConfig } from './config'; import { MobileSidebarContext } from './MobileSidebar'; +import { useSidebarPinState } from './SidebarPinStateContext'; /** * Props for the `SidebarGroup` diff --git a/packages/integration/src/gerrit/GerritIntegration.ts b/packages/integration/src/gerrit/GerritIntegration.ts index d57b54df8e..6ea426e407 100644 --- a/packages/integration/src/gerrit/GerritIntegration.ts +++ b/packages/integration/src/gerrit/GerritIntegration.ts @@ -20,7 +20,7 @@ import { GerritIntegrationConfig, readGerritIntegrationConfigs, } from './config'; -import { parseGerritGitilesUrl, builldGerritGitilesUrl } from './core'; +import { parseGerritGitilesUrl, buildGerritGitilesUrl } from './core'; /** * A Gerrit based integration. @@ -61,7 +61,7 @@ export class GerritIntegration implements ScmIntegration { let updated; if (url.startsWith('/')) { const { branch, project } = parseGerritGitilesUrl(this.config, base); - return builldGerritGitilesUrl(this.config, project, branch, url); + return buildGerritGitilesUrl(this.config, project, branch, url); } if (url) { updated = new URL(url, base); diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts index 351e7ce58e..e226492edb 100644 --- a/packages/integration/src/gerrit/core.test.ts +++ b/packages/integration/src/gerrit/core.test.ts @@ -20,7 +20,7 @@ import fetch from 'cross-fetch'; import { setupRequestMockHandlers } from '@backstage/test-utils'; import { GerritIntegrationConfig } from './config'; import { - builldGerritGitilesUrl, + buildGerritGitilesUrl, getGerritBranchApiUrl, getGerritCloneRepoUrl, getGerritRequestOptions, @@ -33,14 +33,14 @@ describe('gerrit core', () => { const worker = setupServer(); setupRequestMockHandlers(worker); - describe('builldGerritGitilesUrl', () => { + describe('buildGerritGitilesUrl', () => { it('can create an url from arguments', () => { const config: GerritIntegrationConfig = { host: 'gerrit.com', gitilesBaseUrl: 'https://gerrit.com/gitiles', }; expect( - builldGerritGitilesUrl(config, 'repo', 'dev', 'catalog-info.yaml'), + buildGerritGitilesUrl(config, 'repo', 'dev', 'catalog-info.yaml'), ).toEqual( 'https://gerrit.com/gitiles/repo/+/refs/heads/dev/catalog-info.yaml', ); diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts index cd2b919e09..0403d8fc75 100644 --- a/packages/integration/src/gerrit/core.ts +++ b/packages/integration/src/gerrit/core.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { trimStart } from 'lodash'; -import { GerritIntegrationConfig } from '.'; +import { GerritIntegrationConfig } from './config'; const GERRIT_BODY_PREFIX = ")]}'"; @@ -79,7 +80,7 @@ export function parseGerritGitilesUrl( * @param filePath - The absolute file path. * @public */ -export function builldGerritGitilesUrl( +export function buildGerritGitilesUrl( config: GerritIntegrationConfig, project: string, branch: string, diff --git a/plugins/auth-node/src/DefaultIdentityClient.ts b/plugins/auth-node/src/DefaultIdentityClient.ts index 499fc62929..b8f6b73865 100644 --- a/plugins/auth-node/src/DefaultIdentityClient.ts +++ b/plugins/auth-node/src/DefaultIdentityClient.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { AuthenticationError } from '@backstage/errors'; import { @@ -24,12 +25,12 @@ import { jwtVerify, } from 'jose'; import { GetKeyFunction } from 'jose/dist/types/types'; - import { BackstageIdentityResponse, IdentityApiGetIdentityRequest, } from './types'; -import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '.'; +import { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader'; +import { IdentityApi } from './IdentityApi'; const CLOCK_MARGIN_S = 10; diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx index 23476b5c3e..8f527a635d 100644 --- a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx @@ -30,8 +30,8 @@ import { import { setupServer } from 'msw/node'; import { DateTime } from 'luxon'; import { siteMock } from '../../mocks/mocks'; -import { azureSiteApiRef } from '../..'; import { AzureSitesOverviewTable } from './AzureSitesOverviewTable'; +import { azureSiteApiRef } from '../../api'; const errorApiMock = { post: jest.fn(), error$: jest.fn() }; const identityApiMock = (getCredentials: any) => ({ diff --git a/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx index dbaa9051b6..8f563c63eb 100644 --- a/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { googleAuthApiRef, storageApiRef } from '@backstage/core-plugin-api'; import { MockStorageApi, TestApiProvider, renderInTestApp, } from '@backstage/test-utils'; - -import { HomePageCalendar } from '.'; -import { gcalendarApiRef, gcalendarPlugin } from '../..'; +import { HomePageCalendar } from './HomePageCalendar'; +import { gcalendarApiRef } from '../../api'; +import { gcalendarPlugin } from '../../plugin'; describe('', () => { const primaryCalendar = { diff --git a/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx b/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx index 922c18da49..544d761102 100644 --- a/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; - import { Box, Button, styled } from '@material-ui/core'; - import { CalendarEvent } from './CalendarEvent'; import { eventsMock } from './signInEventMock'; -import { GCalendarEvent } from '../..'; +import { GCalendarEvent } from '../../api'; type Props = { handleAuthClick: React.MouseEventHandler; diff --git a/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx b/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx index cd4aa6da0f..73a06693b1 100644 --- a/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx +++ b/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx @@ -34,7 +34,7 @@ import { Link, } from '@backstage/core-components'; import useAsync from 'react-use/lib/useAsync'; -import { periskopApiRef } from '../..'; +import { periskopApiRef } from '../../plugin'; import { AggregatedError, NotFoundInInstance } from '../../types'; /** diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index 5e7a2bb349..322195a63f 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -15,8 +15,7 @@ */ import { JsonPrimitive } from '@backstage/types'; -import { ResourcePermission } from '.'; -import { Permission } from './permission'; +import { Permission, ResourcePermission } from './permission'; /** * A request with a UUID identifier, so that batched responses can be matched up with the original diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts index 5518662e45..bc9b797000 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts @@ -23,7 +23,7 @@ import { import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { TemplateAction } from '../..'; +import { TemplateAction } from '../../types'; const mockOctokit = { rest: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts index 0798b04a93..97fdb3d634 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts @@ -23,7 +23,7 @@ import { import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { TemplateAction } from '../..'; +import { TemplateAction } from '../../types'; const mockOctokit = { rest: { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 9e60bc7727..7a015c111f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JsonObject, Observable } from '@backstage/types'; -import ObservableImpl from 'zen-observable'; + import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { JsonObject, Observable } from '@backstage/types'; import { Logger } from 'winston'; +import ObservableImpl from 'zen-observable'; import { + SerializedTask, + SerializedTaskEvent, + TaskBroker, + TaskBrokerDispatchOptions, TaskCompletionState, TaskContext, TaskSecrets, TaskStore, - TaskBroker, - SerializedTaskEvent, - SerializedTask, } from './types'; -import { TaskBrokerDispatchOptions } from '.'; /** * TaskManager diff --git a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts index 0a21eddc15..4dd993a163 100644 --- a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts +++ b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts @@ -17,7 +17,8 @@ import { getVoidLogger } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; -import { createCacheMiddleware, TechDocsCache } from '.'; +import { createCacheMiddleware } from './cacheMiddleware'; +import { TechDocsCache } from './TechDocsCache'; /** * Mocks cached HTTP response. diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts index 3fca7ddeb9..9239573bd1 100644 --- a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Transformer } from './index'; +import type { Transformer } from './transformer'; import { replaceGithubUrlType, ScmIntegrationRegistry, diff --git a/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts index 69256d3ed6..789c9179d5 100644 --- a/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts +++ b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Transformer } from './index'; +import type { Transformer } from './transformer'; import MenuIcon from '@material-ui/icons/Menu'; import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts b/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts index 965f2d4771..e57b256fb8 100644 --- a/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { scrollIntoNavigation } from '.'; +import { scrollIntoNavigation } from './scrollIntoNavigation'; import { createTestShadowDom, FIXTURES } from '../../test-utils'; jest.useFakeTimers(); diff --git a/plugins/xcmetrics/src/utils/buildData.ts b/plugins/xcmetrics/src/utils/buildData.ts index df6fad996e..bbfcbcbf57 100644 --- a/plugins/xcmetrics/src/utils/buildData.ts +++ b/plugins/xcmetrics/src/utils/buildData.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { formatDuration } from '.'; +import { formatDuration } from './format'; import { BuildCount, BuildTime } from '../api'; export const getErrorRatios = (buildCounts?: BuildCount[]) => { From c27640a8bd71eb1c5b65c538a1ee9e7b9c587051 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 25 Oct 2022 11:41:47 +0200 Subject: [PATCH 186/221] actions: generate snyk report in debug mode Signed-off-by: Johan Haals --- .github/workflows/sync_snyk-monitor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 87dec287d7..8b06756606 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -43,6 +43,7 @@ jobs: --org=backstage-dgh --strict-out-of-sync=false --sarif-file-output=snyk.sarif + --debug env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=4096 From bd07f250fc67d71959471023194170d0a30aa6bb Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 25 Oct 2022 12:01:47 +0200 Subject: [PATCH 187/221] actions/snyk: debug correct workflow Signed-off-by: Johan Haals --- .github/workflows/sync_snyk-github-issues.yml | 1 + .github/workflows/sync_snyk-monitor.yml | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 0e0d4c203e..dd35ba26f3 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -36,6 +36,7 @@ jobs: --org=backstage-dgh --strict-out-of-sync=false --json-file-output=snyk.json + --debug json: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 8b06756606..87dec287d7 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -43,7 +43,6 @@ jobs: --org=backstage-dgh --strict-out-of-sync=false --sarif-file-output=snyk.sarif - --debug env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=4096 From f905853ad6b0a61b3780cfe7f3fe45392ce3c09b Mon Sep 17 00:00:00 2001 From: Dmitry Lobanov Date: Sat, 8 Oct 2022 10:22:58 +0000 Subject: [PATCH 188/221] refactor: replace @material-ui Link Signed-off-by: Dmitry Lobanov --- .changeset/two-yaks-wave.md | 20 ++++++++++++++++++ packages/app/src/components/Root/Root.tsx | 12 +++-------- .../HeaderIconLinkRow/IconLinkVertical.tsx | 12 +++-------- .../src/components/Link/Link.tsx | 1 + .../src/layout/Page/Page.stories.tsx | 7 ++++--- .../src/layout/Sidebar/Intro.tsx | 11 +++------- .../packages/app/src/components/Root/Root.tsx | 12 +++-------- .../src/components/Root/Root.tsx | 5 ++--- .../AzureSitesOverviewTable.tsx | 9 ++------ .../components/AboutCard/AboutCard.test.tsx | 11 ++++++---- .../BuildWithStepsPage/BuildWithStepsPage.tsx | 15 ++++--------- .../WorkflowRunDetails/WorkflowRunDetails.tsx | 5 ++--- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 11 +++------- .../Problems/ProblemsTable/ProblemsTable.tsx | 4 ++-- .../components/CalendarCard/CalendarEvent.tsx | 6 +++--- .../CalendarEventPopoverContent.tsx | 11 +++++----- .../WorkflowRunDetails/WorkflowRunDetails.tsx | 5 ++--- .../WorkflowRunsTable/WorkflowRunsTable.tsx | 8 +++++-- .../GithubIssues/IssueCard/IssueCard.tsx | 15 ++++--------- .../components/ClusterPage/ClusterPage.tsx | 9 ++------ .../ProfileCatalog/ProfileCatalog.tsx | 7 +++---- .../BuildWithStepsPage/BuildWithStepsPage.tsx | 21 +++++++++++-------- .../ConsumerGroupOffsets.tsx | 10 +++------ .../components/Incident/IncidentListItem.tsx | 10 ++------- .../RollbarTopItemsTable.tsx | 10 +++------ .../components/TemplateCard/TemplateCard.tsx | 4 ++-- .../TemplateCard/CardLink.tsx | 5 +++-- 27 files changed, 109 insertions(+), 147 deletions(-) create mode 100644 .changeset/two-yaks-wave.md diff --git a/.changeset/two-yaks-wave.md b/.changeset/two-yaks-wave.md new file mode 100644 index 0000000000..97c2908928 --- /dev/null +++ b/.changeset/two-yaks-wave.md @@ -0,0 +1,20 @@ +--- +'@backstage/core-components': patch +'@backstage/create-app': patch +'@backstage/plugin-azure-sites': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-circleci': patch +'@backstage/plugin-cloudbuild': patch +'@backstage/plugin-dynatrace': patch +'@backstage/plugin-gcalendar': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-github-issues': patch +'@backstage/plugin-gitops-profiles': patch +'@backstage/plugin-jenkins': patch +'@backstage/plugin-kafka': patch +'@backstage/plugin-pagerduty': patch +'@backstage/plugin-rollbar': patch +'@backstage/plugin-scaffolder': patch +--- + +Prefer using `Link` from `@backstage/core-components` rather than material-UI. diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 0c142bafbd..41e5b15db9 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -15,7 +15,7 @@ */ import React, { PropsWithChildren } from 'react'; -import { Link, makeStyles } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExtensionIcon from '@material-ui/icons/Extension'; import RuleIcon from '@material-ui/icons/AssignmentTurnedIn'; @@ -29,7 +29,6 @@ import MenuIcon from '@material-ui/icons/Menu'; import MoneyIcon from '@material-ui/icons/MonetizationOn'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; -import { NavLink } from 'react-router-dom'; import { GraphiQLIcon } from '@backstage/plugin-graphiql'; import { Settings as SidebarSettings, @@ -46,6 +45,7 @@ import { SidebarPage, SidebarScrollWrapper, SidebarSpace, + Link, useSidebarOpenState, } from '@backstage/core-components'; import { MyGroupsSidebarItem } from '@backstage/plugin-org'; @@ -74,13 +74,7 @@ const SidebarLogo = () => { return (
- + {isOpen ? : }
diff --git a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx index dda2782c45..f128455549 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx @@ -16,9 +16,8 @@ import React from 'react'; import classnames from 'classnames'; import { makeStyles } from '@material-ui/core/styles'; -import Link from '@material-ui/core/Link'; import LinkIcon from '@material-ui/icons/Link'; -import { Link as RouterLink } from '../Link'; +import { Link } from '../Link'; export type IconLinkVerticalProps = { color?: 'primary' | 'secondary'; @@ -80,14 +79,10 @@ export function IconLinkVertical({ if (disabled) { return ( - +
{icon} {label} - +
); } @@ -96,7 +91,6 @@ export function IconLinkVertical({ title={title} className={classnames(classes.link, classes[color])} to={href} - component={RouterLink} onClick={onClick} > {icon} diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index 07bf8fd78f..66b7453abd 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -16,6 +16,7 @@ import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; import classnames from 'classnames'; +// eslint-disable-next-line no-restricted-imports import MaterialLink, { LinkProps as MaterialLinkProps, } from '@material-ui/core/Link'; diff --git a/packages/core-components/src/layout/Page/Page.stories.tsx b/packages/core-components/src/layout/Page/Page.stories.tsx index 289f0a6c0d..e8fdeea17b 100644 --- a/packages/core-components/src/layout/Page/Page.stories.tsx +++ b/packages/core-components/src/layout/Page/Page.stories.tsx @@ -18,7 +18,6 @@ import { wrapInTestApp } from '@backstage/test-utils'; import Box from '@material-ui/core/Box'; import Chip from '@material-ui/core/Chip'; import Grid from '@material-ui/core/Grid'; -import Link from '@material-ui/core/Link'; import Typography from '@material-ui/core/Typography'; import React, { useState } from 'react'; import { @@ -28,6 +27,7 @@ import { Table, TableColumn, TrendLine, + Link, } from '../../components'; import { Content } from '../Content'; import { ContentHeader } from '../ContentHeader'; @@ -75,7 +75,7 @@ const columns: TableColumn[] = [ highlight: true, render: (row: Partial) => ( <> - {row.branch} + {row.branch} {row.hash} ), @@ -169,7 +169,8 @@ const DataGrid = () => ( able to function. - Contact #cost-awareness for information and support. + Contact #cost-awareness for + information and support. diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx index 05d580c960..91e188b203 100644 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ b/packages/core-components/src/layout/Sidebar/Intro.tsx @@ -16,8 +16,8 @@ import { BackstageTheme } from '@backstage/theme'; import Collapse from '@material-ui/core/Collapse'; -import Link from '@material-ui/core/Link'; import { makeStyles } from '@material-ui/core/styles'; +import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; import React, { useContext, useState } from 'react'; @@ -106,17 +106,12 @@ export function IntroCard(props: IntroCardProps) {
{text}
- + Dismiss - +
); diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx index 5400421e25..6768b48dad 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx @@ -1,5 +1,5 @@ import React, { PropsWithChildren } from 'react'; -import { Link, makeStyles } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExtensionIcon from '@material-ui/icons/Extension'; import MapIcon from '@material-ui/icons/MyLocation'; @@ -7,7 +7,6 @@ import LibraryBooks from '@material-ui/icons/LibraryBooks'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; -import { NavLink } from 'react-router-dom'; import { Settings as SidebarSettings, UserSettingsSignInAvatar, @@ -23,6 +22,7 @@ import { SidebarScrollWrapper, SidebarSpace, useSidebarOpenState, + Link, } from '@backstage/core-components'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; @@ -48,13 +48,7 @@ const SidebarLogo = () => { return (
- + {isOpen ? : }
diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx index cd8b5d35ed..81e3d12d9d 100644 --- a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx @@ -16,7 +16,7 @@ import React, { PropsWithChildren } from 'react'; -import { Link, makeStyles } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; @@ -28,8 +28,8 @@ import { sidebarConfig, SidebarDivider, useSidebarOpenState, + Link, } from '@backstage/core-components'; -import { NavLink } from 'react-router-dom'; const useSidebarLogoStyles = makeStyles({ root: { @@ -53,7 +53,6 @@ const SidebarLogo = () => { return (
{ field: 'name', highlight: true, render: (func: AzureSite) => { - return ( - - {func.name} - - ); + return {func.name}; }, }, { diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index f2fee7d9f4..c77787f717 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -216,7 +216,7 @@ describe('', () => { ); }); - it('renders without "view source" link', async () => { + it('renders disabled "view source" link', async () => { const entity = { apiVersion: 'v1', kind: 'Component', @@ -250,7 +250,8 @@ describe('', () => { }, }, ); - expect(getByText('View Source').closest('a')).not.toHaveAttribute('href'); + expect(getByText('View Source')).toBeVisible(); + expect(getByText('View Source').closest('a')).toBeNull(); }); it.each([ @@ -445,7 +446,8 @@ describe('', () => { }, ); - expect(getByText('View TechDocs').closest('a')).not.toHaveAttribute('href'); + expect(getByText('View TechDocs')).toBeVisible(); + expect(getByText('View TechDocs').closest('a')).toBeNull(); }); it('renders disabled techdocs link when route is not bound', async () => { @@ -497,6 +499,7 @@ describe('', () => { }, ); - expect(getByText('View TechDocs').closest('a')).not.toHaveAttribute('href'); + expect(getByText('View TechDocs')).toBeVisible(); + expect(getByText('View TechDocs').closest('a')).toBeNull(); }); }); diff --git a/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index 137833ee0b..8d48be915e 100644 --- a/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -17,32 +17,25 @@ import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { BuildWithSteps, BuildStepAction } from '../../api'; -import { - Grid, - Box, - IconButton, - Typography, - Link as MaterialLink, -} from '@material-ui/core'; +import { Grid, Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { ActionOutput } from './lib/ActionOutput/ActionOutput'; import LaunchIcon from '@material-ui/icons/Launch'; import { useBuildWithSteps } from '../../state/useBuildWithSteps'; import { Breadcrumbs, + Button, InfoCard, Progress, Link, } from '@backstage/core-components'; -const IconLink = IconButton as any as typeof MaterialLink; - const BuildName = ({ build }: { build?: BuildWithSteps }) => ( #{build?.build_num} - {build?.subject} - + ); diff --git a/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 0e532178e1..aacb437e7b 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -17,7 +17,6 @@ import { Entity } from '@backstage/catalog-model'; import { Box, LinearProgress, - Link as MaterialLink, makeStyles, Paper, Table, @@ -135,10 +134,10 @@ export const WorkflowRunDetails = (props: { entity: Entity }) => { {details.value?.logUrl && ( - + Workflow runs on Google{' '} - + )} diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index a8e2a12ea8..736e641dbd 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -14,10 +14,9 @@ * limitations under the License. */ import React from 'react'; -import { Link, Typography, Box, IconButton, Tooltip } from '@material-ui/core'; +import { Typography, Box, IconButton, Tooltip } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import GoogleIcon from '@material-ui/icons/CloudCircle'; -import { Link as RouterLink } from 'react-router-dom'; import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import SyncIcon from '@material-ui/icons/Sync'; @@ -25,7 +24,7 @@ import { useProjectName } from '../useProjectName'; import { Entity } from '@backstage/catalog-model'; import { buildRouteRef } from '../../routes'; import { DateTime } from 'luxon'; -import { Table, TableColumn } from '@backstage/core-components'; +import { Table, TableColumn, Link } from '@backstage/core-components'; import { useRouteRef } from '@backstage/core-plugin-api'; const generatedColumns: TableColumn[] = [ @@ -59,11 +58,7 @@ const generatedColumns: TableColumn[] = [ const LinkWrapper = () => { const routeLink = useRouteRef(buildRouteRef); return ( - + {row.message} ); diff --git a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx index 78d26e3fe2..12570be6ef 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { Table, TableColumn } from '@backstage/core-components'; import { DynatraceProblem } from '../../../api/DynatraceApi'; import { ProblemStatus } from '../ProblemStatus'; -import { Link } from '@material-ui/core'; +import { Link } from '@backstage/core-components'; type ProblemsTableProps = { problems: DynatraceProblem[]; @@ -36,7 +36,7 @@ export const ProblemsTable = (props: ProblemsTableProps) => { field: 'title', render: (row: Partial) => ( {row.title} diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx index 89705a5a7b..5ea538df86 100644 --- a/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx @@ -22,10 +22,10 @@ import { import React, { useState } from 'react'; import { useAnalytics } from '@backstage/core-plugin-api'; +import { Link } from '@backstage/core-components'; import { Box, - Link, Paper, Popover, Tooltip, @@ -133,12 +133,12 @@ export const CalendarEvent = ({ event }: { event: GCalendarEvent }) => { { e.stopPropagation(); analytics.captureEvent('click', 'zoom link'); }} + noTrack > Zoom link diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx index 4af33171d4..45e793470b 100644 --- a/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx @@ -18,12 +18,11 @@ import React from 'react'; import DOMPurify from 'dompurify'; import { useAnalytics } from '@backstage/core-plugin-api'; - +import { Link } from '@backstage/core-components'; import { Box, Divider, IconButton, - Link, Tooltip, Typography, makeStyles, @@ -77,11 +76,11 @@ export const CalendarEventPopoverContent = ({ analytics.captureEvent('click', 'open in calendar') } + noTrack > @@ -92,9 +91,9 @@ export const CalendarEventPopoverContent = ({ {zoomLink && ( analytics.captureEvent('click', 'zoom link')} + noTrack > Join Zoom Meeting diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index cda0b9c219..f05ea692f5 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -22,7 +22,6 @@ import { Box, CircularProgress, LinearProgress, - Link as MaterialLink, ListItemText, makeStyles, Paper, @@ -246,10 +245,10 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { {details.value?.html_url && ( - + Workflow runs on GitHub{' '} - + )} diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index fb80376354..88f8567b90 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -15,7 +15,6 @@ */ import React from 'react'; import { - Link, Typography, Box, IconButton, @@ -33,7 +32,12 @@ import { getProjectNameFromEntity } from '../getProjectNameFromEntity'; import { Entity } from '@backstage/catalog-model'; import { readGithubIntegrationConfigs } from '@backstage/integration'; -import { EmptyState, Table, TableColumn } from '@backstage/core-components'; +import { + EmptyState, + Table, + TableColumn, + Link, +} from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; const generatedColumns: TableColumn[] = [ diff --git a/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx index 1094bc561f..bbc4995796 100644 --- a/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx @@ -16,13 +16,9 @@ import React from 'react'; import { DateTime } from 'luxon'; -import { - Box, - Paper, - Typography, - CardActionArea, - Link, -} from '@material-ui/core'; + +import { Box, Paper, Typography, CardActionArea } from '@material-ui/core'; +import { Link } from '@backstage/core-components'; import { Assignees } from './Assignees'; import { CommentsCount } from './CommentsCount'; import Divider from '@material-ui/core/Divider'; @@ -63,10 +59,7 @@ export const IssueCard = (props: IssueCardProps) => { - + {repositoryName} diff --git a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx index 117159cc56..fbc2d06ce0 100644 --- a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx +++ b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx @@ -15,7 +15,6 @@ */ import React, { useEffect, useState } from 'react'; -import { Link } from '@material-ui/core'; import { useParams } from 'react-router-dom'; import { gitOpsApiRef, Status } from '../../api'; import { transformRunStatus } from '../ProfileCatalog'; @@ -27,6 +26,7 @@ import { Table, Progress, HeaderLabel, + Link, } from '@backstage/core-components'; import { useApi, githubAuthApiRef } from '@backstage/core-plugin-api'; @@ -94,12 +94,7 @@ const ClusterPage = () => { data={transformRunStatus(runStatus)} columns={columns} /> - + Details diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx index caab33217d..e5cef084b5 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx @@ -15,7 +15,7 @@ */ import React, { useEffect, useState } from 'react'; -import { TextField, List, ListItem, Link } from '@material-ui/core'; +import { TextField, List, ListItem } from '@material-ui/core'; import ClusterTemplateCardList from '../ClusterTemplateCardList'; import ProfileCardList from '../ProfileCardList'; @@ -32,6 +32,7 @@ import { SimpleStepper, SimpleStepperStep, InfoCard, + Link, Progress, Table, StatusWarning, @@ -340,9 +341,7 @@ const ProfileCatalog = () => { /> Details diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index e06332f8f0..3b52c65930 100644 --- a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -15,7 +15,6 @@ */ import { Box, - Link as MaterialLink, Paper, Table, TableBody, @@ -102,10 +101,12 @@ const BuildWithStepsView = () => { Jenkins - - View on Jenkins{' '} - - + {value?.url && ( + + View on Jenkins{' '} + + + )} @@ -114,10 +115,12 @@ const BuildWithStepsView = () => { GitHub - - View on GitHub{' '} - - + {value?.source?.url && ( + + View on GitHub{' '} + + + )} diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx index 17f7af3740..d166195cf9 100644 --- a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx +++ b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Box, Grid, Typography, Link } from '@material-ui/core'; +import { Box, Grid, Typography } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import React from 'react'; import { useConsumerGroupsOffsetsForEntity } from './useConsumerGroupsOffsetsForEntity'; -import { Table, TableColumn } from '@backstage/core-components'; +import { Table, TableColumn, Link } from '@backstage/core-components'; export type TopicPartitionInfo = { topic: string; @@ -103,11 +103,7 @@ export const ConsumerGroupOffsets = ({ Consumed Topics for {consumerGroup} ( - {(dashboardUrl && ( - - {clusterId} - - )) || + {(dashboardUrl && {clusterId}) || clusterId} ) diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 5945c45e83..b23fcb8490 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -22,7 +22,6 @@ import { ListItemText, makeStyles, IconButton, - Link, Typography, Chip, } from '@material-ui/core'; @@ -32,6 +31,7 @@ import { DateTime, Duration } from 'luxon'; import { PagerDutyIncident } from '../types'; import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; import { BackstageTheme } from '@backstage/theme'; +import { Link } from '@backstage/core-components'; const useStyles = makeStyles(theme => ({ denseListIcon: { @@ -100,13 +100,7 @@ export const IncidentListItem = ({ incident }: Props) => { secondary={ Created {createdAt} and assigned to{' '} - - {user?.summary ?? 'nobody'} - + {user?.summary ?? 'nobody'} } /> diff --git a/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx b/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx index 45d1ab8e4f..89fa7596b0 100644 --- a/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx +++ b/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Box, Link, Typography } from '@material-ui/core'; +import { Box, Typography } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { @@ -24,7 +24,7 @@ import { } from '../../api/types'; import { buildItemUrl } from '../../utils'; import { TrendGraph } from '../TrendGraph/TrendGraph'; -import { Table, TableColumn } from '@backstage/core-components'; +import { Table, TableColumn, Link } from '@backstage/core-components'; const columns: TableColumn[] = [ { @@ -34,11 +34,7 @@ const columns: TableColumn[] = [ align: 'left', width: '70px', render: (data: any) => ( - + {data.item.counter} ), diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 9d1509e7a6..984e51f3b5 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -24,6 +24,7 @@ import { import { Button, ItemCardHeader, + Link, MarkdownContent, } from '@backstage/core-components'; import { @@ -52,7 +53,6 @@ import { CardMedia, Chip, IconButton, - Link, makeStyles, Tooltip, Typography, @@ -162,7 +162,7 @@ const DeprecationWarning = () => {
diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx index 3ec31673db..9bdb3cf7fb 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx @@ -15,7 +15,8 @@ */ import { IconComponent } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; -import { Link, makeStyles } from '@material-ui/core'; +import { Link } from '@backstage/core-components'; +import { makeStyles } from '@material-ui/core'; import React from 'react'; interface CardLinkProps { @@ -37,7 +38,7 @@ export const CardLink = ({ icon: Icon, text, url }: CardLinkProps) => { return (
- + {text || url}
From 7539b3674830af1ca98ea3d74ca4be6b290c4473 Mon Sep 17 00:00:00 2001 From: Dmitry Lobanov Date: Sat, 8 Oct 2022 10:23:40 +0000 Subject: [PATCH 189/221] chore: (eslint) restrict imports of @material-ui Link BREAKING CHANGE: Added a new ESLint rule that restricts imports of Link from @material-ui Signed-off-by: Dmitry Lobanov --- .changeset/great-colts-invite.md | 16 ++++++++++++++++ packages/cli/config/eslint-factory.js | 11 +++++++++++ 2 files changed, 27 insertions(+) create mode 100644 .changeset/great-colts-invite.md diff --git a/.changeset/great-colts-invite.md b/.changeset/great-colts-invite.md new file mode 100644 index 0000000000..1ca71580dc --- /dev/null +++ b/.changeset/great-colts-invite.md @@ -0,0 +1,16 @@ +--- +'@backstage/cli': minor +--- + +Added a new ESLint rule that restricts imports of Link from @material-ui + +The rule can be can be overridden in the following way: + +```diff +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { ++ restrictedImports: [ ++ { name: '@material-ui/core', importNames: [] }, ++ { name: '@material-ui/core/Link', importNames: [] }, ++ ], +}); +``` diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index e213f58e4e..04e23b2077 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -239,6 +239,17 @@ function createConfigForRole(dir, role, extraConfig = {}) { name: '@material-ui/icons/', // because this is possible too ._. message: "Please import '@material-ui/icons/' instead.", }, + { + name: '@material-ui/core', + importNames: ['Link'], + message: + 'Prefer using `Link` from `@backstage/core-components` rather than material-UI', + }, + { + name: '@material-ui/core/Link', + message: + 'Prefer using `Link` from `@backstage/core-components` rather than material-UI', + }, ...require('module').builtinModules, ...(extraConfig.restrictedImports ?? []), ], From 8c9fe99c0306cd0de2e5991ab4493802bd058fb9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 11:57:48 +0000 Subject: [PATCH 190/221] Update dependency immer to v9.0.16 Signed-off-by: Renovate Bot --- yarn.lock | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 71a2ade870..dd27522bee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24835,7 +24835,14 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.1, immer@npm:^9.0.7": +"immer@npm:^9.0.1": + version: 9.0.16 + resolution: "immer@npm:9.0.16" + checksum: e9a5ca65c929b329da7a3b7beccf7984271cda7bdd47b2cab619eac3277dcd56598c211b55cc340786b6eff0c06652ac018808d9fd744443f06882364dece6bc + languageName: node + linkType: hard + +"immer@npm:^9.0.7": version: 9.0.15 resolution: "immer@npm:9.0.15" checksum: 92e3d63e810e3c3c2bb61b70c45443e37ef983ad12924e3edaf03725ae5979618f5b473439bb3bb4a8c4769f25132f18dec10ea15c40f0b20da5691ff96ff611 From ee653a97c52c0f9e3237f8088b78d98339ecb940 Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Sat, 22 Oct 2022 22:32:39 -0600 Subject: [PATCH 191/221] Using GitHub Integration Host information in place of static GitHub hostname Signed-off-by: Josh Maxwell --- .../src/components/GithubIssues/IssueCard/IssueCard.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx index bbc4995796..2337c8b4ac 100644 --- a/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx @@ -14,14 +14,13 @@ * limitations under the License. */ -import React from 'react'; -import { DateTime } from 'luxon'; - -import { Box, Paper, Typography, CardActionArea } from '@material-ui/core'; import { Link } from '@backstage/core-components'; +import { Box, CardActionArea, Paper, Typography } from '@material-ui/core'; +import Divider from '@material-ui/core/Divider'; +import { DateTime } from 'luxon'; +import React from 'react'; import { Assignees } from './Assignees'; import { CommentsCount } from './CommentsCount'; -import Divider from '@material-ui/core/Divider'; type IssueCardProps = { title: string; From c8dd2a8c87df3dad1bae6577efca37d40cd196ed Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Sun, 23 Oct 2022 13:34:19 -0600 Subject: [PATCH 192/221] Adding changeset Signed-off-by: Josh Maxwell --- .changeset/clean-feet-remain.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/clean-feet-remain.md diff --git a/.changeset/clean-feet-remain.md b/.changeset/clean-feet-remain.md new file mode 100644 index 0000000000..c8f301105c --- /dev/null +++ b/.changeset/clean-feet-remain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-issues': patch +--- + +Respecting GitHub integration host value from catalog-info.yaml file From d331fe795ebf96929c9b8852b165385f287d0b03 Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Mon, 24 Oct 2022 21:42:34 -0600 Subject: [PATCH 193/221] Replacing gitlab integration host with stripped url already present Signed-off-by: Josh Maxwell --- .../src/components/GithubIssues/IssueCard/IssueCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx index 2337c8b4ac..a5a5b79ac0 100644 --- a/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx @@ -58,7 +58,7 @@ export const IssueCard = (props: IssueCardProps) => { - + {repositoryName} From 487507f2f11762701ca69322e610c1aa4da268c4 Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Mon, 24 Oct 2022 21:45:04 -0600 Subject: [PATCH 194/221] Updating changeset message Signed-off-by: Josh Maxwell --- .changeset/clean-feet-remain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clean-feet-remain.md b/.changeset/clean-feet-remain.md index c8f301105c..f55a41a81a 100644 --- a/.changeset/clean-feet-remain.md +++ b/.changeset/clean-feet-remain.md @@ -2,4 +2,4 @@ '@backstage/plugin-github-issues': patch --- -Respecting GitHub integration host value from catalog-info.yaml file +Stripping specific issues url already present to target base issues url. From 23f827ff4f719f0d0adb8878079e03098bbabfb4 Mon Sep 17 00:00:00 2001 From: Josh Maxwell Date: Mon, 24 Oct 2022 21:47:57 -0600 Subject: [PATCH 195/221] Uppercasing URL to make spellchecker happy Signed-off-by: Josh Maxwell --- .changeset/clean-feet-remain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clean-feet-remain.md b/.changeset/clean-feet-remain.md index f55a41a81a..c712ef9e1e 100644 --- a/.changeset/clean-feet-remain.md +++ b/.changeset/clean-feet-remain.md @@ -2,4 +2,4 @@ '@backstage/plugin-github-issues': patch --- -Stripping specific issues url already present to target base issues url. +Stripping specific issues URL already present to target base issues URL. From ca89c7b87f7e7947b3787853ce2edae662b21965 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:07:41 +0000 Subject: [PATCH 196/221] Update dependency inquirer to v8.2.5 Signed-off-by: Renovate Bot --- yarn.lock | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 6da9291470..32abbd8f9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25037,7 +25037,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": +"inquirer@npm:^8.0.0": version: 8.2.4 resolution: "inquirer@npm:8.2.4" dependencies: @@ -25060,6 +25060,29 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^8.2.0": + version: 8.2.5 + resolution: "inquirer@npm:8.2.5" + dependencies: + ansi-escapes: ^4.2.1 + chalk: ^4.1.1 + cli-cursor: ^3.1.0 + cli-width: ^3.0.0 + external-editor: ^3.0.3 + figures: ^3.0.0 + lodash: ^4.17.21 + mute-stream: 0.0.8 + ora: ^5.4.1 + run-async: ^2.4.0 + rxjs: ^7.5.5 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + through: ^2.3.6 + wrap-ansi: ^7.0.0 + checksum: f13ee4c444187786fb393609dedf6b30870115a57b603f2e6424f29a99abc13446fd45ee22461c33c9c40a92a60a8df62d0d6b25d74fc6676fa4cb211de55b55 + languageName: node + linkType: hard + "internal-slot@npm:^1.0.3": version: 1.0.3 resolution: "internal-slot@npm:1.0.3" From 6c13b7b295a6d7465dca6b1e9b4cbdc8637c9d4e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:08:33 +0000 Subject: [PATCH 197/221] Update dependency jest-when to v3.5.2 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6da9291470..e187b02738 100644 --- a/yarn.lock +++ b/yarn.lock @@ -26605,11 +26605,11 @@ __metadata: linkType: hard "jest-when@npm:^3.1.0": - version: 3.5.1 - resolution: "jest-when@npm:3.5.1" + version: 3.5.2 + resolution: "jest-when@npm:3.5.2" peerDependencies: jest: ">= 25" - checksum: 1efb9f497f7c846fe8b0f4125d5f449c4a4d78d5d0afa910d134b301ae4c119ea52c9465db38d2146269d42808afe8f3a4328d1d656878a9a69458ee653f6499 + checksum: 9ad95552d377ef4d517c96a14c38bd8626d6856f518e7efc8a01d76a45a39d3c52281392c98da781c302114c78cd1ea17556c7f638d49d15971fcce9d58306e8 languageName: node linkType: hard From f8ce2b0ea5e7cee8eec9f16f885eaa424919e658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn?= Date: Tue, 25 Oct 2022 14:29:49 +0200 Subject: [PATCH 198/221] Update contact info for Lunar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change updates the contact point for Lunar as adopters of Backstage. Signed-off-by: Bjørn --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index f667de120c..1fadc68861 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -25,7 +25,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Expedia Group](https://www.expediagroup.com) | [@gman0922](https://github.com/gman0922), [Sheena Sharma](mailto:shesharma@expediagroup.com), [Alekhya Karuturi](mailto:akaruturi@expediagroup.com) | EG Developer Front Door | | [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | | [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Lunar](https://lunar.app) | [Bjørn Hald Sørensen](https://github.com/crevil) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | | [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | | [Peloton](https://www.onepeloton.com/) | [Matt Waldron](https://github.com/daftgopher) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | | [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | From 3758f4a7c2f7f58f22dea19439c85aec7e764509 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 25 Oct 2022 14:40:40 +0200 Subject: [PATCH 199/221] chore: simplify date parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals --- packages/backend-common/src/service/lib/hostFactory.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 089bf610ed..57334062ea 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -86,8 +86,7 @@ export async function createHttpsServer( function getCertificateExpiration(cert: string, logger?: Logger) { try { const crt = forge.pki.certificateFromPem(cert); - const crtTimestamp = Date.parse(crt.validity.notAfter.toString()); - return crtTimestamp - Date.now(); + return crt.validity.notAfter.getTime() - Date.now(); } catch (error) { logger?.warn(`Unable to parse self-signed certificate. ${error}`); return 0; From d4df1fbb2ee1411f9e23a3229cd870413ba758a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 25 Oct 2022 13:18:27 +0000 Subject: [PATCH 200/221] Version Packages (next) --- .changeset/pre.json | 72 +- docs/releases/v1.8.0-next.0-changelog.md | 2326 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 11 + packages/app-defaults/package.json | 2 +- packages/app/CHANGELOG.md | 63 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 11 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 21 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 8 + packages/backend-defaults/package.json | 2 +- packages/backend-next/CHANGELOG.md | 10 + packages/backend-next/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 10 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 11 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 11 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 43 + packages/backend/package.json | 2 +- packages/catalog-client/CHANGELOG.md | 8 + packages/catalog-client/package.json | 2 +- packages/catalog-model/CHANGELOG.md | 10 + packages/catalog-model/package.json | 2 +- packages/cli/CHANGELOG.md | 32 + packages/cli/package.json | 2 +- packages/config-loader/CHANGELOG.md | 10 + packages/config-loader/package.json | 2 +- packages/config/CHANGELOG.md | 7 + packages/config/package.json | 2 +- packages/core-app-api/CHANGELOG.md | 15 + packages/core-app-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 19 + packages/core-components/package.json | 2 +- packages/core-plugin-api/CHANGELOG.md | 13 + packages/core-plugin-api/package.json | 2 +- packages/create-app/CHANGELOG.md | 10 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 15 + packages/dev-utils/package.json | 2 +- packages/errors/CHANGELOG.md | 7 + packages/errors/package.json | 2 +- packages/integration-react/CHANGELOG.md | 11 + packages/integration-react/package.json | 2 +- packages/integration/CHANGELOG.md | 31 + packages/integration/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 19 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 11 + packages/techdocs-cli/package.json | 2 +- packages/test-utils/CHANGELOG.md | 13 + packages/test-utils/package.json | 2 +- packages/types/CHANGELOG.md | 6 + packages/types/package.json | 2 +- plugins/adr-backend/CHANGELOG.md | 14 + plugins/adr-backend/package.json | 2 +- plugins/adr-common/CHANGELOG.md | 9 + plugins/adr-common/package.json | 2 +- plugins/adr/CHANGELOG.md | 15 + plugins/adr/package.json | 2 +- plugins/airbrake-backend/CHANGELOG.md | 8 + plugins/airbrake-backend/package.json | 2 +- plugins/airbrake/CHANGELOG.md | 13 + plugins/airbrake/package.json | 2 +- plugins/allure/CHANGELOG.md | 11 + plugins/allure/package.json | 2 +- plugins/analytics-module-ga/CHANGELOG.md | 10 + plugins/analytics-module-ga/package.json | 2 +- plugins/apache-airflow/CHANGELOG.md | 8 + plugins/apache-airflow/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 12 + plugins/api-docs/package.json | 2 +- plugins/apollo-explorer/CHANGELOG.md | 9 + plugins/apollo-explorer/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 11 + plugins/app-backend/package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 14 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 10 + plugins/auth-node/package.json | 2 +- plugins/azure-devops-backend/CHANGELOG.md | 9 + plugins/azure-devops-backend/package.json | 2 +- plugins/azure-devops/CHANGELOG.md | 13 + plugins/azure-devops/package.json | 2 +- plugins/azure-sites-backend/CHANGELOG.md | 14 + plugins/azure-sites-backend/package.json | 2 +- plugins/azure-sites-common/CHANGELOG.md | 7 + plugins/azure-sites-common/package.json | 2 +- plugins/azure-sites/CHANGELOG.md | 18 + plugins/azure-sites/package.json | 2 +- plugins/badges-backend/CHANGELOG.md | 11 + plugins/badges-backend/package.json | 2 +- plugins/badges/CHANGELOG.md | 12 + plugins/badges/package.json | 2 +- plugins/bazaar-backend/CHANGELOG.md | 11 + plugins/bazaar-backend/package.json | 2 +- plugins/bazaar/CHANGELOG.md | 18 + plugins/bazaar/package.json | 2 +- plugins/bitbucket-cloud-common/CHANGELOG.md | 7 + plugins/bitbucket-cloud-common/package.json | 2 +- plugins/bitrise/CHANGELOG.md | 12 + plugins/bitrise/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 30 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 22 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 21 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 29 + .../package.json | 2 +- .../CHANGELOG.md | 46 + .../package.json | 2 +- .../CHANGELOG.md | 22 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 12 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 21 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-common/CHANGELOG.md | 9 + plugins/catalog-common/package.json | 2 +- plugins/catalog-customized/CHANGELOG.md | 8 + plugins/catalog-customized/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 12 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-graphql/CHANGELOG.md | 9 + plugins/catalog-graphql/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 18 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 12 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 20 + plugins/catalog-react/package.json | 2 +- plugins/catalog/CHANGELOG.md | 19 + plugins/catalog/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/cicd-statistics/CHANGELOG.md | 9 + plugins/cicd-statistics/package.json | 2 +- plugins/circleci/CHANGELOG.md | 13 + plugins/circleci/package.json | 2 +- plugins/cloudbuild/CHANGELOG.md | 12 + plugins/cloudbuild/package.json | 2 +- plugins/code-climate/CHANGELOG.md | 11 + plugins/code-climate/package.json | 2 +- plugins/code-coverage-backend/CHANGELOG.md | 12 + plugins/code-coverage-backend/package.json | 2 +- plugins/code-coverage/CHANGELOG.md | 14 + plugins/code-coverage/package.json | 2 +- plugins/codescene/CHANGELOG.md | 11 + plugins/codescene/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 12 + plugins/config-schema/package.json | 2 +- plugins/cost-insights/CHANGELOG.md | 17 + plugins/cost-insights/package.json | 2 +- plugins/dynatrace/CHANGELOG.md | 13 + plugins/dynatrace/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 10 + .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list-common/CHANGELOG.md | 7 + plugins/example-todo-list-common/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 9 + plugins/example-todo-list/package.json | 2 +- plugins/explore-react/CHANGELOG.md | 7 + plugins/explore-react/package.json | 2 +- plugins/explore/CHANGELOG.md | 12 + plugins/explore/package.json | 2 +- plugins/firehydrant/CHANGELOG.md | 10 + plugins/firehydrant/package.json | 2 +- plugins/fossa/CHANGELOG.md | 12 + plugins/fossa/package.json | 2 +- plugins/gcalendar/CHANGELOG.md | 12 + plugins/gcalendar/package.json | 2 +- plugins/gcp-projects/CHANGELOG.md | 9 + plugins/gcp-projects/package.json | 2 +- plugins/git-release-manager/CHANGELOG.md | 12 + plugins/git-release-manager/package.json | 2 +- plugins/github-actions/CHANGELOG.md | 14 + plugins/github-actions/package.json | 2 +- plugins/github-deployments/CHANGELOG.md | 14 + plugins/github-deployments/package.json | 2 +- plugins/github-issues/CHANGELOG.md | 20 + plugins/github-issues/package.json | 2 +- .../github-pull-requests-board/CHANGELOG.md | 18 + .../github-pull-requests-board/package.json | 2 +- plugins/gitops-profiles/CHANGELOG.md | 10 + plugins/gitops-profiles/package.json | 2 +- plugins/gocd/CHANGELOG.md | 12 + plugins/gocd/package.json | 2 +- plugins/graphiql/CHANGELOG.md | 9 + plugins/graphiql/package.json | 2 +- plugins/graphql-backend/CHANGELOG.md | 9 + plugins/graphql-backend/package.json | 2 +- plugins/home/CHANGELOG.md | 13 + plugins/home/package.json | 2 +- plugins/ilert/CHANGELOG.md | 16 + plugins/ilert/package.json | 2 +- plugins/jenkins-backend/CHANGELOG.md | 14 + plugins/jenkins-backend/package.json | 2 +- plugins/jenkins-common/CHANGELOG.md | 8 + plugins/jenkins-common/package.json | 2 +- plugins/jenkins/CHANGELOG.md | 14 + plugins/jenkins/package.json | 2 +- plugins/kafka-backend/CHANGELOG.md | 10 + plugins/kafka-backend/package.json | 2 +- plugins/kafka/CHANGELOG.md | 13 + plugins/kafka/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 21 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-common/CHANGELOG.md | 13 + plugins/kubernetes-common/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 13 + plugins/kubernetes/package.json | 2 +- plugins/lighthouse/CHANGELOG.md | 12 + plugins/lighthouse/package.json | 2 +- plugins/newrelic-dashboard/CHANGELOG.md | 11 + plugins/newrelic-dashboard/package.json | 2 +- plugins/newrelic/CHANGELOG.md | 9 + plugins/newrelic/package.json | 2 +- plugins/org/CHANGELOG.md | 11 + plugins/org/package.json | 2 +- plugins/pagerduty/CHANGELOG.md | 13 + plugins/pagerduty/package.json | 2 +- plugins/periskop-backend/CHANGELOG.md | 8 + plugins/periskop-backend/package.json | 2 +- plugins/periskop/CHANGELOG.md | 13 + plugins/periskop/package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 12 + plugins/permission-backend/package.json | 2 +- plugins/permission-common/CHANGELOG.md | 11 + plugins/permission-common/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 11 + plugins/permission-node/package.json | 2 +- plugins/permission-react/CHANGELOG.md | 9 + plugins/permission-react/package.json | 2 +- plugins/playlist-backend/CHANGELOG.md | 16 + plugins/playlist-backend/package.json | 2 +- plugins/playlist-common/CHANGELOG.md | 7 + plugins/playlist-common/package.json | 2 +- plugins/playlist/CHANGELOG.md | 17 + plugins/playlist/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 8 + plugins/proxy-backend/package.json | 2 +- plugins/rollbar-backend/CHANGELOG.md | 8 + plugins/rollbar-backend/package.json | 2 +- plugins/rollbar/CHANGELOG.md | 12 + plugins/rollbar/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 57 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 8 + plugins/scaffolder-common/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 31 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 10 + plugins/search-backend-module-pg/package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 12 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 15 + plugins/search-backend/package.json | 2 +- plugins/search-common/CHANGELOG.md | 8 + plugins/search-common/package.json | 2 +- plugins/search-react/CHANGELOG.md | 12 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 17 + plugins/search/package.json | 2 +- plugins/sentry/CHANGELOG.md | 11 + plugins/sentry/package.json | 2 +- plugins/shortcuts/CHANGELOG.md | 10 + plugins/shortcuts/package.json | 2 +- plugins/sonarqube-backend/CHANGELOG.md | 9 + plugins/sonarqube-backend/package.json | 2 +- plugins/sonarqube/CHANGELOG.md | 11 + plugins/sonarqube/package.json | 2 +- plugins/splunk-on-call/CHANGELOG.md | 11 + plugins/splunk-on-call/package.json | 2 +- plugins/stack-overflow-backend/CHANGELOG.md | 18 + plugins/stack-overflow-backend/package.json | 2 +- plugins/stack-overflow/CHANGELOG.md | 12 + plugins/stack-overflow/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- plugins/tech-insights-backend/CHANGELOG.md | 17 + plugins/tech-insights-backend/package.json | 2 +- plugins/tech-insights-common/CHANGELOG.md | 7 + plugins/tech-insights-common/package.json | 2 +- plugins/tech-insights-node/CHANGELOG.md | 13 + plugins/tech-insights-node/package.json | 2 +- plugins/tech-insights/CHANGELOG.md | 15 + plugins/tech-insights/package.json | 2 +- plugins/tech-radar/CHANGELOG.md | 10 + plugins/tech-radar/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 16 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 25 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 13 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 11 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 46 + plugins/techdocs/package.json | 2 +- plugins/todo-backend/CHANGELOG.md | 12 + plugins/todo-backend/package.json | 2 +- plugins/todo/CHANGELOG.md | 12 + plugins/todo/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 11 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 12 + plugins/user-settings/package.json | 2 +- plugins/vault-backend/CHANGELOG.md | 11 + plugins/vault-backend/package.json | 2 +- plugins/vault/CHANGELOG.md | 12 + plugins/vault/package.json | 2 +- plugins/xcmetrics/CHANGELOG.md | 13 + plugins/xcmetrics/package.json | 2 +- yarn.lock | 311 ++- 336 files changed, 5182 insertions(+), 178 deletions(-) create mode 100644 docs/releases/v1.8.0-next.0-changelog.md create mode 100644 plugins/azure-sites-backend/CHANGELOG.md create mode 100644 plugins/azure-sites-common/CHANGELOG.md create mode 100644 plugins/azure-sites/CHANGELOG.md diff --git a/.changeset/pre.json b/.changeset/pre.json index 0bdcd4a8d9..a2fd9eb458 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -173,7 +173,75 @@ "@backstage/plugin-user-settings-backend": "0.1.1", "@backstage/plugin-vault": "0.1.4", "@backstage/plugin-vault-backend": "0.2.3", - "@backstage/plugin-xcmetrics": "0.2.30" + "@backstage/plugin-xcmetrics": "0.2.30", + "@backstage/plugin-azure-sites": "0.0.0", + "@backstage/plugin-azure-sites-backend": "0.0.0", + "@backstage/plugin-azure-sites-common": "0.0.0" }, - "changesets": [] + "changesets": [ + "analyze-software-creation", + "analyze-software-exploration", + "big-islands-add", + "brave-eels-allow", + "brown-days-pretend", + "calm-bottles-happen", + "chatty-planets-flash", + "clean-feet-remain", + "clean-planets-rhyme", + "dirty-birds-burn", + "dull-oranges-tap", + "eight-pears-attack", + "eleven-pets-sneeze", + "few-books-remember", + "flat-items-perform", + "flat-kangaroos-kiss", + "forty-bags-trade", + "forty-jokes-lie", + "fresh-cooks-sing", + "fresh-weeks-share", + "gorgeous-balloons-sit", + "gorgeous-onions-thank", + "gorgeous-queens-pull", + "great-colts-invite", + "grumpy-pigs-reflect", + "happy-avocados-tan", + "heavy-elephants-nail", + "itchy-paws-protect", + "kind-emus-juggle", + "lazy-planes-repair", + "little-bikes-eat", + "lucky-cats-peel", + "lucky-spoons-hide", + "mean-files-fly", + "metal-dogs-swim", + "nasty-crabs-share", + "orange-trees-peel", + "popular-bulldogs-lie", + "popular-mails-wave", + "real-swans-repair", + "renovate-6fb5f1b", + "selfish-kiwis-matter", + "shaggy-birds-happen", + "shaggy-colts-watch", + "sharp-goats-itch", + "shiny-beers-relax", + "short-balloons-work", + "sixty-islands-develop", + "sixty-pigs-shave", + "sixty-singers-push", + "spicy-parents-lick", + "spotty-dryers-explain", + "stupid-pens-occur", + "sweet-readers-compare", + "tame-ads-appear", + "tasty-colts-hug", + "tasty-scissors-tickle", + "ten-pens-draw", + "three-houses-agree", + "three-poems-think", + "two-oranges-joke", + "two-yaks-wave", + "unlucky-buttons-poke", + "wet-cameras-call" + ] } diff --git a/docs/releases/v1.8.0-next.0-changelog.md b/docs/releases/v1.8.0-next.0-changelog.md new file mode 100644 index 0000000000..c661325d95 --- /dev/null +++ b/docs/releases/v1.8.0-next.0-changelog.md @@ -0,0 +1,2326 @@ +# Release v1.8.0-next.0 + +## @backstage/backend-common@0.16.0-next.0 + +### Minor Changes + +- a7607b5413: **BREAKING CHANGE**: The `UrlReader` interface has been updated to require that `readUrl` is implemented. `readUrl` has previously been optional to implement but a warning has been logged when calling its predecessor `read`. + The `read` method is now deprecated and will be removed in a future release. + +### Patch Changes + +- 55227712dd: Generated development HTTPS backend certificate is now checked for expiration date instead of file age. +- d05e1841ce: This patch adds GiteaURLReader to the available classes. It currently only reads single files via gitea's public repos api +- 210a3b5668: Small update to fix compatibility with newer versions of the `keyv` library +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/cli@0.21.0-next.0 + +### Minor Changes + +- 7539b36748: Added a new ESLint rule that restricts imports of Link from @material-ui + + The rule can be can be overridden in the following way: + + ```diff + module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + + restrictedImports: [ + + { name: '@material-ui/core', importNames: [] }, + + { name: '@material-ui/core/Link', importNames: [] }, + + ], + }); + ``` + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to version 1.3.9 which fixes a `.tsx` parser bug +- 9c767e8f45: Updated dependency `@svgr/plugin-jsx` to `6.5.x`. + Updated dependency `@svgr/plugin-svgo` to `6.5.x`. + Updated dependency `@svgr/rollup` to `6.5.x`. + Updated dependency `@svgr/webpack` to `6.5.x`. +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/release-manifests@0.0.6 + +## @backstage/core-app-api@1.2.0-next.0 + +### Minor Changes + +- 9b737e5f2e: Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. +- 127fcad26d: Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.12.0-next.0 + +### Minor Changes + +- fb3733e446: **BREAKING**: Removed the `HomepageTimer` as it has been replaced by the `HeaderWorldClock` in the Home plugin and was deprecated over a year ago. + +### Patch Changes + +- 5f695c219a: Set the `searchTooltip` to "Filter" to follow how the `searchPlaceholder` is set making this more consistent +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 858986f6b6: Disable base path workaround in `Link` component when React Router v6 stable is used. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.1.0-next.0 + +### Minor Changes + +- a228f113d0: The app `Router` component now accepts an optional `basename` property. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/integration@1.4.0-next.0 + +### Minor Changes + +- d05e1841ce: This patch brings Gitea as a valid integration: target, via the ScmIntegration interface. It adds gitea to the relevant static properties (get integration by name, get integration by type) for plugins to be able to reference the same Gitea server. +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + + Deprecates: + + - `getGitHubFileFetchUrl` replaced by `getGithubFileFetchUrl` + - `GitHubIntegrationConfig` replaced by `GithubIntegrationConfig` + - `GitHubIntegration` replaced by `GithubIntegration` + - `readGitHubIntegrationConfig` replaced by `readGithubIntegrationConfig` + - `readGitHubIntegrationConfigs` replaced by `readGithubIntegrationConfigs` + - `replaceGitHubUrlType` replaced by `replaceGithubUrlType` + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-azure-sites@0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-azure-sites-backend@0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-azure-sites-common@0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +## @backstage/plugin-bazaar@0.2.0-next.0 + +### Minor Changes + +- 28b39e0e0e: The limit prop of BazaarOverviewCard has been removed entirely, and instead replaced with a new optional boolean prop `fullWidth`. The BazaarOverviewCard now always use full height without fixed width. Also fixed problem with link to Bazaar. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.2.0-next.0 + +### Minor Changes + +- 67fe5bc9a9: BREAKING: Support authenticated backends by including a server token for catalog requests. The constructor of `GithubLocationAnalyzer` now requires an instance of `TokenManager` to be supplied: + + ```diff + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + + tokenManager: env.tokenManager, + }), + ); + ... + ``` + +- f64d66a45c: Added the ability for the GitHub discovery provider to validate that catalog files exist before emitting them. + + Users can now set the `validateLocationsExist` property to `true` in their GitHub discovery configuration to opt in to this feature. + This feature only works with `catalogPath`s that do not contain wildcards. + + When `validateLocationsExist` is set to `true`, the GitHub discovery provider will retrieve the object from the + repository at the provided `catalogPath`. + If this file exists and is non-empty, then it will be emitted as a location for further processing. + If this file does not exist or is empty, then it will not be emitted. + Not emitting locations that do not exist allows for far fewer calls to the GitHub API to validate locations that do not exist. + +### Patch Changes + +- 67fe5bc9a9: Properly derive Github credentials when making requests in `GithubLocationAnalyzer` to support Github App authentication +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-cost-insights@0.12.0-next.0 + +### Minor Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues + +### Patch Changes + +- cbe11d1e23: Tweak README +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-github-issues@0.2.0-next.0 + +### Minor Changes + +- ead285b9e4: **BREAKING**: Changed the casing of all exported types to have a lowercase "h" in "github". E.g. "GitHubIssuesPage" was renamed to "GithubIssuesPage". Please rename your imports where necessary. + +### Patch Changes + +- c8dd2a8c87: Stripping specific issues URL already present to target base issues URL. +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-ilert@0.2.0-next.0 + +### Minor Changes + +- 0697af30da: Added support for multiple responders in alert list, added new tab with list to support iLert resource 'service', added new tab with list to support iLert resource 'status page' + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kubernetes-backend@0.8.0-next.0 + +### Minor Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-scaffolder@1.8.0-next.0 + +### Minor Changes + +- edae17309e: Added props to override default Scaffolder page title, subtitle and pageTitleOverride. + Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component). + +### Patch Changes + +- 4830a3569f: Basic analytics instrumentation is now in place: + + - As users make their way through template steps, a `click` event is fired, including the step number. + - After a user clicks "Create" a `create` event is fired, including the name of the software that was just created. The template used at creation is set on the `entityRef` context key. + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + +## @backstage/plugin-scaffolder-backend@1.8.0-next.0 + +### Minor Changes + +- ea14eb62a2: Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: + + - `scaffolder_task_count`: Tracks successful task runs. + + Labels: + + - `template`: The entity ref of the scaffolded template + - `user`: The entity ref of the user that invoked the template run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_task_duration`: a histogram which tracks the duration of a task run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `result`: A boolean describing whether the task ran successfully + + - `scaffolder_step_count`: a count that tracks each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_step_duration`: a histogram which tracks the duration of each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + You can find a guide for running Prometheus metrics here: + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + +## @backstage/plugin-techdocs@1.4.0-next.0 + +### Minor Changes + +- 5691baea69: Add ability to configure filters when using EntityListDocsGrid + + The following example will render two sections of cards grid: + + - One section for documentations tagged as `recommended` + - One section for documentations tagged as `runbook` + + ```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } + ]}} /> + ``` + +### Patch Changes + +- cbe11d1e23: Tweak README +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- 3a1a999b7b: Include query parameters when navigating to relative links in documents +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/app-defaults@1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/backend-app-api@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/backend-defaults@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + +## @backstage/backend-plugin-api@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/backend-tasks@0.3.7-next.0 + +### Patch Changes + +- 30e43717c7: Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/backend-test-utils@0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/catalog-client@1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/catalog-model@1.1.3-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/config@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## @backstage/config-loader@1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/create-app@0.4.33-next.0 + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to `v1.3.9` which fixes a `.tsx` parser bug. You may want to run `yarn backstage-cli versions:bump` to get on latest version including the CLI itself. +- 80bfac5266: Updated the create-app command to no longer require Git to be installed and configured. A git repository will only be initialized if possible and if not already in an git repository. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## @backstage/integration-react@1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + +## @backstage/test-utils@1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/types@1.0.1-next.0 + +### Patch Changes + +- 30e43717c7: Added the `HumanDuration` type, moved here from `@backstage/backend-tasks`. This type matches the `Duration.fromObject` form of `luxon`. + +## @backstage/plugin-adr@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-adr-backend@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-adr-common@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-airbrake@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/dev-utils@1.0.8-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-allure@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + +## @backstage/plugin-api-docs@0.8.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + +## @backstage/plugin-auth-backend@0.17.1-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-auth-node@0.2.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-azure-devops@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-bazaar-backend@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + +## @backstage/plugin-bitrise@0.1.38-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog@1.6.1-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-catalog-backend@1.5.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.1.11-next.0 + +### Patch Changes + +- bae3617be5: `AwsS3EntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- defb389ecd: Add `awsS3EntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.9-next.0 + +### Patch Changes + +- 87ff05892d: `AzureDevOpsEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- 0ca399b31b: Add `azureDevOpsEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.3-next.0 + +### Patch Changes + +- 68f7f5a857: `BitbucketServerEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- cd48ed8370: Add `bitbucketServerEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.6-next.0 + +### Patch Changes + +- 4fba50f5d4: Add `gerritEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- 134b69f478: `GerritEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.9-next.0 + +### Patch Changes + +- 6bb046bcbe: Add `gitlabDiscoveryEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- 81cedb5033: `GitlabDiscoveryEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.4-next.0 + +### Patch Changes + +- 8d1a5e08ca: `MicrosoftGraphOrgEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- 384f99c276: Add `microsoftGraphOrgEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.4-next.0 + +### Patch Changes + +- 4ce887400d: Added support to use the `UrlReaders` when `$ref` pointing to a URL. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-common@1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-catalog-graph@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-import@0.9.1-next.0 + +### Patch Changes + +- 1e7b640518: Get rid of `this-is-undefined-in-esm` warning +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-catalog-node@1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-catalog-react@1.2.1-next.0 + +### Patch Changes + +- a889314692: Both `EntityProvider` and `AsyncEntityProvider` contexts now wrap all children with an `AnalyticsContext` containing the corresponding `entityRef`; this opens up the possibility for all events underneath these contexts to be associated with and aggregated by the corresponding entity. +- e47f466f80: Removed forced capitalization for Entity types in the catalog sidebar. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-cicd-statistics@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-cicd-statistics@0.1.13-next.0 + +## @backstage/plugin-circleci@0.3.11-next.0 + +### Patch Changes + +- 383574c49b: Update screenshots in documentation to match latest CircleCI plugin +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.4-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-codescene@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-dynatrace@1.0.1-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-explore-react@0.0.23-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore-react@0.0.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + +## @backstage/plugin-firehydrant@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.24-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.11-next.0 + +### Patch Changes + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.5-next.0 + +### Patch Changes + +- cc8bfc56c3: Add a new "Team" Filter Options to the Github Pull Requests Dashboard. + + When toggling this option on, the dashboard will displays all of the PRs opened + by the members of that team on any repositories of the organization. + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.29-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-catalog-graphql@0.3.15-next.0 + +## @backstage/plugin-home@0.4.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.10-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + +## @backstage/plugin-jenkins-backend@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + +## @backstage/plugin-jenkins-common@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-kafka@0.3.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-kubernetes@0.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kubernetes-common@0.4.4-next.0 + +### Patch Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + +## @backstage/plugin-lighthouse@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-org@0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.4-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.9-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-permission-backend@0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-permission-common@0.7.1-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 64848c963c: Properly handle rules that have no parameters in `PermissionClient` +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-permission-node@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-permission-react@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-playlist@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + +## @backstage/plugin-playlist-backend@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + +## @backstage/plugin-playlist-common@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + +## @backstage/plugin-proxy-backend@0.2.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-rollbar@0.4.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-scaffolder-common@1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + +## @backstage/plugin-search@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend-module-pg@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend-node@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-common@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + +## @backstage/plugin-search-react@1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-sentry@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-splunk-on-call@0.3.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-stack-overflow-backend@0.1.7-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/cli@0.21.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-tech-insights@0.3.2-next.0 + +### Patch Changes + +- 7095e8bc03: Fixed bug when sending data by Post in `runChecks` and `runBulkChecks` functions of the `TechInsightsClient` class, the default `Content-Type` used was `plain/text` +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-insights-backend@0.5.4-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-insights-common@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## @backstage/plugin-tech-insights-node@0.3.6-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-radar@0.5.18-next.0 + +### Patch Changes + +- 1f888af5f6: Fixed bug in Tech Radar where, on hover, the tech list quadrant would rerender and scroll top +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-backend@1.4.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + +### Patch Changes + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-node@1.4.2-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-techdocs-react@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-user-settings@0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-user-settings-backend@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-vault@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-xcmetrics@0.2.31-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- dcf9e728de: Removed an unused and hidden build details route. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## example-app@0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.8.0-next.0 + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-tech-insights@0.3.2-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-cost-insights@0.12.0-next.0 + - @backstage/plugin-dynatrace@1.0.1-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/plugin-catalog-import@0.9.1-next.0 + - @backstage/plugin-tech-radar@0.5.18-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-gcalendar@0.3.7-next.0 + - @backstage/plugin-code-coverage@0.2.4-next.0 + - @backstage/plugin-github-actions@0.5.11-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + - @backstage/plugin-circleci@0.3.11-next.0 + - @backstage/plugin-azure-sites@0.1.0-next.0 + - @backstage/plugin-cloudbuild@0.3.11-next.0 + - @backstage/plugin-jenkins@0.7.10-next.0 + - @backstage/plugin-kafka@0.3.11-next.0 + - @backstage/plugin-pagerduty@0.5.4-next.0 + - @backstage/plugin-rollbar@0.4.11-next.0 + - @backstage/plugin-airbrake@0.3.11-next.0 + - @backstage/plugin-api-docs@0.8.11-next.0 + - @backstage/plugin-azure-devops@0.2.2-next.0 + - @backstage/plugin-badges@0.2.35-next.0 + - @internal/plugin-catalog-customized@0.0.4-next.0 + - @backstage/plugin-catalog-graph@0.2.23-next.0 + - @backstage/plugin-explore@0.3.42-next.0 + - @backstage/plugin-gocd@0.1.17-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/plugin-kubernetes@0.7.4-next.0 + - @backstage/plugin-lighthouse@0.3.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.4-next.0 + - @backstage/plugin-org@0.5.11-next.0 + - @backstage/plugin-playlist@0.1.2-next.0 + - @backstage/plugin-search@1.0.4-next.0 + - @backstage/plugin-sentry@0.4.4-next.0 + - @backstage/plugin-todo@0.2.13-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-apache-airflow@0.2.4-next.0 + - @backstage/plugin-gcp-projects@0.3.30-next.0 + - @backstage/plugin-graphiql@0.2.43-next.0 + - @backstage/plugin-newrelic@0.3.29-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-shortcuts@0.3.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/plugin-user-settings@0.5.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## example-backend@0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-techdocs-backend@1.4.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.17.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.4-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/plugin-azure-sites-backend@0.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.8.0-next.0 + - example-app@0.2.77-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/plugin-azure-devops-backend@0.3.17-next.0 + - @backstage/plugin-badges-backend@0.1.32-next.0 + - @backstage/plugin-code-coverage-backend@0.2.4-next.0 + - @backstage/plugin-graphql-backend@0.1.28-next.0 + - @backstage/plugin-jenkins-backend@0.1.28-next.0 + - @backstage/plugin-kafka-backend@0.2.31-next.0 + - @backstage/plugin-permission-backend@0.5.13-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-playlist-backend@0.2.1-next.0 + - @backstage/plugin-proxy-backend@0.2.32-next.0 + - @backstage/plugin-rollbar-backend@0.1.35-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 + - @backstage/plugin-search-backend@1.1.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.2-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 + - @backstage/plugin-todo-backend@0.1.35-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## example-backend-next@0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/backend-defaults@0.1.3-next.0 + +## techdocs-cli-embedded-app@0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @internal/plugin-catalog-customized@0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + +## @internal/plugin-todo-list@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @internal/plugin-todo-list-common@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 diff --git a/package.json b/package.json index 85c4fa6e23..168f24787d 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@types/react": "^17", "@types/react-dom": "^17" }, - "version": "1.7.0", + "version": "1.8.0-next.0", "dependencies": { "@backstage/errors": "workspace:^", "@manypkg/get-packages": "^1.1.3", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 2dcaaa362a..12b6a0894f 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/app-defaults +## 1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/theme@0.2.16 + ## 1.0.7 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index de27f276a6..6658f0a8bd 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "1.0.7", + "version": "1.0.8-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index d0c822dd1e..d909773ff4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,68 @@ # example-app +## 0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.8.0-next.0 + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-tech-insights@0.3.2-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-cost-insights@0.12.0-next.0 + - @backstage/plugin-dynatrace@1.0.1-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/plugin-catalog-import@0.9.1-next.0 + - @backstage/plugin-tech-radar@0.5.18-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-gcalendar@0.3.7-next.0 + - @backstage/plugin-code-coverage@0.2.4-next.0 + - @backstage/plugin-github-actions@0.5.11-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + - @backstage/plugin-circleci@0.3.11-next.0 + - @backstage/plugin-azure-sites@0.1.0-next.0 + - @backstage/plugin-cloudbuild@0.3.11-next.0 + - @backstage/plugin-jenkins@0.7.10-next.0 + - @backstage/plugin-kafka@0.3.11-next.0 + - @backstage/plugin-pagerduty@0.5.4-next.0 + - @backstage/plugin-rollbar@0.4.11-next.0 + - @backstage/plugin-airbrake@0.3.11-next.0 + - @backstage/plugin-api-docs@0.8.11-next.0 + - @backstage/plugin-azure-devops@0.2.2-next.0 + - @backstage/plugin-badges@0.2.35-next.0 + - @internal/plugin-catalog-customized@0.0.4-next.0 + - @backstage/plugin-catalog-graph@0.2.23-next.0 + - @backstage/plugin-explore@0.3.42-next.0 + - @backstage/plugin-gocd@0.1.17-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/plugin-kubernetes@0.7.4-next.0 + - @backstage/plugin-lighthouse@0.3.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.4-next.0 + - @backstage/plugin-org@0.5.11-next.0 + - @backstage/plugin-playlist@0.1.2-next.0 + - @backstage/plugin-search@1.0.4-next.0 + - @backstage/plugin-sentry@0.4.4-next.0 + - @backstage/plugin-todo@0.2.13-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-apache-airflow@0.2.4-next.0 + - @backstage/plugin-gcp-projects@0.3.30-next.0 + - @backstage/plugin-graphiql@0.2.43-next.0 + - @backstage/plugin-newrelic@0.3.29-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-shortcuts@0.3.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/plugin-user-settings@0.5.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.2.76 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 441bdd1138..9388f305f6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.76", + "version": "0.2.77-next.0", "private": true, "backstage": { "role": "frontend" diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 9db48afd49..d8b2cd7c3b 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-app-api +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.2 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 04a03677c0..0658176962 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-app-api", "description": "Core API used by Backstage backend apps", - "version": "0.2.2", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index c6211b05b4..3c219161d0 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/backend-common +## 0.16.0-next.0 + +### Minor Changes + +- a7607b5413: **BREAKING CHANGE**: The `UrlReader` interface has been updated to require that `readUrl` is implemented. `readUrl` has previously been optional to implement but a warning has been logged when calling its predecessor `read`. + The `read` method is now deprecated and will be removed in a future release. + +### Patch Changes + +- 55227712dd: Generated development HTTPS backend certificate is now checked for expiration date instead of file age. +- d05e1841ce: This patch adds GiteaURLReader to the available classes. It currently only reads single files via gitea's public repos api +- 210a3b5668: Small update to fix compatibility with newer versions of the `keyv` library +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.15.2 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 8def408fca..94b2338ab0 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.15.2", + "version": "0.16.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index a569ff349c..9defd1025a 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-defaults +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + ## 0.1.2 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 4de7123983..07fa8c16eb 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.1.2", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md index 3315f4a27f..60d4a9567c 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,15 @@ # example-backend-next +## 0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/backend-defaults@0.1.3-next.0 + ## 0.0.4 ### Patch Changes diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index f82f2fc4fc..003ae5d791 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-next", - "version": "0.0.4", + "version": "0.0.5-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index c45f0a968e..512f0562f6 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-plugin-api +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.1.3 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 59e1de9529..4804213f5c 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-plugin-api", "description": "Core API used by Backstage backend plugins", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 87d3acf503..ecef237ff0 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-tasks +## 0.3.7-next.0 + +### Patch Changes + +- 30e43717c7: Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.3.6 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 6c10478e6c..69ab5713dd 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.3.6", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index e32a82bab8..6847198298 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-test-utils +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.1.29 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index aeedaec8e3..96fb7657bd 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.29", + "version": "0.1.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index e519556f7f..098c836bd5 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,48 @@ # example-backend +## 0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-techdocs-backend@1.4.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.17.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.4-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/plugin-azure-sites-backend@0.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.8.0-next.0 + - example-app@0.2.77-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/plugin-azure-devops-backend@0.3.17-next.0 + - @backstage/plugin-badges-backend@0.1.32-next.0 + - @backstage/plugin-code-coverage-backend@0.2.4-next.0 + - @backstage/plugin-graphql-backend@0.1.28-next.0 + - @backstage/plugin-jenkins-backend@0.1.28-next.0 + - @backstage/plugin-kafka-backend@0.2.31-next.0 + - @backstage/plugin-permission-backend@0.5.13-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-playlist-backend@0.2.1-next.0 + - @backstage/plugin-proxy-backend@0.2.32-next.0 + - @backstage/plugin-rollbar-backend@0.1.35-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 + - @backstage/plugin-search-backend@1.1.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.2-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 + - @backstage/plugin-todo-backend@0.1.35-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.2.76 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 4e296616fc..0852985d82 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.76", + "version": "0.2.77-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index fbd49ff49e..f5b0e5b2c3 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + ## 1.1.1 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 9c4e79b079..b5f4d69858 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "1.1.1", + "version": "1.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 2ac459101c..87ca344f30 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/catalog-model +## 1.1.3-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 1.1.2 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 91016d6a33..0651b6d3ba 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-model", "description": "Types and validators that help describe the model of a Backstage Catalog", - "version": "1.1.2", + "version": "1.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index ad63532139..c3f0dde1fe 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/cli +## 0.21.0-next.0 + +### Minor Changes + +- 7539b36748: Added a new ESLint rule that restricts imports of Link from @material-ui + + The rule can be can be overridden in the following way: + + ```diff + module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + + restrictedImports: [ + + { name: '@material-ui/core', importNames: [] }, + + { name: '@material-ui/core/Link', importNames: [] }, + + ], + }); + ``` + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to version 1.3.9 which fixes a `.tsx` parser bug +- 9c767e8f45: Updated dependency `@svgr/plugin-jsx` to `6.5.x`. + Updated dependency `@svgr/plugin-svgo` to `6.5.x`. + Updated dependency `@svgr/rollup` to `6.5.x`. + Updated dependency `@svgr/webpack` to `6.5.x`. +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/release-manifests@0.0.6 + ## 0.20.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 20cf2d35cf..b62d3f8691 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.20.0", + "version": "0.21.0-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index b5b243d788..d6597c29a5 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/config-loader +## 1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 1.1.5 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 6a96e7841b..e322636d35 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "1.1.5", + "version": "1.1.6-next.0", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index a03770f991..35b324bd24 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/config +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + ## 1.0.3 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 018aeb47cf..4c962dbf87 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "1.0.3", + "version": "1.0.4-next.0", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 147a3737cc..07845f1031 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/core-app-api +## 1.2.0-next.0 + +### Minor Changes + +- 9b737e5f2e: Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. +- 127fcad26d: Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + ## 1.1.1 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 7d4b6eba30..09d5c75fa8 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "1.1.1", + "version": "1.2.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 0d58a7032a..00b1c55fdc 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/core-components +## 0.12.0-next.0 + +### Minor Changes + +- fb3733e446: **BREAKING**: Removed the `HomepageTimer` as it has been replaced by the `HeaderWorldClock` in the Home plugin and was deprecated over a year ago. + +### Patch Changes + +- 5f695c219a: Set the `searchTooltip` to "Filter" to follow how the `searchPlaceholder` is set making this more consistent +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 858986f6b6: Disable base path workaround in `Link` component when React Router v6 stable is used. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + ## 0.11.2 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 4c7e9fb4db..33dfc762c9 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.11.2", + "version": "0.12.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 8b167be430..4f93cebf5d 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/core-plugin-api +## 1.1.0-next.0 + +### Minor Changes + +- a228f113d0: The app `Router` component now accepts an optional `basename` property. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + ## 1.0.7 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 1d8e21344c..7de98c55f9 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "1.0.7", + "version": "1.1.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 52ed5caee8..d8d97e178c 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/create-app +## 0.4.33-next.0 + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to `v1.3.9` which fixes a `.tsx` parser bug. You may want to run `yarn backstage-cli versions:bump` to get on latest version including the CLI itself. +- 80bfac5266: Updated the create-app command to no longer require Git to be installed and configured. A git repository will only be initialized if possible and if not already in an git repository. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/cli-common@0.1.10 + ## 0.4.32 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 61384b5d0b..a2a168b263 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.32", + "version": "0.4.33-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 9513c71468..7177a25fb2 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/dev-utils +## 1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + ## 1.0.7 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 225f4e9a78..0decdbe985 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "1.0.7", + "version": "1.0.8-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 23978aeab8..f64792a5a3 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/errors +## 1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + ## 1.1.2 ### Patch Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index 9abfeaaaaa..9b9e0dcd1d 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/errors", "description": "Common utilities for error handling within Backstage", - "version": "1.1.2", + "version": "1.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 07d8fa516f..b396593d12 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/integration-react +## 1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 1.1.5 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 398ca58f36..7efea2e74a 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "1.1.5", + "version": "1.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 244516e70e..4c34e91f8d 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/integration +## 1.4.0-next.0 + +### Minor Changes + +- d05e1841ce: This patch brings Gitea as a valid integration: target, via the ScmIntegration interface. It adds gitea to the relevant static properties (get integration by name, get integration by type) for plugins to be able to reference the same Gitea server. +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + + Deprecates: + + - `getGitHubFileFetchUrl` replaced by `getGithubFileFetchUrl` + - `GitHubIntegrationConfig` replaced by `GithubIntegrationConfig` + - `GitHubIntegration` replaced by `GithubIntegration` + - `readGitHubIntegrationConfig` replaced by `readGithubIntegrationConfig` + - `readGitHubIntegrationConfigs` replaced by `readGithubIntegrationConfigs` + - `replaceGitHubUrlType` replaced by `replaceGithubUrlType` + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 1.3.2 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index add2b07a93..bbed7de2d1 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration", "description": "Helpers for managing integrations towards external systems", - "version": "1.3.2", + "version": "1.4.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index ffabaf3ffb..fc7dbe743b 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,24 @@ # techdocs-cli-embedded-app +## 0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 0.2.75 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 2bcf099d80..c34c52e3f2 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.75", + "version": "0.2.76-next.0", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index c5f85ab548..26f085b6ce 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @techdocs/cli +## 1.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + ## 1.2.2 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 1d721109bf..10b738c025 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.2.2", + "version": "1.2.3-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index c891069ab5..c2e2975656 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/test-utils +## 1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 1.2.1 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index cf0bf2b225..70e6dca4cc 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "1.2.1", + "version": "1.2.2-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 4c0b2f6083..9070442f8f 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/types +## 1.0.1-next.0 + +### Patch Changes + +- 30e43717c7: Added the `HumanDuration` type, moved here from `@backstage/backend-tasks`. This type matches the `Duration.fromObject` form of `luxon`. + ## 1.0.0 ### Major Changes diff --git a/packages/types/package.json b/packages/types/package.json index 3c2bea13c0..a9c340cec3 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/types", "description": "Common TypeScript types used within Backstage", - "version": "1.0.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md index 586caf36a1..84143ccf78 100644 --- a/plugins/adr-backend/CHANGELOG.md +++ b/plugins/adr-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-adr-backend +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index 30b623ace0..ccbd3f119e 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr-backend", - "version": "0.2.2", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md index be906e2ddc..f9093a1dcf 100644 --- a/plugins/adr-common/CHANGELOG.md +++ b/plugins/adr-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-adr-common +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json index e62be75b1c..e9a9edc597 100644 --- a/plugins/adr-common/package.json +++ b/plugins/adr-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-adr-common", "description": "Common functionalities for the adr plugin", - "version": "0.2.2", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md index 40a627a340..e1f34381e4 100644 --- a/plugins/adr/CHANGELOG.md +++ b/plugins/adr/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-adr +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 8e4835eb31..e6113c7a83 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr", - "version": "0.2.2", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md index 0df7e1669c..38ed0acbc7 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-airbrake-backend +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.2.10 ### Patch Changes diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index d35d005137..f8904563a4 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake-backend", - "version": "0.2.10", + "version": "0.2.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index 68bc61ff92..a714e4d69f 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-airbrake +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/dev-utils@1.0.8-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + ## 0.3.10 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index dd65baa46d..3f04501019 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.10", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index 5b6330f11e..0f57d596c5 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-allure +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.26 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index ae0f87fb1e..68808cf511 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.26", + "version": "0.1.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index 1d16fce74d..149215b223 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-analytics-module-ga +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 0.1.21 ### Patch Changes diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index dc3dd253f4..ac94ad8979 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.21", + "version": "0.1.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index aa40bfc9b2..b4dee086eb 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-apache-airflow +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index b66067cb6e..bba1177b06 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.2.3", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 630587a60e..de93784591 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-api-docs +## 0.8.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/theme@0.2.16 + ## 0.8.10 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 1a5c8ae07c..ecb6fe3ff4 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.8.10", + "version": "0.8.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/apollo-explorer/CHANGELOG.md b/plugins/apollo-explorer/CHANGELOG.md index 3a56b728fe..5ef5cb16f9 100644 --- a/plugins/apollo-explorer/CHANGELOG.md +++ b/plugins/apollo-explorer/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-apollo-explorer +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 0.1.3 ### Patch Changes diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index 3e2f958fe7..91fe56950e 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apollo-explorer", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index ba4a4b9ea4..81c2020152 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-app-backend +## 0.3.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + ## 0.3.37 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index d3b14e6afd..5e72d7b666 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.37", + "version": "0.3.38-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 11c6cedb8e..a65fa417d8 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-auth-backend +## 0.17.1-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.17.0 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 23482f8eb7..4b3d7910e5 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.17.0", + "version": "0.17.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index ab1229ea69..aac75b87e9 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-node +## 0.2.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.6 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index f657a2df01..79dcc6d6b8 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.2.6", + "version": "0.2.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index c7721c604d..dfaffa8e35 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-azure-devops-backend +## 0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.3.16 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index e429240ab3..940b24ed44 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.3.16", + "version": "0.3.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 7c5c8983bf..1317547b7f 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-azure-devops +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 3e1a69b3dc..a2ffd77c84 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.2.1", + "version": "0.2.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-sites-backend/CHANGELOG.md b/plugins/azure-sites-backend/CHANGELOG.md new file mode 100644 index 0000000000..b06742648f --- /dev/null +++ b/plugins/azure-sites-backend/CHANGELOG.md @@ -0,0 +1,14 @@ +# @backstage/plugin-azure-sites-backend + +## 0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/config@1.0.4-next.0 diff --git a/plugins/azure-sites-backend/package.json b/plugins/azure-sites-backend/package.json index 6930f36587..f6529b7a3d 100644 --- a/plugins/azure-sites-backend/package.json +++ b/plugins/azure-sites-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-sites-backend", - "version": "0.0.0", + "version": "0.1.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-sites-common/CHANGELOG.md b/plugins/azure-sites-common/CHANGELOG.md new file mode 100644 index 0000000000..387e8d9ddd --- /dev/null +++ b/plugins/azure-sites-common/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/plugin-azure-sites-common + +## 0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. diff --git a/plugins/azure-sites-common/package.json b/plugins/azure-sites-common/package.json index eb0093bf65..568edaeff9 100644 --- a/plugins/azure-sites-common/package.json +++ b/plugins/azure-sites-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-azure-sites-common", "description": "Common functionalities for the azure plugin", - "version": "0.0.0", + "version": "0.1.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-sites/CHANGELOG.md b/plugins/azure-sites/CHANGELOG.md new file mode 100644 index 0000000000..4e1b2bb12b --- /dev/null +++ b/plugins/azure-sites/CHANGELOG.md @@ -0,0 +1,18 @@ +# @backstage/plugin-azure-sites + +## 0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/theme@0.2.16 diff --git a/plugins/azure-sites/package.json b/plugins/azure-sites/package.json index 2ffe0225e6..feff8e8645 100644 --- a/plugins/azure-sites/package.json +++ b/plugins/azure-sites/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-sites", - "version": "0.0.0", + "version": "0.1.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index 8e71d4f13f..c88b7cf763 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-badges-backend +## 0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.31 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 0ca4eccccf..a8e465597c 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.1.31", + "version": "0.1.32-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index d2f5756609..f736588933 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-badges +## 0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.2.34 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 0d212b70b5..68d47e08ac 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.34", + "version": "0.2.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index ef7e924dcb..d0524bd9f5 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-bazaar-backend +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 6ac340184e..2f0e766917 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.2.0", + "version": "0.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index 82269fc0ba..7ff38b63f8 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-bazaar +## 0.2.0-next.0 + +### Minor Changes + +- 28b39e0e0e: The limit prop of BazaarOverviewCard has been removed entirely, and instead replaced with a new optional boolean prop `fullWidth`. The BazaarOverviewCard now always use full height without fixed width. Also fixed problem with link to Bazaar. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.25 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index 522d84aeec..eb0542a504 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.25", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index c9befa2959..d3ab58bf5a 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index f7f1ffceac..fbc63e4497 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", "description": "Common functionalities for bitbucket-cloud plugins", - "version": "0.2.0", + "version": "0.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 9e6d7a9306..2ec214ea00 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-bitrise +## 0.1.38-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.37 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 21dc188246..cb23af3b90 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.37", + "version": "0.1.38-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 47dbcd7f53..59ab7970f2 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.1.11-next.0 + +### Patch Changes + +- bae3617be5: `AwsS3EntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/aws-s3/discovery + +- defb389ecd: Add `awsS3EntityProviderCatalogModule` (new backend-plugin-api, alpha). +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 37acb57254..a000ee9d84 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", "description": "A Backstage catalog backend module that helps integrate towards AWS", - "version": "0.1.10", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 7c40b06228..7fed636045 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.9-next.0 + +### Patch Changes + +- 87ff05892d: `AzureDevOpsEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/azure/discovery + +- 0ca399b31b: Add `azureDevOpsEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index b4396a32af..204321f876 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", "description": "A Backstage catalog backend module that helps integrate towards Azure", - "version": "0.1.8", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index a024b12275..47cc94f257 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index f3564463cc..93a9407aa1 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.1.4", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 9c9b61247f..1224da8bdd 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.3-next.0 + +### Patch Changes + +- 68f7f5a857: `BitbucketServerEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/bitbucketServer/discovery + +- cd48ed8370: Add `bitbucketServerEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 30d5cef631..594b5f0852 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.2", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md index ebc6e7b9df..571c18de00 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 2d4eae3b8f..8ee6661b01 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.2.4", + "version": "0.2.5-next.0", "deprecated": true, "main": "src/index.ts", "types": "src/index.ts", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index e5ba22ced6..a6babaf4fc 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.6-next.0 + +### Patch Changes + +- 4fba50f5d4: Add `gerritEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- 134b69f478: `GerritEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/gerrit/discovery + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 9b57cf949b..c6d342640f 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.5", + "version": "0.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 9c89e00de4..b89231a422 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-catalog-backend-module-github +## 0.2.0-next.0 + +### Minor Changes + +- 67fe5bc9a9: BREAKING: Support authenticated backends by including a server token for catalog requests. The constructor of `GithubLocationAnalyzer` now requires an instance of `TokenManager` to be supplied: + + ```diff + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + + tokenManager: env.tokenManager, + }), + ); + ... + ``` + +- f64d66a45c: Added the ability for the GitHub discovery provider to validate that catalog files exist before emitting them. + + Users can now set the `validateLocationsExist` property to `true` in their GitHub discovery configuration to opt in to this feature. + This feature only works with `catalogPath`s that do not contain wildcards. + + When `validateLocationsExist` is set to `true`, the GitHub discovery provider will retrieve the object from the + repository at the provided `catalogPath`. + If this file exists and is non-empty, then it will be emitted as a location for further processing. + If this file does not exist or is empty, then it will not be emitted. + Not emitting locations that do not exist allows for far fewer calls to the GitHub API to validate locations that do not exist. + +### Patch Changes + +- 67fe5bc9a9: Properly derive Github credentials when making requests in `GithubLocationAnalyzer` to support Github App authentication +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index e1a3ed39b8..60185636ea 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-github", "description": "A Backstage catalog backend module that helps integrate towards GitHub", - "version": "0.1.8", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 65979281e7..18cce9e01e 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.1.9-next.0 + +### Patch Changes + +- 6bb046bcbe: Add `gitlabDiscoveryEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- 81cedb5033: `GitlabDiscoveryEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/gitlab/discovery + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index a0a0cfc38f..e556d4b2de 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", "description": "A Backstage catalog backend module that helps integrate towards GitLab", - "version": "0.1.8", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 8045a29598..8a58fce1f5 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.5.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index eb97de27d0..d095be6b5c 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend module that helps integrate towards LDAP", - "version": "0.5.4", + "version": "0.5.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 4112931043..8b8d4479d3 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.4.4-next.0 + +### Patch Changes + +- 8d1a5e08ca: `MicrosoftGraphOrgEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-msgraph#readme + +- 384f99c276: Add `microsoftGraphOrgEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index c4c29a065d..cf301c71a0 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", - "version": "0.4.3", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index a11be4a5f2..0dc46b07a7 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.4-next.0 + +### Patch Changes + +- 4ce887400d: Added support to use the `UrlReaders` when `$ref` pointing to a URL. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 1f32a1a4f2..6f21b8b2e0 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 60ecdae3b9..494f332c12 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-catalog-backend +## 1.5.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.5.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d3e44d47b1..b18a133505 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "1.5.0", + "version": "1.5.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 2ecbca904f..d1378e9418 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-common +## 1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.0.7 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index b700002f9c..8af6d06c5a 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "1.0.7", + "version": "1.0.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-customized/CHANGELOG.md b/plugins/catalog-customized/CHANGELOG.md index 3f659df9b3..04be429ab6 100644 --- a/plugins/catalog-customized/CHANGELOG.md +++ b/plugins/catalog-customized/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-catalog-customized +## 0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + ## 0.0.3 ### Patch Changes diff --git a/plugins/catalog-customized/package.json b/plugins/catalog-customized/package.json index bb5564c135..537180f05e 100644 --- a/plugins/catalog-customized/package.json +++ b/plugins/catalog-customized/package.json @@ -1,7 +1,7 @@ { "name": "@internal/plugin-catalog-customized", "description": "The internal Backstage Customizable plugin for browsing the Backstage catalog", - "version": "0.0.3", + "version": "0.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index a6a2f5770b..7f8e1e0277 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-graph +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.2.22 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index ccfbd5160e..3b78d5d7ba 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.2.22", + "version": "0.2.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index 1546bcb395..97b2a49835 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-graphql +## 0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.3.14 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 0a48e3d58b..503c7eb3b0 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-graphql", "description": "An experimental Backstage catalog GraphQL module", - "version": "0.3.14", + "version": "0.3.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 3bb70d5d17..8562b77510 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-import +## 0.9.1-next.0 + +### Patch Changes + +- 1e7b640518: Get rid of `this-is-undefined-in-esm` warning +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + ## 0.9.0 ### Minor Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 66b4648c05..b8cacc4450 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.9.0", + "version": "0.9.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index cbce789d3c..ffbd18cd8e 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-node +## 1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + ## 1.2.0 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 14e1986745..320f372249 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-node", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", - "version": "1.2.0", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index a58a75687c..3019049086 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-react +## 1.2.1-next.0 + +### Patch Changes + +- a889314692: Both `EntityProvider` and `AsyncEntityProvider` contexts now wrap all children with an `AnalyticsContext` containing the corresponding `entityRef`; this opens up the possibility for all events underneath these contexts to be associated with and aggregated by the corresponding entity. +- e47f466f80: Removed forced capitalization for Entity types in the catalog sidebar. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-catalog-common@1.0.8-next.0 + ## 1.2.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 6a66a2e21a..c2363fdc76 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.2.0", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index b40f04f501..e95afd8617 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog +## 1.6.1-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.6.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index ad7e9a75c2..de2833c470 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "1.6.0", + "version": "1.6.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md index 2d3dbc8eed..e394418c24 100644 --- a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md +++ b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cicd-statistics-module-gitlab +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-cicd-statistics@0.1.13-next.0 + ## 0.1.6 ### Patch Changes diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json index e0fdf00b7b..5f6d5039bf 100644 --- a/plugins/cicd-statistics-module-gitlab/package.json +++ b/plugins/cicd-statistics-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics-module-gitlab", "description": "CI/CD Statistics plugin module; Gitlab CICD", - "version": "0.1.6", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 24e4a2f6e3..29852c597f 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-cicd-statistics +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + ## 0.1.12 ### Patch Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index d198ff0934..5cfac4666e 100644 --- a/plugins/cicd-statistics/package.json +++ b/plugins/cicd-statistics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics", "description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)", - "version": "0.1.12", + "version": "0.1.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 8d5199d550..b9d916f75a 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-circleci +## 0.3.11-next.0 + +### Patch Changes + +- 383574c49b: Update screenshots in documentation to match latest CircleCI plugin +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.3.10 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 98b6334c35..d5f36ba7d6 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.3.10", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 30a7410a08..bc83c46375 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-cloudbuild +## 0.3.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.3.10 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index eb01b8df4a..ee2cf54177 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.3.10", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md index 367eb96673..20965a33a5 100644 --- a/plugins/code-climate/CHANGELOG.md +++ b/plugins/code-climate/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-code-climate +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.10 ### Patch Changes diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 548f93233c..735c5d2c5e 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-climate", - "version": "0.1.10", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 891194c7a4..251cfa1b33 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-code-coverage-backend +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 147a2b92ac..70be7268cf 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.2.3", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index c724283ddb..5ccbf4eed0 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-code-coverage +## 0.2.4-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.2.3 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index d9e68a5f62..647c7d2fb0 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.2.3", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/codescene/CHANGELOG.md b/plugins/codescene/CHANGELOG.md index cdfaf94a71..d21ec8c71d 100644 --- a/plugins/codescene/CHANGELOG.md +++ b/plugins/codescene/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-codescene +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.5 ### Patch Changes diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 4f8f9a365f..ffd297e8ed 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-codescene", - "version": "0.1.5", + "version": "0.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index af63c96be1..6d62429875 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-config-schema +## 0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.33 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index b6a23dd2aa..b111738405 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.33", + "version": "0.1.34-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 02f9902fce..ce062fa7e1 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-cost-insights +## 0.12.0-next.0 + +### Minor Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues + +### Patch Changes + +- cbe11d1e23: Tweak README +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + ## 0.11.32 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index a1ec810a64..b5859fa886 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.32", + "version": "0.12.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/dynatrace/CHANGELOG.md b/plugins/dynatrace/CHANGELOG.md index 125976f131..4ab45e9df6 100644 --- a/plugins/dynatrace/CHANGELOG.md +++ b/plugins/dynatrace/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-dynatrace +## 1.0.1-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 1.0.0 ### Patch Changes diff --git a/plugins/dynatrace/package.json b/plugins/dynatrace/package.json index 7e024c439b..bdd54bf865 100644 --- a/plugins/dynatrace/package.json +++ b/plugins/dynatrace/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-dynatrace", - "version": "1.0.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 08eb87afc6..df0c013892 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @internal/plugin-todo-list-backend +## 1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 1.0.6 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 5bffe602ab..9d6419a76d 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.6", + "version": "1.0.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index 09b70db880..747dd912c0 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + ## 1.0.5 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 17d67e99f7..23a16e10c7 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.5", + "version": "1.0.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 9ce9eaae57..f08094181d 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/plugin-todo-list +## 1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 1.0.6 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 8bbf1f2e20..0b12ae6208 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.6", + "version": "1.0.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index 19e15c302f..c139c37348 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-explore-react +## 0.0.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + ## 0.0.22 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 9b5d3792cc..27ee4c23df 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore-react", "description": "A frontend library for Backstage plugins that want to interact with the explore plugin", - "version": "0.0.22", + "version": "0.0.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 09569f7206..bc31cd1ec6 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-explore +## 0.3.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-explore-react@0.0.23-next.0 + - @backstage/theme@0.2.16 + ## 0.3.41 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 0116a85d6a..7ff382b9ce 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.41", + "version": "0.3.42-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index 3516fa3d45..cd01270cbe 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-firehydrant +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 0.1.27 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 97831712ef..2c455fbd82 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.27", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 231655bedc..bd43f4d7b8 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-fossa +## 0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.2.42 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index c5be2f84cb..32d9da5146 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.42", + "version": "0.2.43-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md index 3dbedd4f66..3e4cc2473c 100644 --- a/plugins/gcalendar/CHANGELOG.md +++ b/plugins/gcalendar/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-gcalendar +## 0.3.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.3.6 ### Patch Changes diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 96b15932e6..c6f748c6a2 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.3.6", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 82e4638637..3e4f8df529 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-gcp-projects +## 0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 0.3.29 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index b180c40f45..6b1ea7a0d4 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.29", + "version": "0.3.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index 9280dc38c2..00b079b7cd 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-git-release-manager +## 0.3.24-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/theme@0.2.16 + ## 0.3.23 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index de4b23cac3..9b3533d994 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.23", + "version": "0.3.24-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index a4c0eed8c2..b3570d20cf 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-github-actions +## 0.5.11-next.0 + +### Patch Changes + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.5.10 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 3540e00549..8247c48fa5 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.5.10", + "version": "0.5.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 120740492c..0a9a4215df 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-github-deployments +## 0.1.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.41 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index eaaaac9ae1..a25fab05db 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.41", + "version": "0.1.42-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-issues/CHANGELOG.md b/plugins/github-issues/CHANGELOG.md index 8d5aa17129..a812495a5f 100644 --- a/plugins/github-issues/CHANGELOG.md +++ b/plugins/github-issues/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-github-issues +## 0.2.0-next.0 + +### Minor Changes + +- ead285b9e4: **BREAKING**: Changed the casing of all exported types to have a lowercase "h" in "github". E.g. "GitHubIssuesPage" was renamed to "GithubIssuesPage". Please rename your imports where necessary. + +### Patch Changes + +- c8dd2a8c87: Stripping specific issues URL already present to target base issues URL. +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.2 ### Patch Changes diff --git a/plugins/github-issues/package.json b/plugins/github-issues/package.json index 0249fe3a94..3853e845a6 100644 --- a/plugins/github-issues/package.json +++ b/plugins/github-issues/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-issues", - "version": "0.1.2", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-pull-requests-board/CHANGELOG.md b/plugins/github-pull-requests-board/CHANGELOG.md index 7da9bf1098..a91ceae5f0 100644 --- a/plugins/github-pull-requests-board/CHANGELOG.md +++ b/plugins/github-pull-requests-board/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-github-pull-requests-board +## 0.1.5-next.0 + +### Patch Changes + +- cc8bfc56c3: Add a new "Team" Filter Options to the Github Pull Requests Dashboard. + + When toggling this option on, the dashboard will displays all of the PRs opened + by the members of that team on any repositories of the organization. + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.4 ### Patch Changes diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index e68e6b1377..c6f58ebf0f 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-pull-requests-board", "description": "A Backstage plugin that allows you to see all open Pull Requests for all the repositories owned by your team", - "version": "0.1.4", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index b8512bcd2e..a578fe2077 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-gitops-profiles +## 0.3.29-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 0.3.28 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 02e8256eec..000f1c4b6c 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.28", + "version": "0.3.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index 3d140260f4..4409bc5861 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-gocd +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.16 ### Patch Changes diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index d455c6bb98..e8ba4b9374 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gocd", "description": "A Backstage plugin that integrates towards GoCD", - "version": "0.1.16", + "version": "0.1.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index c4cbfa1646..912c4e972b 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-graphiql +## 0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 0.2.42 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 245155fc13..6f10a179a7 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.42", + "version": "0.2.43-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md index 427ac6206d..2fa54c680f 100644 --- a/plugins/graphql-backend/CHANGELOG.md +++ b/plugins/graphql-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-graphql-backend +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-catalog-graphql@0.3.15-next.0 + ## 0.1.27 ### Patch Changes diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index 0f44b3be6b..70c8fb0c6f 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphql-backend", "description": "An experimental Backstage backend plugin for GraphQL", - "version": "0.1.27", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index e64ac0be7e..b9c43b0af8 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-home +## 0.4.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 0.4.26 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 516a91cd74..8c267f4d25 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.26", + "version": "0.4.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index dc2a3c1959..d1810178cc 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-ilert +## 0.2.0-next.0 + +### Minor Changes + +- 0697af30da: Added support for multiple responders in alert list, added new tab with list to support iLert resource 'service', added new tab with list to support iLert resource 'status page' + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.36 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index ffe74c2572..5399a19bd7 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.36", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index 105a79e2bf..54573cdaec 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-jenkins-backend +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + ## 0.1.27 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 89c3076e7b..3a7a930f95 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.1.27", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins-common/CHANGELOG.md b/plugins/jenkins-common/CHANGELOG.md index cebfb0b0a5..fc9c74184d 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-jenkins-common +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + ## 0.1.9 ### Patch Changes diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 01215b674d..95158ea8bb 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins-common", - "version": "0.1.9", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index ff0df1f256..2a15d2917a 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-jenkins +## 0.7.10-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + ## 0.7.9 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 9927fb38ae..a77f3d2439 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.7.9", + "version": "0.7.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index 16dbd0612b..5999228785 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kafka-backend +## 0.2.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.30 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 677dad0f01..096170dc7e 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka-backend", "description": "A Backstage backend plugin that integrates towards Kafka", - "version": "0.2.30", + "version": "0.2.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 5b98e9ffb7..ec8563d64b 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kafka +## 0.3.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 0.3.10 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 966c3962a6..0bcb7dfa24 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.3.10", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 085617f8af..804112324d 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-kubernetes-backend +## 0.8.0-next.0 + +### Minor Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.7.3 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 3e96aeb347..4082bd5b3a 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.7.3", + "version": "0.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 5f561ede04..b85e3c00ee 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes-common +## 0.4.4-next.0 + +### Patch Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index afc3aefc34..48c04fb222 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-common", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", - "version": "0.4.3", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index ebc378d85c..2272804b22 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes +## 0.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 0.7.3 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index d56fb01c55..ee4da4b55f 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.7.3", + "version": "0.7.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 2ab7368212..8a5a1bee97 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-lighthouse +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + ## 0.3.10 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index f7243d2ce6..8c87e8eec3 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.3.10", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index d8bfa338d2..81efe251d3 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-newrelic-dashboard +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index 803ba77c1f..a8100bdd38 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.2.3", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index 8722383768..526010f2f8 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-newrelic +## 0.3.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 0.3.28 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 8f8b58835d..0eedad1ced 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.28", + "version": "0.3.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index d5d0425128..682da2da91 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org +## 0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.5.10 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 4fde7c83a6..507660f1a3 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.5.10", + "version": "0.5.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 157b091fc9..ec4a152a4d 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-pagerduty +## 0.5.4-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.5.3 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 4d22758614..64ab795489 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.5.3", + "version": "0.5.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop-backend/CHANGELOG.md b/plugins/periskop-backend/CHANGELOG.md index 2bedbee959..63ca36cc60 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-periskop-backend +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 3db0d54859..3a91af5252 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop-backend", - "version": "0.1.8", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop/CHANGELOG.md b/plugins/periskop/CHANGELOG.md index f27132f8f7..afec1859f2 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-periskop +## 0.1.9-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.8 ### Patch Changes diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 1661731d2f..53971e9930 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.8", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index a656f43ed4..d57c79ca08 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-permission-backend +## 0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.5.12 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 624accca06..16351d26b9 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.12", + "version": "0.5.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index c06b9a2ad1..31c212bffd 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-permission-common +## 0.7.1-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 64848c963c: Properly handle rules that have no parameters in `PermissionClient` +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.7.0 ### Minor Changes diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 8fb92a0d2b..394cbe896f 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-common", "description": "Isomorphic types and client for Backstage permissions and authorization", - "version": "0.7.0", + "version": "0.7.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 9082ab8337..2d31993117 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-permission-node +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.7.0 ### Minor Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 0111c4505a..33baf825ac 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.0", + "version": "0.7.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 43fce22e80..c8e1cb072e 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-react +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.4.6 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index c852ecec3e..857e1a3881 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.6", + "version": "0.4.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist-backend/CHANGELOG.md b/plugins/playlist-backend/CHANGELOG.md index 2a3d2061ce..afb6a2867f 100644 --- a/plugins/playlist-backend/CHANGELOG.md +++ b/plugins/playlist-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-playlist-backend +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index 38e0729844..6bb557d8a3 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist-backend", - "version": "0.2.0", + "version": "0.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist-common/CHANGELOG.md b/plugins/playlist-common/CHANGELOG.md index 2887b11c01..7dcf06740e 100644 --- a/plugins/playlist-common/CHANGELOG.md +++ b/plugins/playlist-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-playlist-common +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/playlist-common/package.json b/plugins/playlist-common/package.json index eccb95faf3..287c69495a 100644 --- a/plugins/playlist-common/package.json +++ b/plugins/playlist-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-playlist-common", "description": "Common functionalities for the playlist plugin", - "version": "0.1.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist/CHANGELOG.md b/plugins/playlist/CHANGELOG.md index ce6bac5960..b064b2e5b6 100644 --- a/plugins/playlist/CHANGELOG.md +++ b/plugins/playlist/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-playlist +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/playlist/package.json b/plugins/playlist/package.json index bbc4f52f32..df620fc97a 100644 --- a/plugins/playlist/package.json +++ b/plugins/playlist/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist", - "version": "0.1.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 948d4cb393..91c3419ccd 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.2.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.2.31 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index eafad6628c..2deee67f17 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.2.31", + "version": "0.2.32-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 13dca3c262..962b167902 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-rollbar-backend +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.1.34 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 032ba55b21..0fb2c0a4e0 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.34", + "version": "0.1.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index fc4d626bed..340e06d265 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-rollbar +## 0.4.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.4.10 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 84fcdd80a8..1c4b585f5f 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.4.10", + "version": "0.4.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index cac90c0a56..1a73a7fc41 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.12 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 8032ab5e09..a42bfdcf60 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.2.12", + "version": "0.2.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 9abf4e928e..388ad285f1 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.4.5 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 91da543f0e..6a08271e80 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.4.5", + "version": "0.4.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 2f19d95d78..52654ecc2a 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + ## 0.2.10 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 768ea468ae..d617fec38e 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.2.10", + "version": "0.2.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 3437603d8a..caf413992b 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/plugin-scaffolder-backend +## 1.8.0-next.0 + +### Minor Changes + +- ea14eb62a2: Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: + + - `scaffolder_task_count`: Tracks successful task runs. + + Labels: + + - `template`: The entity ref of the scaffolded template + - `user`: The entity ref of the user that invoked the template run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_task_duration`: a histogram which tracks the duration of a task run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `result`: A boolean describing whether the task ran successfully + + - `scaffolder_step_count`: a count that tracks each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_step_duration`: a histogram which tracks the duration of each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + ## 1.7.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index ab3b24dcdd..37565a8d7a 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "1.7.0", + "version": "1.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index f2db35b47b..b6f8b3e77b 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-common +## 1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + ## 1.2.1 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 2ccb5bdd4f..6db049aaed 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-common", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", - "version": "1.2.1", + "version": "1.2.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index d972491a64..709809f5f7 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder +## 1.8.0-next.0 + +### Minor Changes + +- edae17309e: Added props to override default Scaffolder page title, subtitle and pageTitleOverride. + Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component). + +### Patch Changes + +- 4830a3569f: Basic analytics instrumentation is now in place: + + - As users make their way through template steps, a `click` event is fired, including the step number. + - After a user clicks "Create" a `create` event is fired, including the name of the software that was just created. The template used at creation is set on the `entityRef` context key. + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + ## 1.7.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 0825e624d6..cfa69c128a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "1.7.0", + "version": "1.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index abccf644e2..30a3367bf4 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.0.3 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index c03084e2ab..a45c3c43e5 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", "description": "A module for the search backend that implements search using ElasticSearch", - "version": "1.0.3", + "version": "1.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 0a50ee4e4a..dcd0bbdc78 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-pg +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.4.1 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 355d118005..5e91bfabeb 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-pg", "description": "A module for the search backend that implements search using PostgreSQL", - "version": "0.4.1", + "version": "0.4.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 694e70c1f9..bfcd09dcad 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-node +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.0.3 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 121cc66423..a0c4964ccf 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-node", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", - "version": "1.0.3", + "version": "1.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 70e7e01a54..c2cc89ac5b 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-search-backend +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.1.0 ### Minor Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 7977346d82..d221cf1ea5 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend", "description": "The Backstage backend plugin that provides your backstage app with search", - "version": "1.1.0", + "version": "1.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index 732e1e7aeb..4ed96d5397 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-common +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + ## 1.1.0 ### Minor Changes diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 2c68b023a5..a0ec2513b4 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-common", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", - "version": "1.1.0", + "version": "1.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index 8eb6f526f2..d5f100d97c 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-react +## 1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.2.0 ### Minor Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index c4d93341e9..7097d8fa4e 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.2.0", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 79fce6a287..5e71299d1e 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-search +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.0.3 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index ec3a1d754d..7d0f6d4f52 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "1.0.3", + "version": "1.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 005fa9aaff..661950e6ca 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sentry +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.4.3 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 9a10fe7214..190e9b048b 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.4.3", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index 842b0d70a8..e918aa8608 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-shortcuts +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + ## 0.3.2 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index b37c5bba1a..2bd0204af7 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.3.2", + "version": "0.3.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sonarqube-backend/CHANGELOG.md b/plugins/sonarqube-backend/CHANGELOG.md index 8b34005df1..e752ee9a2b 100644 --- a/plugins/sonarqube-backend/CHANGELOG.md +++ b/plugins/sonarqube-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-sonarqube-backend +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/sonarqube-backend/package.json b/plugins/sonarqube-backend/package.json index baaa8cd488..59f02b2950 100644 --- a/plugins/sonarqube-backend/package.json +++ b/plugins/sonarqube-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube-backend", - "version": "0.1.2", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index bf72ac2e28..dd5aa80817 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sonarqube +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.4.2 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 18c1b316a9..b46cb5c0b1 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.4.2", + "version": "0.4.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index fe0a21bea4..02a736b782 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-splunk-on-call +## 0.3.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.3.34 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 5543765716..0434c7b07b 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.34", + "version": "0.3.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow-backend/CHANGELOG.md b/plugins/stack-overflow-backend/CHANGELOG.md index 5fc3631f54..50b989079a 100644 --- a/plugins/stack-overflow-backend/CHANGELOG.md +++ b/plugins/stack-overflow-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-stack-overflow-backend +## 0.1.7-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/cli@0.21.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.1.6 ### Patch Changes diff --git a/plugins/stack-overflow-backend/package.json b/plugins/stack-overflow-backend/package.json index 2fa1a69470..4714c29551 100644 --- a/plugins/stack-overflow-backend/package.json +++ b/plugins/stack-overflow-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow-backend", - "version": "0.1.6", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow/CHANGELOG.md b/plugins/stack-overflow/CHANGELOG.md index 45d6f8a73f..a725475f69 100644 --- a/plugins/stack-overflow/CHANGELOG.md +++ b/plugins/stack-overflow/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-stack-overflow +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 0.1.6 ### Patch Changes diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 6d4c2aff09..2b0910bbae 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow", - "version": "0.1.6", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 14e195b249..84a682dc8e 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + ## 0.1.21 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 9e4c5503c1..9a811bc65a 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend-module-jsonfc", - "version": "0.1.21", + "version": "0.1.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index db98317e31..b39c640ddb 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-tech-insights-backend +## 0.5.4-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + ## 0.5.3 ### Patch Changes diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index dd918cfc4f..6d42c1aecc 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend", - "version": "0.5.3", + "version": "0.5.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-common/CHANGELOG.md b/plugins/tech-insights-common/CHANGELOG.md index 13f502287e..80db666774 100644 --- a/plugins/tech-insights-common/CHANGELOG.md +++ b/plugins/tech-insights-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-tech-insights-common +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + ## 0.2.7 ### Patch Changes diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index 750a8f9ce7..016ccc9a2a 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-common", - "version": "0.2.7", + "version": "0.2.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index d9605bd033..8173dbc132 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-tech-insights-node +## 0.3.6-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 20cb0152cc..7300089744 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-node", - "version": "0.3.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index fa13c9a426..f60027e045 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-tech-insights +## 0.3.2-next.0 + +### Patch Changes + +- 7095e8bc03: Fixed bug when sending data by Post in `runChecks` and `runBulkChecks` functions of the `TechInsightsClient` class, the default `Content-Type` used was `plain/text` +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + ## 0.3.1 ### Patch Changes diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index cd32374fb5..cb9def285e 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.3.1", + "version": "0.3.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index d7ae58f097..df5bee360a 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-tech-radar +## 0.5.18-next.0 + +### Patch Changes + +- 1f888af5f6: Fixed bug in Tech Radar where, on hover, the tech list quadrant would rerender and scroll top +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + ## 0.5.17 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index bf20c2b4aa..a4dd1f0848 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.5.17", + "version": "0.5.18-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 04388b9c3d..baea1ab42f 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + ## 1.0.5 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index e5f52fed60..a3da234606 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.5", + "version": "1.0.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index f05171ce9d..b038fa202f 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-techdocs-backend +## 1.4.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.4.0 ### Minor Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index f191f39f67..5e74c47d3c 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-backend", "description": "The Backstage backend plugin that renders technical documentation for your components", - "version": "1.4.0", + "version": "1.4.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index dd22792833..c56e9cbf12 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.0.6-next.0 + +### Patch Changes + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/theme@0.2.16 + ## 1.0.5 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 786e167a0e..782273e8c0 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", "description": "Plugin module for contributed TechDocs Addons", - "version": "1.0.5", + "version": "1.0.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index b1b1d9a4c1..cc8770c34b 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs-node +## 1.4.2-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.4.1 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 692ca58fa9..eef2da8ccb 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-node", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "1.4.1", + "version": "1.4.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index fe2801aff2..9418517a6c 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-react +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + ## 1.0.5 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 1e0603799d..d7ff40bed8 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-react", "description": "Shared frontend utilities for TechDocs and Addons", - "version": "1.0.5", + "version": "1.0.6-next.0", "publishConfig": { "access": "public", "alphaTypes": "dist/index.alpha.d.ts", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index f266d5d1dd..9a6c681a82 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-techdocs +## 1.4.0-next.0 + +### Minor Changes + +- 5691baea69: Add ability to configure filters when using EntityListDocsGrid + + The following example will render two sections of cards grid: + + - One section for documentations tagged as `recommended` + - One section for documentations tagged as `runbook` + + ```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } + ]}} /> + ``` + +### Patch Changes + +- cbe11d1e23: Tweak README +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- 3a1a999b7b: Include query parameters when navigating to relative links in documents +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + ## 1.3.3 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 4e8a9dfc96..05b841fd0c 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "1.3.3", + "version": "1.4.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index 48fe718759..f4827591df 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-todo-backend +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.34 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 3c59254f89..ae227478b6 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo-backend", "description": "A Backstage backend plugin that lets you browse TODO comments in your source code", - "version": "0.1.34", + "version": "0.1.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index d126f9a9ad..c97576555d 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-todo +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.2.12 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index ef061277c4..c5cd8df216 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo", "description": "A Backstage plugin that lets you browse TODO comments in your source code", - "version": "0.2.12", + "version": "0.2.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 5cd32e82f5..54a75f3888 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-user-settings-backend +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 47aa6b5978..5015b60456 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings-backend", "description": "The Backstage backend plugin to manage user settings", - "version": "0.1.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index dfced55e24..d73d533cba 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-user-settings +## 0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.5.0 ### Minor Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 33a57ccc86..744cf40eec 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings", "description": "A Backstage plugin that provides a settings page", - "version": "0.5.0", + "version": "0.5.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/vault-backend/CHANGELOG.md b/plugins/vault-backend/CHANGELOG.md index dd9f5bb8bf..6f3270dba1 100644 --- a/plugins/vault-backend/CHANGELOG.md +++ b/plugins/vault-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-vault-backend +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index 27a6718c10..2cc170337a 100644 --- a/plugins/vault-backend/package.json +++ b/plugins/vault-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault-backend", "description": "A Backstage backend plugin that integrates towards Vault", - "version": "0.2.3", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/vault/CHANGELOG.md b/plugins/vault/CHANGELOG.md index 610fe46f12..ce0f6c41cf 100644 --- a/plugins/vault/CHANGELOG.md +++ b/plugins/vault/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-vault +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.1.4 ### Patch Changes diff --git a/plugins/vault/package.json b/plugins/vault/package.json index 182b091743..dc5e7b0c13 100644 --- a/plugins/vault/package.json +++ b/plugins/vault/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault", "description": "A Backstage plugin that integrates towards Vault", - "version": "0.1.4", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index 1461d15b9d..8c68404127 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-xcmetrics +## 0.2.31-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- dcf9e728de: Removed an unused and hidden build details route. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + ## 0.2.30 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index c890de6e86..6946095a01 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-xcmetrics", "description": "A Backstage plugin that shows XCode build metrics for your components", - "version": "0.2.30", + "version": "0.2.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/yarn.lock b/yarn.lock index 1b3b301817..4bf1f42703 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3291,6 +3291,17 @@ __metadata: languageName: unknown linkType: soft +"@backstage/catalog-client@npm:^1.1.1": + version: 1.1.1 + resolution: "@backstage/catalog-client@npm:1.1.1" + dependencies: + "@backstage/catalog-model": ^1.1.2 + "@backstage/errors": ^1.1.2 + cross-fetch: ^3.1.5 + checksum: 788c357e5a783ce46e4dcd805b302ffe2b407ad9bb04e49ee6b6aa8fb42adfd9cae8a57e2d18cd4c81ef22011990a5eb6518f3f44fb812485d7c99226db26e1d + languageName: node + linkType: hard + "@backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": version: 0.0.0-use.local resolution: "@backstage/catalog-client@workspace:packages/catalog-client" @@ -3303,7 +3314,22 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@^1.1.1, @backstage/catalog-model@^1.1.2, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@npm:^1.1.1, @backstage/catalog-model@npm:^1.1.2": + version: 1.1.2 + resolution: "@backstage/catalog-model@npm:1.1.2" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/errors": ^1.1.2 + "@backstage/types": ^1.0.0 + ajv: ^8.10.0 + json-schema: ^0.4.0 + lodash: ^4.17.21 + uuid: ^8.0.0 + checksum: d53e5db9f1662b6c2bd48b3b1fed97751b5bf8805575feb12ed2b6f600048d676f954fe4cfa214536d09f984535f0524a24c6330e08c4d255064d0eb62678bc1 + languageName: node + linkType: hard + +"@backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3517,7 +3543,17 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@^1.0.3, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@npm:^1.0.3": + version: 1.0.3 + resolution: "@backstage/config@npm:1.0.3" + dependencies: + "@backstage/types": ^1.0.0 + lodash: ^4.17.21 + checksum: 1026f741ac019043f46965eeb93c796110b7b98add181562a975e3ef9bf27332ea7841cd174a358a55c1edced892fc8b644263caf14f6c6d244b550a1176e1ad + languageName: node + linkType: hard + +"@backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -3563,7 +3599,58 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@^0.11.1, @backstage/core-components@^0.11.2, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@npm:^0.11.1, @backstage/core-components@npm:^0.11.2": + version: 0.11.2 + resolution: "@backstage/core-components@npm:0.11.2" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/errors": ^1.1.2 + "@backstage/theme": ^0.2.16 + "@backstage/version-bridge": ^1.0.1 + "@material-table/core": ^3.1.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + "@react-hookz/web": ^15.0.0 + "@types/react-sparklines": ^1.7.0 + "@types/react-text-truncate": ^0.14.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + history: ^5.0.0 + immer: ^9.0.1 + lodash: ^4.17.21 + pluralize: ^8.0.0 + prop-types: ^15.7.2 + qs: ^6.9.4 + rc-progress: 3.4.0 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-text-truncate: ^0.19.0 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.6 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.8.15 + zod: ^3.11.6 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + react-dom: ^16.13.1 || ^17.0.0 + react-router: 6.0.0-beta.0 || ^6.3.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: 510a0c2594a9aa820293d79fbaeee72bef9ac5e6161645d05e2c64b7176050386984befa07921391f9758bfbf07f96170662f7881e894fb0b0f2787378b19182 + languageName: node + linkType: hard + +"@backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -3635,7 +3722,25 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@^1.0.6, @backstage/core-plugin-api@^1.0.7, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": +"@backstage/core-plugin-api@npm:^1.0.6, @backstage/core-plugin-api@npm:^1.0.7": + version: 1.0.7 + resolution: "@backstage/core-plugin-api@npm:1.0.7" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/types": ^1.0.0 + "@backstage/version-bridge": ^1.0.1 + history: ^5.0.0 + prop-types: ^15.7.2 + zen-observable: ^0.8.15 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: ce07e5ec1f4f21a5e879e3ab2eb271721cc0eadd685a7457098afca1d8106140883fbc7c3cf7b436c594614152a88fcba7466df0900d9fa8e958baab2f8b1a6a + languageName: node + linkType: hard + +"@backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" dependencies: @@ -3721,6 +3826,17 @@ __metadata: languageName: unknown linkType: soft +"@backstage/errors@npm:^1.1.2": + version: 1.1.2 + resolution: "@backstage/errors@npm:1.1.2" + dependencies: + "@backstage/types": ^1.0.0 + cross-fetch: ^3.1.5 + serialize-error: ^8.0.1 + checksum: 1e8b58b8059af8e83ebc1c8fea94227ec887a55e3adc32ab959fa86ac20bed745703a5e046b17505c05e7306c1835834756d27cfc0cbeea2021cb9631656ed22 + languageName: node + linkType: hard + "@backstage/errors@workspace:^, @backstage/errors@workspace:packages/errors": version: 0.0.0-use.local resolution: "@backstage/errors@workspace:packages/errors" @@ -3732,7 +3848,26 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@^1.1.4, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": +"@backstage/integration-react@npm:^1.1.4": + version: 1.1.5 + resolution: "@backstage/integration-react@npm:1.1.5" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/integration": ^1.3.2 + "@backstage/theme": ^0.2.16 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + react-use: ^17.2.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + checksum: 2f50416e0ca7635a59b5bf4bf89b4cf8d91a755e7ba2e4be75fb338011f98533213f984ac2def7ff65fccd51d7a94ba064c59b47d0d1d41c2a4b51f54ed68992 + languageName: node + linkType: hard + +"@backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -3759,6 +3894,22 @@ __metadata: languageName: unknown linkType: soft +"@backstage/integration@npm:^1.3.2": + version: 1.3.2 + resolution: "@backstage/integration@npm:1.3.2" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/errors": ^1.1.2 + "@octokit/auth-app": ^4.0.0 + "@octokit/rest": ^19.0.3 + cross-fetch: ^3.1.5 + git-url-parse: ^13.0.0 + lodash: ^4.17.21 + luxon: ^3.0.0 + checksum: 960f0ea564021082f8b6e10a59782f6ffb80550dbd8a1b62e52d8b03bf72345759f0d744ef7c41434d32ad3987d4dd89e18645f818be6c00cd53e541c0a041a1 + languageName: node + linkType: hard + "@backstage/integration@workspace:^, @backstage/integration@workspace:packages/integration": version: 0.0.0-use.local resolution: "@backstage/integration@workspace:packages/integration" @@ -4776,7 +4927,18 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@^1.0.7, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@npm:^1.0.7": + version: 1.0.7 + resolution: "@backstage/plugin-catalog-common@npm:1.0.7" + dependencies: + "@backstage/catalog-model": ^1.1.2 + "@backstage/plugin-permission-common": ^0.7.0 + "@backstage/plugin-search-common": ^1.1.0 + checksum: 8a229ccbea8ce2bd0863dbf3694ac7f59690097d2607b92ea38aff78d6e5d2ac21f6d16c1a9b5bf7c09901ca7eb52d21e73480f4b1646d0aa94db8c589a3dcd5 + languageName: node + linkType: hard + +"@backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -4904,7 +5066,41 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@^1.1.4, @backstage/plugin-catalog-react@^1.2.0, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@npm:^1.1.4, @backstage/plugin-catalog-react@npm:^1.2.0": + version: 1.2.0 + resolution: "@backstage/plugin-catalog-react@npm:1.2.0" + dependencies: + "@backstage/catalog-client": ^1.1.1 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/errors": ^1.1.2 + "@backstage/integration": ^1.3.2 + "@backstage/plugin-catalog-common": ^1.0.7 + "@backstage/plugin-permission-common": ^0.7.0 + "@backstage/plugin-permission-react": ^0.4.6 + "@backstage/theme": ^0.2.16 + "@backstage/types": ^1.0.0 + "@backstage/version-bridge": ^1.0.1 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + classnames: ^2.2.6 + jwt-decode: ^3.1.0 + lodash: ^4.17.21 + qs: ^6.9.4 + react-use: ^17.2.4 + yaml: ^2.0.0 + zen-observable: ^0.8.15 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + react-router: 6.0.0-beta.0 || ^6.3.0 + checksum: c2875ac0af1e3e051b2faf018bd46b9a1843c2fd67fcfd1a09a7931ad7fbd6adf591200d184f40194702aa6f21757c7bb70636861d25ef2f1e670fca0cb0a7cf + languageName: node + linkType: hard + +"@backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -5795,7 +5991,31 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-home@^0.4.25, @backstage/plugin-home@workspace:^, @backstage/plugin-home@workspace:plugins/home": +"@backstage/plugin-home@npm:^0.4.25, @backstage/plugin-home@npm:^0.4.26": + version: 0.4.26 + resolution: "@backstage/plugin-home@npm:0.4.26" + dependencies: + "@backstage/catalog-model": ^1.1.2 + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 + "@backstage/plugin-stack-overflow": ^0.1.6 + "@backstage/theme": ^0.2.16 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + lodash: ^4.17.21 + react-use: ^17.2.4 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + react-router: 6.0.0-beta.0 || ^6.3.0 + checksum: bb20f2538a1e155bbb7c5587fe953ecaacd6f0384b951dcd862be7a2fa277ed80d793336953cc778b6a574ee898064a5aab436b3c61d3695944084c748449095 + languageName: node + linkType: hard + +"@backstage/plugin-home@workspace:^, @backstage/plugin-home@workspace:plugins/home": version: 0.0.0-use.local resolution: "@backstage/plugin-home@workspace:plugins/home" dependencies: @@ -6297,6 +6517,20 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-permission-common@npm:^0.7.0": + version: 0.7.0 + resolution: "@backstage/plugin-permission-common@npm:0.7.0" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/errors": ^1.1.2 + "@backstage/types": ^1.0.0 + cross-fetch: ^3.1.5 + uuid: ^8.0.0 + zod: ^3.11.6 + checksum: e71705494b55efacf234a46abe162f241ebecb231a6a4c76d7134bb629c1b7d1ffdead8f9725ea515a818ce376a99d2121dc7ef1c8d0badd955fdb1d4cd405cf + languageName: node + linkType: hard + "@backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" @@ -6334,6 +6568,24 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-permission-react@npm:^0.4.6": + version: 0.4.6 + resolution: "@backstage/plugin-permission-react@npm:0.4.6" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-permission-common": ^0.7.0 + cross-fetch: ^3.1.5 + react-use: ^17.2.4 + swr: ^1.1.2 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + react-router: 6.0.0-beta.0 || ^6.3.0 + checksum: 7c8c619d1c0465ac6f48a5f841127f75fb3689c6b7948777f093b11a7a65ac1308cb30ab46658591e8463ccd8dac7bdbc098c4922b9fa858b307f2113ef2acef + languageName: node + linkType: hard + "@backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" @@ -6812,6 +7064,16 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-search-common@npm:^1.1.0": + version: 1.1.0 + resolution: "@backstage/plugin-search-common@npm:1.1.0" + dependencies: + "@backstage/plugin-permission-common": ^0.7.0 + "@backstage/types": ^1.0.0 + checksum: c14e91b5e180c345d47077e678a5b1b9a86620350171f5cafe799e24a815c07952bf817e2b8759019227c07ea93dcf0608ba321afa7ead68c4281e01f6b3b3de + languageName: node + linkType: hard + "@backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": version: 0.0.0-use.local resolution: "@backstage/plugin-search-common@workspace:plugins/search-common" @@ -7053,6 +7315,30 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-stack-overflow@npm:^0.1.6": + version: 0.1.6 + resolution: "@backstage/plugin-stack-overflow@npm:0.1.6" + dependencies: + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-home": ^0.4.26 + "@backstage/plugin-search-common": ^1.1.0 + "@backstage/theme": ^0.2.16 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@testing-library/jest-dom": ^5.10.1 + cross-fetch: ^3.1.5 + lodash: ^4.17.21 + qs: ^6.9.4 + react-use: ^17.2.4 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + checksum: e7900ed037f34746e4156106d4579f0474f272fa46b5320ece2e5a7fec967aacc8fc90194802369460648a15b4a78103f68745261b666668cde6957b255ba166 + languageName: node + linkType: hard + "@backstage/plugin-stack-overflow@workspace:^, @backstage/plugin-stack-overflow@workspace:plugins/stack-overflow": version: 0.0.0-use.local resolution: "@backstage/plugin-stack-overflow@workspace:plugins/stack-overflow" @@ -7689,7 +7975,14 @@ __metadata: languageName: unknown linkType: soft -"@backstage/types@^1.0.0, @backstage/types@workspace:^, @backstage/types@workspace:packages/types": +"@backstage/types@npm:^1.0.0": + version: 1.0.0 + resolution: "@backstage/types@npm:1.0.0" + checksum: 03a9809a8666a9caef2d14d6a521bfdbcde734e9af734f1918ebd25715c6a9d6ba99094a509b7fc59f19bac2c50bcc99fdd5216e64523f2831ada44859f63b38 + languageName: node + linkType: hard + +"@backstage/types@workspace:^, @backstage/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@backstage/types@workspace:packages/types" dependencies: From a4502f2041e1aeb8738ef1e97c175f6c40dc1b95 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 13:18:45 +0000 Subject: [PATCH 201/221] Update dependency passport-saml to v3.2.4 Signed-off-by: Renovate Bot --- yarn.lock | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1b3b301817..0df89d7e21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15345,6 +15345,13 @@ __metadata: languageName: node linkType: hard +"@xmldom/xmldom@npm:^0.7.6": + version: 0.7.6 + resolution: "@xmldom/xmldom@npm:0.7.6" + checksum: 3c31dcd909aaefd65090033bd45e0aca42d636a9ae43c7313f4be87de570d046abba28362810d63c0718d6f08a9ce5f8da27b87437afc24d2290b6d4e75a6eee + languageName: node + linkType: hard + "@xobotyi/scrollbar-width@npm:^1.9.5": version: 1.9.5 resolution: "@xobotyi/scrollbar-width@npm:1.9.5" @@ -31490,17 +31497,17 @@ __metadata: linkType: hard "passport-saml@npm:^3.1.2": - version: 3.2.3 - resolution: "passport-saml@npm:3.2.3" + version: 3.2.4 + resolution: "passport-saml@npm:3.2.4" dependencies: - "@xmldom/xmldom": ^0.7.5 + "@xmldom/xmldom": ^0.7.6 debug: ^4.3.2 passport-strategy: ^1.0.0 xml-crypto: ^2.1.3 xml-encryption: ^2.0.0 xml2js: ^0.4.23 xmlbuilder: ^15.1.1 - checksum: ccddfa37557ef39219b447285d9b87ad08309b3cd2537aab5df344fd3910d9b506eb36e4f24f2eec35047465f00fbea53b320674de9ae68ea18cbbce17de79f4 + checksum: 8e885af4d44c2d862b2ea0d051ab2a36bc6f9a70e62f90daf7ce4eefd126ac2ab4d5fc070693eba05f5e1be248af23fa018611bbfa7fad31708371f387f5dd77 languageName: node linkType: hard From 353bf41cee3da2f928e66c2294df3ed3f3102e0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 13:19:46 +0000 Subject: [PATCH 202/221] Update storybook monorepo to v6.5.13 Signed-off-by: Renovate Bot --- storybook/yarn.lock | 584 +++++++++++++++++++++++++++----------------- 1 file changed, 357 insertions(+), 227 deletions(-) diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 0852807334..be9a98e42a 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -1812,17 +1812,17 @@ __metadata: linkType: hard "@storybook/addon-a11y@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-a11y@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-a11y@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/theming": 6.5.12 + "@storybook/theming": 6.5.13 axe-core: ^4.2.0 core-js: ^3.8.2 global: ^4.4.0 @@ -1839,21 +1839,21 @@ __metadata: optional: true react-dom: optional: true - checksum: f93f3c4f4dd9f2f8cfc79200d6201a385d19a3d1bb71ed4b253347db2628f7f414d3479d5545302383a8ff7580bfa5f542ddc9c22ee95ff1abc005e152193580 + checksum: 6f1ba1f0d97d652a5346a33e051e1ff79aa07786532c6450c5c3dd677c2195e3ee8792cda756d4556e5fbaa2a72a0631843aec2880121b3439bad3e401c25359 languageName: node linkType: hard "@storybook/addon-actions@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-actions@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-actions@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/theming": 6.5.12 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 fast-deep-equal: ^3.1.3 global: ^4.4.0 @@ -1874,23 +1874,23 @@ __metadata: optional: true react-dom: optional: true - checksum: 94f433a6b0956e4301e5b46c68eb56f6a9b01b5ec314099611d584542369d1aec4878a5353052f963e462b1b5f3ce74070f0d03eadf30e275b0b88ef7b713dd8 + checksum: 2679174b1467281860cd6f11fac5ba505e44629eb11ee90713d1c954569cfb54d80aa822a0ed1be2f92f11ca62889fec3dca96f61b89cf238503e0cea4b1db9a languageName: node linkType: hard "@storybook/addon-controls@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-controls@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-controls@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-common": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-common": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/node-logger": 6.5.12 - "@storybook/store": 6.5.12 - "@storybook/theming": 6.5.12 + "@storybook/node-logger": 6.5.13 + "@storybook/store": 6.5.13 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 lodash: ^4.17.21 ts-dedent: ^2.0.0 @@ -1902,19 +1902,19 @@ __metadata: optional: true react-dom: optional: true - checksum: 27ee396ae4ab411b1bd99eacb0ebe747aa36300dc3b787d48eb685a446e64e33ff7df3b8713943132b6dbe3c78af3d2d6115b4da4c6196ec351d843690ab8a55 + checksum: a4f86332686b5681366ad1f1eebb50cb9939ce3424ade64c0043b94827df15a34da0190101c8dc9a2b4b9af67fafd9d3fb50a5b98ff7a24fed6a37a2f8f37f27 languageName: node linkType: hard "@storybook/addon-links@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-links@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-links@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.12 + "@storybook/router": 6.5.13 "@types/qs": ^6.9.5 core-js: ^3.8.2 global: ^4.4.0 @@ -1930,21 +1930,21 @@ __metadata: optional: true react-dom: optional: true - checksum: 9e1394bc2fed9019537f3fee9f60abc4ea866d1688df72a435052dd07f25a8bac848d6acc2beef92af0cebc3715e9db9ae1bd95c87311a8b9fad7dee988bbd1d + checksum: 0bbe14652320c77dbcbcd0a6f45e4776de35475cca43b9825c36424e6ccc250fac4a172cdf01304debdc3c93d74ad2ea3bafb8a752e9a52a5e860f5bb9a397c2 languageName: node linkType: hard "@storybook/addon-storysource@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-storysource@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-storysource@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/router": 6.5.12 - "@storybook/source-loader": 6.5.12 - "@storybook/theming": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/router": 6.5.13 + "@storybook/source-loader": 6.5.13 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 estraverse: ^5.2.0 loader-utils: ^2.0.0 @@ -1959,7 +1959,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 7ebaeffc1646229bebf7c9e918c225c73838b8972c00937e60928a5988440b3e6bf19225cf5f1d21c80aef59b2e20c6f473f6b5c386f5cebf7c9780d74b6ef18 + checksum: 35ad7b940df64b036c36edc1d203da8016203c2430ae98a380b33fef4ffbfe040651db61f54ef5a9e6edec298e58e660ddd933eb67a1563ce1e39faedbc56cba languageName: node linkType: hard @@ -1985,7 +1985,29 @@ __metadata: languageName: node linkType: hard -"@storybook/addons@npm:6.5.12, @storybook/addons@npm:^6.0.0, @storybook/addons@npm:^6.5.9": +"@storybook/addons@npm:6.5.13, @storybook/addons@npm:^6.5.9": + version: 6.5.13 + resolution: "@storybook/addons@npm:6.5.13" + dependencies: + "@storybook/api": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/router": 6.5.13 + "@storybook/theming": 6.5.13 + "@types/webpack-env": ^1.16.0 + core-js: ^3.8.2 + global: ^4.4.0 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 28589da00e8a26b44d4ed8a1938fe934187a85b187e2a0dcd3e6d114460ed5a07fbfe0a89a4d899739767b379015cbabadd47c5c266457922a8c4255e3d769a4 + languageName: node + linkType: hard + +"@storybook/addons@npm:^6.0.0": version: 6.5.12 resolution: "@storybook/addons@npm:6.5.12" dependencies: @@ -2063,27 +2085,55 @@ __metadata: languageName: node linkType: hard -"@storybook/builder-webpack4@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/builder-webpack4@npm:6.5.12" +"@storybook/api@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/api@npm:6.5.13" + dependencies: + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/router": 6.5.13 + "@storybook/semver": ^7.3.2 + "@storybook/theming": 6.5.13 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + store2: ^2.12.0 + telejson: ^6.0.8 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: dd7c8db0cdea2a47ab835c02217f10f99c54bfbf6d826deadf0b160ece4c94b1cb2558cfbaff4e4244c5c776095028a164762bd8de19fcfe10ae318fe0a3fbb4 + languageName: node + linkType: hard + +"@storybook/builder-webpack4@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/builder-webpack4@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/core-events": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/preview-web": 6.5.12 - "@storybook/router": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/preview-web": 6.5.13 + "@storybook/router": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 - "@storybook/theming": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/store": 6.5.13 + "@storybook/theming": 6.5.13 + "@storybook/ui": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 "@types/webpack": ^4.41.26 autoprefixer: ^9.8.6 @@ -2120,30 +2170,30 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 3cb72ade60fc0767480c424cd5da6659027c35759852198936530c555d7fa1ae18326b3d696def06a54a55f7f26a6eb0246e675ea566c5bbfc63db54e0ad8880 + checksum: a95fea3951479d7724155a2ddbf2b04a8bfc0e7fddbf8415caed508b94ad71f7dc8d5d25464061ef26f3b4670f49f6ed40198b48b3f646d7af17d8daee5e89cb languageName: node linkType: hard "@storybook/builder-webpack5@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/builder-webpack5@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/builder-webpack5@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/core-events": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/preview-web": 6.5.12 - "@storybook/router": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/preview-web": 6.5.13 + "@storybook/router": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 - "@storybook/theming": 6.5.12 + "@storybook/store": 6.5.13 + "@storybook/theming": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 babel-loader: ^8.0.0 babel-plugin-named-exports-order: ^0.0.2 @@ -2172,35 +2222,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 60387a186defc3b40ceae8c41376da5de65d52cc652a203bce8ef8733e54bec552143440ce5fb466916a87b0ef67b98932a96985f3ee371a3f74a9049277092f + checksum: f980ab5c832bac9584f80eb0229934bb58ceb4cf24f756043acea4cb397f52f7af49c1fd53ac897aefb5ffc9c808bcb8a0c4867afd2c2a21b7ba2f0a56ae0089 languageName: node linkType: hard -"@storybook/channel-postmessage@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/channel-postmessage@npm:6.5.12" +"@storybook/channel-postmessage@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/channel-postmessage@npm:6.5.13" dependencies: - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 core-js: ^3.8.2 global: ^4.4.0 qs: ^6.10.0 telejson: ^6.0.8 - checksum: c225f848f4774e8159b9fd8bd904520ab2755f46ac6ef5a8ed7193b5cd79856e0bb797d10adfa0a1db9b9df075c41b4487d195cc451fb65b04737db70e5db6db + checksum: 8d6ccfff2aeafaae30b5fc1af856be8d06b3703b96841ecc0d70959e51542514901763e1a291e1d0278afe31b23cc5c0a5b351994f321e9bd03490be5b51e2d0 languageName: node linkType: hard -"@storybook/channel-websocket@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/channel-websocket@npm:6.5.12" +"@storybook/channel-websocket@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/channel-websocket@npm:6.5.13" dependencies: - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 core-js: ^3.8.2 global: ^4.4.0 telejson: ^6.0.8 - checksum: 03d4ed3f2b67daceea06325c8705b95013b65d014dfda769a923b2c1c890d1eca1fe8eea09e3386cc572c738ce43bc8951225ebd92de4205ad80438bb9e36ebe + checksum: 16e3b1a51a1af093f6c78ab7ca9c4c69ed05b45fd9bbdefb3050809e92064ccbf7a46f6800d21df2555e5d110d64735dd8d35155e54c3118fa7b4efe6b3b0457 languageName: node linkType: hard @@ -2226,17 +2276,28 @@ __metadata: languageName: node linkType: hard -"@storybook/client-api@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/client-api@npm:6.5.12" +"@storybook/channels@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/channels@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + core-js: ^3.8.2 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + checksum: 5b8881a2799a4c5ceafea40bc2c8bad1a31649036341eec8da5a77acf79a9d610afeaa5b4ed5d06022ed3c74cb9562dcfc5046d62fd8d27cd65bcba09aa5e903 + languageName: node + linkType: hard + +"@storybook/client-api@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/client-api@npm:6.5.13" + dependencies: + "@storybook/addons": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 "@types/qs": ^6.9.5 "@types/webpack-env": ^1.16.0 core-js: ^3.8.2 @@ -2253,7 +2314,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 6a103cdf1c0499e238e6a652f192b3287b7bce2a96c194c48854d1e6d8e470568aa36307aa49650e8322965295b10d601c4a087e2cb7e3515bb9ba8281aeda35 + checksum: b0af25786b9144a55ebaa7754dd1b3701f5f8796770eaf59e7bc6d21ada12911fcbe4bf0da037d01bdda2c46138f265a948befe1f3de356fdc0ae3af80973388 languageName: node linkType: hard @@ -2277,7 +2338,36 @@ __metadata: languageName: node linkType: hard -"@storybook/components@npm:6.5.12, @storybook/components@npm:^6.0.0": +"@storybook/client-logger@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/client-logger@npm:6.5.13" + dependencies: + core-js: ^3.8.2 + global: ^4.4.0 + checksum: 0252d9364a0b2a8faae588fdb29aaf458f660904c330ec7af790f63a668710926ece8f087f58f9b1bebb052e2fe517b8b74867e7500567499cc710ab71ccbbab + languageName: node + linkType: hard + +"@storybook/components@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/components@npm:6.5.13" + dependencies: + "@storybook/client-logger": 6.5.13 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/theming": 6.5.13 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5d01c0f445f6574ccadcfa79afd99c078bd1f81d65e59186361100dc57bd73ccbb877e5a8bbc49dd6551bce1b32fbe6f135c2bea15c0126a83faf222cfaed878 + languageName: node + linkType: hard + +"@storybook/components@npm:^6.0.0": version: 6.5.12 resolution: "@storybook/components@npm:6.5.12" dependencies: @@ -2296,20 +2386,20 @@ __metadata: languageName: node linkType: hard -"@storybook/core-client@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core-client@npm:6.5.12" +"@storybook/core-client@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-client@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channel-websocket": 6.5.12 - "@storybook/client-api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channel-websocket": 6.5.13 + "@storybook/client-api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/preview-web": 6.5.12 - "@storybook/store": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/preview-web": 6.5.13 + "@storybook/store": 6.5.13 + "@storybook/ui": 6.5.13 airbnb-js-shims: ^2.2.1 ansi-to-html: ^0.6.11 core-js: ^3.8.2 @@ -2327,13 +2417,13 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 4fb567964a6c15526ee6ee882e20d72c650ad0c74504ee2a058c13856efd25a0c9c7f666d36c6b4e70a75ece73c1ed812f554bbbf87771cd6171cd09ebf31410 + checksum: c4350b1b579f0781a239fdede79f1d0975e297ecb61ba4096834d62bd553420615231dc9146c446d0178088e83863fd9dc720fbb4485b5779fac7d99ce3eeb9e languageName: node linkType: hard -"@storybook/core-common@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core-common@npm:6.5.12" +"@storybook/core-common@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-common@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/plugin-proposal-class-properties": ^7.12.1 @@ -2357,7 +2447,7 @@ __metadata: "@babel/preset-react": ^7.12.10 "@babel/preset-typescript": ^7.12.7 "@babel/register": ^7.12.1 - "@storybook/node-logger": 6.5.12 + "@storybook/node-logger": 6.5.13 "@storybook/semver": ^7.3.2 "@types/node": ^14.0.10 || ^16.0.0 "@types/pretty-hrtime": ^1.0.0 @@ -2391,7 +2481,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: d12b276718d3bb527084135882abc35fcdc4690896579b9f5e0417236a0d02f2791424ac62a891a0353af635be10c22db7dfe04d0db644ab0757ae3981f0ff1a + checksum: 369fbe41e9ac657410a8e7fb4668be0e77c50b84c29b352397cc26b72d79397a7e84dcbf7a94f2d02d819d395a66e30a3915de40e85936d7b7dc50bb426aeabb languageName: node linkType: hard @@ -2413,22 +2503,31 @@ __metadata: languageName: node linkType: hard -"@storybook/core-server@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core-server@npm:6.5.12" +"@storybook/core-events@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-events@npm:6.5.13" + dependencies: + core-js: ^3.8.2 + checksum: 2afeaf5fd658a4e9eedb9ad458ba0bcc73ad4ae5ba0e9971434818258db01d9b48b604d4db396ebfc1e1571dace3f6659e9ed61ac35428a792a4e24bbc08b29c + languageName: node + linkType: hard + +"@storybook/core-server@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-server@npm:6.5.13" dependencies: "@discoveryjs/json-ext": ^0.5.3 - "@storybook/builder-webpack4": 6.5.12 - "@storybook/core-client": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/builder-webpack4": 6.5.13 + "@storybook/core-client": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/csf-tools": 6.5.12 - "@storybook/manager-webpack4": 6.5.12 - "@storybook/node-logger": 6.5.12 + "@storybook/csf-tools": 6.5.13 + "@storybook/manager-webpack4": 6.5.13 + "@storybook/node-logger": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 - "@storybook/telemetry": 6.5.12 + "@storybook/store": 6.5.13 + "@storybook/telemetry": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 "@types/node-fetch": ^2.5.7 "@types/pretty-hrtime": ^1.0.0 @@ -2472,16 +2571,16 @@ __metadata: optional: true typescript: optional: true - checksum: 1e7e8de948012eb126f30261d23a552e18e671ad7796c46051e17be545c37a70c72437cbb24249d2e88b3525ed6a4b7205ddeeb167aca6b9478df85b128a57d3 + checksum: 142b13ef4fef21a68c8255f35f42ca5c3b9f636b51986f61dc6ae95485788d0f581552604b8a4a796e7cd2ad9548b87317ac9647ea44be58f64b91c440bb71ea languageName: node linkType: hard -"@storybook/core@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core@npm:6.5.12" +"@storybook/core@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core@npm:6.5.13" dependencies: - "@storybook/core-client": 6.5.12 - "@storybook/core-server": 6.5.12 + "@storybook/core-client": 6.5.13 + "@storybook/core-server": 6.5.13 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -2493,13 +2592,13 @@ __metadata: optional: true typescript: optional: true - checksum: 82606be8f89ad34a662d366e64d85af5a61270e2cff4dab8c3c3b0ec17ae3e34e560b54a1e2dd3fdd52eb072f8101b409e762f8581e65abcf07097e768baaaf0 + checksum: e0dbe5d8d52f2a12ab63db965d5d15ee671029a03b7204756954e2bc3253b560c8e430aaab5559ccbddcfd3e97d2bc1c0c58ed370aa593912724402aa86996b8 languageName: node linkType: hard -"@storybook/csf-tools@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/csf-tools@npm:6.5.12" +"@storybook/csf-tools@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/csf-tools@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/generator": ^7.12.11 @@ -2520,7 +2619,7 @@ __metadata: peerDependenciesMeta: "@storybook/mdx2-csf": optional: true - checksum: 21da554c88f22ee583cd1956cf440506212d9e8727c7f0a493a92804e58b83d3fcfa18d3081a9fd1b5e8da07a1cfbee15bfa638a13a8fad585eac04fe26f5112 + checksum: 2b8a5bed04ea89084334742e1095c4565b0b7367b5126e3a9b6648224b59c2136a9d57cbb9067264fc3951e9db58df40b23b975170180d171cce35dfabf2a090 languageName: node linkType: hard @@ -2533,18 +2632,18 @@ __metadata: languageName: node linkType: hard -"@storybook/docs-tools@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/docs-tools@npm:6.5.12" +"@storybook/docs-tools@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/docs-tools@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 core-js: ^3.8.2 doctrine: ^3.0.0 lodash: ^4.17.21 regenerator-runtime: ^0.13.7 - checksum: 9433b0bc74e739f37d4be857e366d74e56566cdf27f72f462eb09a6e84713aadc8c768e075fbe7a062be104bb1536c14b8dea1e890917ca94b1580e71dbe60be + checksum: d3ad4674922025aaf6e4e2b7c2ac6f4eaec8f5692dc9a792a15d2d5e38dcd2e1c138daffb31a3da04da1e04c645b3cd8d921890f9ba318bfee7b6a12f263b48a languageName: node linkType: hard @@ -2561,19 +2660,19 @@ __metadata: languageName: node linkType: hard -"@storybook/manager-webpack4@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/manager-webpack4@npm:6.5.12" +"@storybook/manager-webpack4@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/manager-webpack4@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/plugin-transform-template-literals": ^7.12.1 "@babel/preset-react": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/core-client": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/theming": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/core-client": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/theming": 6.5.13 + "@storybook/ui": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 "@types/webpack": ^4.41.26 babel-loader: ^8.0.0 @@ -2606,23 +2705,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 89c6ab508a930def13403275201e1e7efd667a25f0e04a6a4fe83e83d8a77065309f11c6ba183bb412072b80580e232365789d2317ae9c1e92df92c4061752be + checksum: 6645f30b6199d0badb2097aca16478e4d8bc88210b55e60c4da86962e8b0a3441128f1e696cddf8a535591920617a7abf5f1f9771dabc486479e76b8d4b20fbd languageName: node linkType: hard "@storybook/manager-webpack5@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/manager-webpack5@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/manager-webpack5@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/plugin-transform-template-literals": ^7.12.1 "@babel/preset-react": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/core-client": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/theming": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/core-client": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/theming": 6.5.13 + "@storybook/ui": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 babel-loader: ^8.0.0 case-sensitive-paths-webpack-plugin: ^2.3.0 @@ -2652,7 +2751,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 701768cee510e9de024259c88ebd85ebc212d37d9f913fc7a1e13ab8751d7fb579bb99e494a439d6d5388c02e204eea8c8d91d1490b138105b414df8a384b7ae + checksum: 95e720d00e8869f8ec5e8a36f50e5da6aaecdc9a1ea306ae3d7b50f2f07b60a9eb5cb3787e88262c664c958b1f56b7f34e9bf027eb627a96d127b5771ff6a137 languageName: node linkType: hard @@ -2675,29 +2774,29 @@ __metadata: languageName: node linkType: hard -"@storybook/node-logger@npm:6.5.12, @storybook/node-logger@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/node-logger@npm:6.5.12" +"@storybook/node-logger@npm:6.5.13, @storybook/node-logger@npm:^6.5.9": + version: 6.5.13 + resolution: "@storybook/node-logger@npm:6.5.13" dependencies: "@types/npmlog": ^4.1.2 chalk: ^4.1.0 core-js: ^3.8.2 npmlog: ^5.0.1 pretty-hrtime: ^1.0.3 - checksum: 7589477486a25e67d9119e9c363e8bde23e52601043a506ac0d28f4d353f3a228face79b40a2eb0cc0c7c8b05ed084336fef5dcc3213ed4484527c6631eafeb0 + checksum: bcd1d98822687580e39f27003e16c73e3c775cdfe6e9f8fd8fbe9f4626a82f3f63fe281f9c894f3917faa52202ccb8217916978032d27ba6dbfa9720064e7739 languageName: node linkType: hard -"@storybook/preview-web@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/preview-web@npm:6.5.12" +"@storybook/preview-web@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/preview-web@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 ansi-to-html: ^0.6.11 core-js: ^3.8.2 global: ^4.4.0 @@ -2711,7 +2810,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: e11671fd136042a0ac19be6749f40bcdf858adb6fbc36998eb2c1737fc622c15df233eb04ff3cf555a61a31d32e5218cf9ff3ff9b941b457a8159a8ca54ab2e8 + checksum: d66d29667a936ee80d15de07bdeec0c6cb2a476fdaa59f262297f5c7dc774acb57e4779c2dd77e61f5a6d9974ac3359babe587a2cd9baf6aa7673ec949b3234d languageName: node linkType: hard @@ -2734,22 +2833,22 @@ __metadata: linkType: hard "@storybook/react@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/react@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/react@npm:6.5.13" dependencies: "@babel/preset-flow": ^7.12.1 "@babel/preset-react": ^7.12.10 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.3 - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core": 6.5.12 - "@storybook/core-common": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core": 6.5.13 + "@storybook/core-common": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/docs-tools": 6.5.12 - "@storybook/node-logger": 6.5.12 + "@storybook/docs-tools": 6.5.13 + "@storybook/node-logger": 6.5.13 "@storybook/react-docgen-typescript-plugin": 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 "@types/estree": ^0.0.51 "@types/node": ^14.14.20 || ^16.0.0 "@types/webpack-env": ^1.16.0 @@ -2794,7 +2893,7 @@ __metadata: build-storybook: bin/build.js start-storybook: bin/index.js storybook-server: bin/index.js - checksum: 7b762f5b0db2b94d3e492ed45a7566009dc9ff008c9b29db278d6404e1c7c9f419a0114090bf23fc5b3e193a83d74e21f0fe6ba04105eb30f59be1d4c87d652d + checksum: 5a21e4e49a0aba7376dbaef5408e03537cf937d3025e735b45848bce7c9f476a025ca9af4260aa32982a3da668e14a217f66a0ec465ed820369ff2fab49b2ab3 languageName: node linkType: hard @@ -2830,6 +2929,22 @@ __metadata: languageName: node linkType: hard +"@storybook/router@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/router@npm:6.5.13" + dependencies: + "@storybook/client-logger": 6.5.13 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: ca144b2f6e3a46d5ac9d449b068d0905e9c72939c8574f095d8d7f7307b172a0c5c13f56ff08d5d5bff540292d9ed13eeecc3a4e600e282ea31e70ed763b735a + languageName: node + linkType: hard + "@storybook/semver@npm:^7.3.2": version: 7.3.2 resolution: "@storybook/semver@npm:7.3.2" @@ -2842,12 +2957,12 @@ __metadata: languageName: node linkType: hard -"@storybook/source-loader@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/source-loader@npm:6.5.12" +"@storybook/source-loader@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/source-loader@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 core-js: ^3.8.2 estraverse: ^5.2.0 @@ -2859,17 +2974,17 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: ad6b0774877678d8495e7afaeb820adbc2d1b091df5678da39123eca875cc30fe41f013c2db0a4c5f7614529dc46be00cb165484e65a960d3fe7657b04cf418f + checksum: 93da14a367a954f664d233f64bd9e7d983475e489b8fbe5c90b723ff793279ab899d86f967eb6882f7997296f4286f6f5c82c17ca83e9294c91f8e4aa0ec0a1d languageName: node linkType: hard -"@storybook/store@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/store@npm:6.5.12" +"@storybook/store@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/store@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 core-js: ^3.8.2 fast-deep-equal: ^3.1.3 @@ -2885,16 +3000,16 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 7fab43471c692cda33e9cbb7abf8a932bf922bd01bf26c56a3dd909f57ff0f26a80c6456a41c5be01191bb818536f16f5c98617d263b49a60e432b0acca07949 + checksum: 69f55927bd3569ec9d87f4351879fd07654d51524a0f9da05c64c7f7f3b50e33024f1554fa59668997e47189e9838b85b691de422ab539bd71126b56922d9381 languageName: node linkType: hard -"@storybook/telemetry@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/telemetry@npm:6.5.12" +"@storybook/telemetry@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/telemetry@npm:6.5.13" dependencies: - "@storybook/client-logger": 6.5.12 - "@storybook/core-common": 6.5.12 + "@storybook/client-logger": 6.5.13 + "@storybook/core-common": 6.5.13 chalk: ^4.1.0 core-js: ^3.8.2 detect-package-manager: ^2.0.1 @@ -2905,7 +3020,7 @@ __metadata: nanoid: ^3.3.1 read-pkg-up: ^7.0.1 regenerator-runtime: ^0.13.7 - checksum: fe465e31e20bc271b1b066a1c1fc4ea8b7cdca1858bc875e19d7ad4e7988c0cff084ce822dc0657ecdefafa02a416dd239753c9c197f6758b39d9260964d631c + checksum: 94ad6fb58b09c8073600ad95b2a48f476524ea7bc6155aee8e9682966a99ac875a9923ee6512252238e8c56eeef725a712e94ba87e1060d7dca9ab196a9051a6 languageName: node linkType: hard @@ -2952,19 +3067,34 @@ __metadata: languageName: node linkType: hard -"@storybook/ui@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/ui@npm:6.5.12" +"@storybook/theming@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/theming@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-events": 6.5.12 - "@storybook/router": 6.5.12 + "@storybook/client-logger": 6.5.13 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: f7a59c7d81b87f3fbf65c5eb72f5db5a5c1707236c92350d46bc7e1dcf848d57522ca5dcdd4fe19336d4611bd20727e0d900c4b591d2e8e1dd8a754cb9c56aa3 + languageName: node + linkType: hard + +"@storybook/ui@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/ui@npm:6.5.13" + dependencies: + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/router": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/theming": 6.5.12 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 memoizerific: ^1.11.3 qs: ^6.10.0 @@ -2973,7 +3103,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 026ddc42d00773ad711824a5374b040b589e8a87b8500db4d6a8cb67263eba0789d6e6afe7fd8e0fcb798380eba210b4225af197f064d3e88ec0bda97a83b28b + checksum: d2866987f51d945246776d42628bc2b79e701f1e59fd511bb1e590c83c4b9d9adee5c7e1a11ecb2ef12b9192613e2d576694bbc9aeb1df5545567f2aa44c0145 languageName: node linkType: hard From 431ecaf1d527bb1c113aa3a968a42f346c263f3a Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 25 Oct 2022 14:50:18 +0200 Subject: [PATCH 203/221] this seems to solve the issue. actually get the nested element instead of the parent of that element Signed-off-by: Morgan --- .../TechDocsReaderPage/TechDocsReaderPage.tsx | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 1c276b87a1..4cf2955f25 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ReactNode, Children, ReactElement } from 'react'; +import React, { ReactNode, Children, ReactElement, ReactChild } from 'react'; import { useOutlet } from 'react-router-dom'; import { Page } from '@backstage/core-components'; @@ -35,6 +35,14 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; +type Extension = ReactChild & { + type: { + __backstage_data: { + map: Map; + }; + }; +}; + /** * Props for {@link TechDocsReaderLayout} * @public @@ -88,19 +96,27 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { if (!children) { const childrenList = outlet ? Children.toArray(outlet.props.children) : []; - const page = childrenList.find(child => { + let page: React.ReactNode; + childrenList.forEach(child => { + // console.log({child: JSON.stringify(child)}) + // const { type } = child as Extension; + // console.log(!type?.__backstage_data?.map?.get(TECHDOCS_ADDONS_WRAPPER_KEY)); + if (getComponentData(child, TECHDOCS_ADDONS_WRAPPER_KEY)) { - return false; + return; } // react-router 6 stable wraps children in a routing context provider, so check one level deeper const nestedChildren = (child as ReactElement)?.props?.children; if (nestedChildren) { - return !Children.toArray(nestedChildren).some(nested => - getComponentData(nested, TECHDOCS_ADDONS_WRAPPER_KEY), - ); + Children.toArray(nestedChildren).forEach(nested => { + if (!getComponentData(nested, TECHDOCS_ADDONS_WRAPPER_KEY)) { + page = nested; + } + }); + return; } - return true; + page = child; }); return ( From 1a22b5f1b13aa4547b64bfe2f5aebebd892138eb Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 25 Oct 2022 16:19:21 +0200 Subject: [PATCH 204/221] rewrite loop code Signed-off-by: Morgan --- .../TechDocsReaderPage/TechDocsReaderPage.tsx | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 4cf2955f25..4a6bfef89f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -85,6 +85,7 @@ export type TechDocsReaderPageProps = { /** * An addon-aware implementation of the TechDocsReaderPage. + * * @public */ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { @@ -96,28 +97,12 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { if (!children) { const childrenList = outlet ? Children.toArray(outlet.props.children) : []; - let page: React.ReactNode; - childrenList.forEach(child => { - // console.log({child: JSON.stringify(child)}) - // const { type } = child as Extension; - // console.log(!type?.__backstage_data?.map?.get(TECHDOCS_ADDONS_WRAPPER_KEY)); - - if (getComponentData(child, TECHDOCS_ADDONS_WRAPPER_KEY)) { - return; - } - - // react-router 6 stable wraps children in a routing context provider, so check one level deeper - const nestedChildren = (child as ReactElement)?.props?.children; - if (nestedChildren) { - Children.toArray(nestedChildren).forEach(nested => { - if (!getComponentData(nested, TECHDOCS_ADDONS_WRAPPER_KEY)) { - page = nested; - } - }); - return; - } - page = child; - }); + const grandChildren = childrenList.flatMap( + child => (child as ReactElement)?.props?.children ?? [], + ); + const page: React.ReactNode = grandChildren.find( + grandChild => !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY), + ); return ( From 5878dcd12038835a1bfb1863b09ffee09ba0a3e4 Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 25 Oct 2022 16:39:57 +0200 Subject: [PATCH 205/221] remove unused import Signed-off-by: Morgan --- .../TechDocsReaderPage/TechDocsReaderPage.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 4a6bfef89f..e307564f9c 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ReactNode, Children, ReactElement, ReactChild } from 'react'; +import React, { ReactNode, Children, ReactElement } from 'react'; import { useOutlet } from 'react-router-dom'; import { Page } from '@backstage/core-components'; @@ -35,14 +35,6 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; -type Extension = ReactChild & { - type: { - __backstage_data: { - map: Map; - }; - }; -}; - /** * Props for {@link TechDocsReaderLayout} * @public From 9e4d8e619898e64b9ddfaf2460d5fe2bb02f6edc Mon Sep 17 00:00:00 2001 From: Morgan Date: Tue, 25 Oct 2022 16:40:33 +0200 Subject: [PATCH 206/221] add changeset Signed-off-by: Morgan --- .changeset/beige-gorillas-sip.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/beige-gorillas-sip.md diff --git a/.changeset/beige-gorillas-sip.md b/.changeset/beige-gorillas-sip.md new file mode 100644 index 0000000000..b0dcaadfc6 --- /dev/null +++ b/.changeset/beige-gorillas-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fix logic bug that broke techdocs-cli-embedded-app From cc8701d2fb7e3e996fa065576ffcc5af29c43b29 Mon Sep 17 00:00:00 2001 From: Otto Sichert Date: Tue, 25 Oct 2022 17:23:06 +0200 Subject: [PATCH 207/221] Add documentation for configuring TechDocsReaderPage Signed-off-by: Otto Sichert --- .../TechDocsReaderPage/TechDocsReaderPage.tsx | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index e307564f9c..0e9b244d74 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -35,6 +35,82 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; +/* An explanation for the multiple ways of customizing the TechDocs reader page + +Please refer to this page on the microsite for the latest recommended approach: +https://backstage.io/docs/features/techdocs/how-to-guides#how-to-customize-the-techdocs-reader-page + +The component is responsible for rendering the and +its contained version of a , which in turn renders the . + +Historically, there have been different approaches on how this can be customized, and how the + inside could be exchanged for a custom implementation (which was not +possible before). Also, the current implementation supports every scenario to avoid breaking default +configurations of TechDocs. + +In particular, there are 4 different TechDocs page configurations: + +CONFIGURATION 1: only, no children + +} > + +This is the simplest way to use TechDocs. Only a full page is passed, assuming that it comes with +its content inside. Since we allowed customizing it, we started providing as +a default implementation (which contains ). + +CONFIGURATION 2 (not advised): with element children + + + {techdocsPage} + + } +/> + +Previously, there were two ways of passing children to : either as elements (as +shown above), or as a render function (described below in CONFIGURATION 3). The "techdocsPage" is +located in packages/app/src/components/techdocs and is the default implementation of the content +inside. + +CONFIGURATION 3 (not advised): with render function as child + + + {({ metadata, entityMetadata, onReady }) => ( + techdocsPage + )} + + } +/> + +Similar to CONFIGURATION 2, the direct children will be passed to the but in +this case interpreted as render prop. + +CONFIGURATION 4: and provided content in + +} +> + {techDocsPage} + + + + + + + +This is the current state in packages/app/src/App.tsx and moved the location of children from inside +the element prop in the to the children of the . Then, in they +are retrieved using the useOutlet hook from React Router. + +NOTE: Render functions are no longer supported in this approach. +*/ + /** * Props for {@link TechDocsReaderLayout} * @public @@ -96,6 +172,7 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { grandChild => !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY), ); + // As explained above, "page" is configuration 4 and is 1 return ( {(page as JSX.Element) || } @@ -103,6 +180,7 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { ); } + // As explained above, a render function is configuration 3 and React element is 2 return ( {({ metadata, entityMetadata, onReady }) => ( From bc4c4b32e74955853eefb4409656b7181a44286f Mon Sep 17 00:00:00 2001 From: Stephen Werdick Date: Tue, 25 Oct 2022 16:52:26 -0700 Subject: [PATCH 208/221] adding Alaska Airlines to adopters Signed-off-by: Stephen Werdick --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index f667de120c..81ae6e255f 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -215,3 +215,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Ferrovial](https://ferrovial.com) | [Jose Luis Rosado](mailto:jlrosado@ferrovial.com) | Backstage is helping us to improve and acelerate dev experience helping teams to quickly find technical documentation, infrastructure templates, pipelines, software components and quickstarters that have been developed by our squads in a inner source friendly environment. | | [Inter&Co](https://bancointer.com.br) | [Arnaud Lanna](https://github.com/arnaudlanna), [Adriano Silva](https://github.com/adrianovss), [Bruno Grossi](https://github.com/begrossi) | We're using Backstage as our internal Developer Portal to catalog and collect repositories and microservices pieces of information like ownership, deployment time, and documentation. | | [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | +| [Alaska Airlines](https://alaskaair.com) | [@swerdick](https://github.com/swerdick) | Backstage is the developer portal for our 'software delivery platform'. Consolidating developer tools to one place, and providing automation to make it easy for developers to create and deploy applications to Kubernetes | From 787574422e32ccc1ff57e16a900b027ebb0f2500 Mon Sep 17 00:00:00 2001 From: Stephen Werdick Date: Tue, 25 Oct 2022 16:54:44 -0700 Subject: [PATCH 209/221] fixing formatting on the above line Signed-off-by: Stephen Werdick --- ADOPTERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 81ae6e255f..595aa16c40 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -214,5 +214,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Vipps](https://vipps.no) | [Martin Ehrnst](https://github.com/ehrnst) | Vipps use backstage for our service catalog, documentation, and developer portal. Using templates we are able to simplify the developer experience when deploying new services to our platform. | | [Ferrovial](https://ferrovial.com) | [Jose Luis Rosado](mailto:jlrosado@ferrovial.com) | Backstage is helping us to improve and acelerate dev experience helping teams to quickly find technical documentation, infrastructure templates, pipelines, software components and quickstarters that have been developed by our squads in a inner source friendly environment. | | [Inter&Co](https://bancointer.com.br) | [Arnaud Lanna](https://github.com/arnaudlanna), [Adriano Silva](https://github.com/adrianovss), [Bruno Grossi](https://github.com/begrossi) | We're using Backstage as our internal Developer Portal to catalog and collect repositories and microservices pieces of information like ownership, deployment time, and documentation. | -| [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | +| [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | | [Alaska Airlines](https://alaskaair.com) | [@swerdick](https://github.com/swerdick) | Backstage is the developer portal for our 'software delivery platform'. Consolidating developer tools to one place, and providing automation to make it easy for developers to create and deploy applications to Kubernetes | From c96eeb526c3fce25b1f4f85d871a70dce63bea6c Mon Sep 17 00:00:00 2001 From: Suzanne Daniels Date: Tue, 25 Oct 2022 19:56:32 -0400 Subject: [PATCH 210/221] updating streaming link Signed-off-by: Suzanne Daniels --- microsite/pages/en/live.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsite/pages/en/live.js b/microsite/pages/en/live.js index 43b5a0eb67..2accd6d11c 100644 --- a/microsite/pages/en/live.js +++ b/microsite/pages/en/live.js @@ -34,7 +34,7 @@ const Background = props => { From 24c487d6ef9df58770b8917442ce141e10e32e77 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 00:15:55 +0000 Subject: [PATCH 211/221] Update dependency @google-cloud/firestore to v6.4.1 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6c4f093c83..b4a778e193 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8661,14 +8661,14 @@ __metadata: linkType: hard "@google-cloud/firestore@npm:^6.0.0": - version: 6.4.0 - resolution: "@google-cloud/firestore@npm:6.4.0" + version: 6.4.1 + resolution: "@google-cloud/firestore@npm:6.4.1" dependencies: fast-deep-equal: ^3.1.1 functional-red-black-tree: ^1.0.1 google-gax: ^3.5.1 protobufjs: ^7.0.0 - checksum: 7503e56f85c20aad4fa01b5de0b97e5108fe44c87db3423cb53bcdb083e053c6458c7a690820dc7db9ead384ce28da66142c1781d8cc406d4e7056374b29d23d + checksum: 7bd79d7444cefd8ffe8f768c28dbd1f0b32628ff5b611b4bdaa9b415428436f5368a8e258a002948d084381762b64501a574828c51c5cea7c054cc604f93b6d6 languageName: node linkType: hard From 340e91786c2e43d8f07ed27a40c6b006e7ce1099 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 01:06:58 +0000 Subject: [PATCH 212/221] Update dependency recursive-readdir to v2.2.3 Signed-off-by: Renovate Bot --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index b4a778e193..db7d560aae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29845,7 +29845,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -34390,11 +34390,11 @@ __metadata: linkType: hard "recursive-readdir@npm:^2.2.2": - version: 2.2.2 - resolution: "recursive-readdir@npm:2.2.2" + version: 2.2.3 + resolution: "recursive-readdir@npm:2.2.3" dependencies: - minimatch: 3.0.4 - checksum: a6b22994d76458443d4a27f5fd7147ac63ad31bba972666a291d511d4d819ee40ff71ba7524c14f6a565b8cfaf7f48b318f971804b913cf538d58f04e25d1fee + minimatch: ^3.0.5 + checksum: 88ec96e276237290607edc0872b4f9842837b95cfde0cdbb1e00ba9623dfdf3514d44cdd14496ab60a0c2dd180a6ef8a3f1c34599e6cf2273afac9b72a6fb2b5 languageName: node linkType: hard From 9b4b9bd247f53e599f8a7b09f5b90a226b51b252 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 02:44:07 +0000 Subject: [PATCH 213/221] Update CodeMirror Signed-off-by: Renovate Bot --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index db7d560aae..dacfd03f2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8293,8 +8293,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.2.1 - resolution: "@codemirror/language@npm:6.2.1" + version: 6.3.0 + resolution: "@codemirror/language@npm:6.3.0" dependencies: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 @@ -8302,16 +8302,16 @@ __metadata: "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 style-mod: ^4.0.0 - checksum: e483eacf3346ef3d8db5c4c730e059790e6bf717b8148d79fa000082841f889f3a7867cc5df36f31503fcd7b2ca935b60c3e710c5637985f7ef0613728678fa3 + checksum: 996d6aad1a4cfb455f3459951d5ecfb67ff5240cc29397b3b801e569fcda24f87364aa7e42b63e85ecf62af80eaecd01258303dbf5b5f0b1eba31daa9a5bd258 languageName: node linkType: hard "@codemirror/legacy-modes@npm:^6.1.0": - version: 6.1.0 - resolution: "@codemirror/legacy-modes@npm:6.1.0" + version: 6.2.0 + resolution: "@codemirror/legacy-modes@npm:6.2.0" dependencies: "@codemirror/language": ^6.0.0 - checksum: bc0e3b771360de435735e0203c47feef8336749738da364c744da50e838d1ad5b36402da00131323238b579f657868210659029d44a1935baf3de8cb964903a6 + checksum: c4449ad4e9b80fad982956ace705af5e21cd8c8bb248930bcb394ec019babf1a7b23e1e2894428b67d28a0c5bee99592320673690d19039392ddbc77075ef132 languageName: node linkType: hard From 84515c9152a28a1c1fbe7b5856328421a653d616 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 03:33:12 +0000 Subject: [PATCH 214/221] Update dependency @graphql-codegen/typescript to v2.8.0 Signed-off-by: Renovate Bot --- yarn.lock | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index dacfd03f2f..8b0a14708b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8880,7 +8880,22 @@ __metadata: languageName: node linkType: hard -"@graphql-codegen/typescript@npm:^2.4.2, @graphql-codegen/typescript@npm:^2.7.5": +"@graphql-codegen/typescript@npm:^2.4.2": + version: 2.8.0 + resolution: "@graphql-codegen/typescript@npm:2.8.0" + dependencies: + "@graphql-codegen/plugin-helpers": ^2.6.2 + "@graphql-codegen/schema-ast": ^2.5.1 + "@graphql-codegen/visitor-plugin-common": 2.13.0 + auto-bind: ~4.0.0 + tslib: ~2.4.0 + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 45550613384c930d046f1ca8bfd73c25e1219fc45315b1885017e08577a517547d5af86908162f61eee0670689df5c926b5e394b318d5c3d94ee5eef5ac1bc52 + languageName: node + linkType: hard + +"@graphql-codegen/typescript@npm:^2.7.5": version: 2.7.5 resolution: "@graphql-codegen/typescript@npm:2.7.5" dependencies: From 7a4f3a62aeba3de6620d5fb3f6f1694525324ce9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 04:40:00 +0000 Subject: [PATCH 215/221] Update dependency @roadiehq/backstage-plugin-buildkite to v2.1.0 Signed-off-by: Renovate Bot --- yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8b0a14708b..b393ee60d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12269,13 +12269,13 @@ __metadata: linkType: hard "@roadiehq/backstage-plugin-buildkite@npm:^2.0.8": - version: 2.0.9 - resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.0.9" + version: 2.1.0 + resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.1.0" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.12.1 "@material-ui/icons": ^4.11.2 @@ -12288,7 +12288,7 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 45a7f04969be1896919709311e38218f642d754cf0ae32c608b87d8965147e048d1537854278d97b5bcd777f84861bd65386ffcb5a8831d004cbdc2f5f16dbed + checksum: 0322733ccc62dfd5ec23aabea9d3d82b9a8c49ee8d9668f1d95a67bbb1a6b5d863b0b4148bbeb479bfc59f2ee42d566b298870fdaecea6d8ebee864f0a257afc languageName: node linkType: hard From 408a0f28273671593d0ecfd998542fa759d1ded3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 05:36:29 +0000 Subject: [PATCH 216/221] Update dependency @roadiehq/backstage-plugin-github-insights to v2.1.0 Signed-off-by: Renovate Bot --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index b393ee60d1..6e3533b533 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3848,7 +3848,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@npm:^1.1.4": +"@backstage/integration-react@npm:^1.1.5": version: 1.1.5 resolution: "@backstage/integration-react@npm:1.1.5" dependencies: @@ -12293,14 +12293,14 @@ __metadata: linkType: hard "@roadiehq/backstage-plugin-github-insights@npm:^2.0.5": - version: 2.0.6 - resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.0.6" + version: 2.1.0 + resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.1.0" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/integration-react": ^1.1.4 - "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/integration-react": ^1.1.5 + "@backstage/plugin-catalog-react": ^1.2.0 "@backstage/theme": ^0.2.16 "@date-io/core": 2.10.7 "@material-ui/core": ^4.11.0 @@ -12316,7 +12316,7 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: ff9701f4e904c44c1031616f59837f9a3bca1448f846791e6b6c2b1eb35ae78fbfc2e7161d233a620d227fc076ba71663af6380eaede9e8cda8930fa008d92ec + checksum: 33ba1f1cf2370bd0af8cd3b9ed117f432de13be7b7a17dc23d427195826a66f696d0dcf916e6105c02b595d6c1eb151e4d280c8f248f75e8cfff05e8480c7cd8 languageName: node linkType: hard From 502ebe7d245886b355c55adec3837b1f9c60ea6c Mon Sep 17 00:00:00 2001 From: Matthew Boyle Date: Tue, 25 Oct 2022 19:56:34 +0100 Subject: [PATCH 217/221] update the configuration steps of the docs Signed-off-by: Matthew Boyle --- docs/assets/getting-started/b-scaffold-1.png | Bin 43464 -> 27164 bytes docs/assets/getting-started/b-scaffold-2.png | Bin 48920 -> 45990 bytes docs/assets/getting-started/b-scaffold-3.png | Bin 48814 -> 0 bytes docs/getting-started/configuration.md | 22 +++++++++---------- 4 files changed, 10 insertions(+), 12 deletions(-) delete mode 100644 docs/assets/getting-started/b-scaffold-3.png diff --git a/docs/assets/getting-started/b-scaffold-1.png b/docs/assets/getting-started/b-scaffold-1.png index 094b104b7c63d2a96a11d11206014cd6dcf0ee7e..d32010ae91477c3c37725545f33887f415df6bf5 100644 GIT binary patch literal 27164 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 Ye@@oh$xEp482pivc=&g^xQ^%l2M!h0p#T5? diff --git a/docs/assets/getting-started/b-scaffold-2.png b/docs/assets/getting-started/b-scaffold-2.png index db66b83a26db90a6a41e30443615402c0ce3ffd1..2e8374451b74f1d11bc6c32f2fcbc313c552e4da 100644 GIT binary patch literal 45990 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

- Software template deployment input screen asking for a name, the group owning this, and a description + Software template deployment input screen asking for a name

-- For the location, we're going to use the default -- As owner, type your GitHub username -- For the repository name, type `tutorial`. Go to the next step - +- You should see the following screen:

Software template deployment input screen asking for the GitHub username, and name of the new repo to create

+- For host, it should default to github.com +- As owner, type your GitHub username +- For the repository name, type `tutorial`. Go to the next step + - Review the details of this new service, and press `Create` if you want to deploy it like this. - - You can follow along with the progress, and as soon as every step is - finished, you can take a look at your new service +- You can follow along with the progress, and as soon as every step is + finished, you can take a look at your new service Achievement unlocked. You've set up an installation of the core Backstage App, made it persistent, and configured it so you are now able to use software From 35de97b7d48519cdb97e0ff8fc37d5669bc99baa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 12:48:13 +0000 Subject: [PATCH 218/221] Update dependency @roadiehq/backstage-plugin-github-pull-requests to v2.3.0 Signed-off-by: Renovate Bot --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 90823e053b..1d37d426f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5991,7 +5991,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-home@npm:^0.4.25, @backstage/plugin-home@npm:^0.4.26": +"@backstage/plugin-home@npm:^0.4.26": version: 0.4.26 resolution: "@backstage/plugin-home@npm:0.4.26" dependencies: @@ -12321,14 +12321,14 @@ __metadata: linkType: hard "@roadiehq/backstage-plugin-github-pull-requests@npm:^2.2.7": - version: 2.2.8 - resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.2.8" + version: 2.3.0 + resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.3.0" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-catalog-react": ^1.1.4 - "@backstage/plugin-home": ^0.4.25 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 + "@backstage/plugin-home": ^0.4.26 "@material-ui/core": ^4.11.0 "@material-ui/icons": ^4.9.1 "@material-ui/lab": ^4.0.0-alpha.60 @@ -12345,7 +12345,7 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: d334641a6eb1a464a930ef81ff3fd418d7ac96e15ff2aa9c9542d724dceac394d505335b53c77f4ef859791c347a8a70964e36e1e9b094ab289074e2c5b70bcf + checksum: bafb8645333b8477e4f87dff2c7e5bdd4008dc808577e7e05ff56e3b68ad9e5ff8d90cc746b46bc96267faefe6ad4082b9a15ae84cd02edaf5a15cd126aa6733 languageName: node linkType: hard From 6a8b7c7fbfdb6133aab163ced2c08f094403ee75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:12:01 +0000 Subject: [PATCH 219/221] Update dependency @roadiehq/backstage-plugin-travis-ci to v2.1.0 Signed-off-by: Renovate Bot --- yarn.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index ad78b71708..12971db870 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12350,13 +12350,13 @@ __metadata: linkType: hard "@roadiehq/backstage-plugin-travis-ci@npm:^2.0.5": - version: 2.0.6 - resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.0.6" + version: 2.1.0 + resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.1.0" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.11.3 "@material-ui/icons": ^4.11.2 @@ -12371,7 +12371,7 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: dfa6645c58640eabd60c5ea520c5e4cf9bba1001d40342bde87ad583b129a4a0c1f9ae1497aa52d3e975a76352251f716cbab927b29ae0cb63d3800129367234 + checksum: 99bd8d242bd954f9fd3c75910353d6cce6672ba605a819828a336316c0c7fd0d2f36bfdf7a82b702e284b8f636c06f50b93ae53af6afc3b12e8f766e7fe2ce9c languageName: node linkType: hard From b2e2093feeccbdfd759048cc6aa98abcff88931a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 26 Oct 2022 13:57:04 +0000 Subject: [PATCH 220/221] Update dependency @tanstack/react-query to v4.13.0 Signed-off-by: Renovate Bot --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 701905a5d8..d21c1e5e24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13019,18 +13019,18 @@ __metadata: languageName: node linkType: hard -"@tanstack/query-core@npm:4.12.0": - version: 4.12.0 - resolution: "@tanstack/query-core@npm:4.12.0" - checksum: 535719c2079c18bb83e459b968858c424b3404b34d662e853389067dae3b2dc61b5f92b28a93237830cac3de7d35ef61793d7ddb1df6b45bc7fe31ae7ddba246 +"@tanstack/query-core@npm:4.13.0": + version: 4.13.0 + resolution: "@tanstack/query-core@npm:4.13.0" + checksum: 11cb95be4dc6e1ba3f0f4eb04c255eab7fa5eca3441d6e698e2c6d03c84c03efaf34d475594e259593d76f584af0c0e830c7d868a682d6dc0f61d1a439728d12 languageName: node linkType: hard "@tanstack/react-query@npm:^4.1.3": - version: 4.12.0 - resolution: "@tanstack/react-query@npm:4.12.0" + version: 4.13.0 + resolution: "@tanstack/react-query@npm:4.13.0" dependencies: - "@tanstack/query-core": 4.12.0 + "@tanstack/query-core": 4.13.0 use-sync-external-store: ^1.2.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -13041,7 +13041,7 @@ __metadata: optional: true react-native: optional: true - checksum: 7c3688735bafa58e6993e1161fd53ec9d4f065de8cb04c4ec696e7e53fe86aef333df88553c8cecad278154c66ebe18c2c7072b0a0bbdce03c7112aabca81cda + checksum: e538b585e4c2b4a8ff84c17509f4ce226e18e3e862553b6a9ca90e29c200db4ec7ab4f400f19ef4846a76c51babe0db120c54779054805fbecdcd29b369caf0e languageName: node linkType: hard From 2bc18faf8512901ff99e7b3e0a7952ad3e180fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 27 Oct 2022 13:13:23 +0200 Subject: [PATCH 221/221] fix unclean yarn.lock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2332db24c1..c42aac2eec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3314,7 +3314,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@npm:^1.1.1, @backstage/catalog-model@npm:^1.1.2": +"@backstage/catalog-model@npm:^1.1.2": version: 1.1.2 resolution: "@backstage/catalog-model@npm:1.1.2" dependencies: @@ -3599,7 +3599,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.11.1, @backstage/core-components@npm:^0.11.2": +"@backstage/core-components@npm:^0.11.2": version: 0.11.2 resolution: "@backstage/core-components@npm:0.11.2" dependencies: @@ -3722,7 +3722,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@npm:^1.0.6, @backstage/core-plugin-api@npm:^1.0.7": +"@backstage/core-plugin-api@npm:^1.0.7": version: 1.0.7 resolution: "@backstage/core-plugin-api@npm:1.0.7" dependencies: @@ -5066,7 +5066,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.1.4, @backstage/plugin-catalog-react@npm:^1.2.0": +"@backstage/plugin-catalog-react@npm:^1.2.0": version: 1.2.0 resolution: "@backstage/plugin-catalog-react@npm:1.2.0" dependencies:

1VP(HeviFV|Ur|B) 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 R!2g7tHMTZ-d;03#{{bqFt)>6~ literal 48920 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 z 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 Yq5xqnqe`^VUk88Yzn(r`i17IRpToIQ#sB~S diff --git a/docs/assets/getting-started/b-scaffold-3.png b/docs/assets/getting-started/b-scaffold-3.png deleted file mode 100644 index b98ec2d8e391c30f6db03da1654634089f34d909..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48814 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 E0I1zSBLDyZ diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 9ce65294ad..fc5b02e6f4 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -284,27 +284,25 @@ otherwise something went terribly wrong. ## Create a new component using a software template -- Go to `create` and choose to create a website with the `React SSR Template` -- Type in a name, let's use `tutorial` -- Select the group `team-a` which will own this new website, and go to the next - step - +- Go to `create` and choose to create a website with the `Example Node.js Template` +- Type in a name, let's use `tutorial` and click `Next Step`