diff --git a/.changeset/eight-eels-compete.md b/.changeset/eight-eels-compete.md new file mode 100644 index 0000000000..5cd0cdf041 --- /dev/null +++ b/.changeset/eight-eels-compete.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-bazaar-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-playlist-backend': patch +'@backstage/plugin-search-backend-module-pg': patch +'@backstage/plugin-user-settings-backend': patch +--- + +Ensured typescript type checks in migration files. diff --git a/.changeset/empty-taxis-run.md b/.changeset/empty-taxis-run.md new file mode 100644 index 0000000000..d0360b4521 --- /dev/null +++ b/.changeset/empty-taxis-run.md @@ -0,0 +1,22 @@ +--- +'@backstage/backend-app-api': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-node': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-events-backend-module-aws-sqs': patch +'@backstage/plugin-events-backend-module-github': patch +'@backstage/plugin-events-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend': patch +--- + +Refactored to use `coreServices` from `@backstage/backend-plugin-api`. diff --git a/.changeset/gentle-ligers-help.md b/.changeset/gentle-ligers-help.md new file mode 100644 index 0000000000..f7f9c3b270 --- /dev/null +++ b/.changeset/gentle-ligers-help.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Tweak feature flag registration so that it happens immediately before the first rendering of the app, rather than just after. diff --git a/.changeset/loud-snails-sleep.md b/.changeset/loud-snails-sleep.md new file mode 100644 index 0000000000..d72c1a22b4 --- /dev/null +++ b/.changeset/loud-snails-sleep.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-vault-backend': patch +--- + +Added (optional) config `vault.publicUrl` as alternative to `vault.baseUrl` for `editUrl` and `showUrl` in case `vault.baseUrl` is internal diff --git a/.changeset/many-bikes-press.md b/.changeset/many-bikes-press.md new file mode 100644 index 0000000000..68ddb68c05 --- /dev/null +++ b/.changeset/many-bikes-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +**BREAKING**: All core service references are now exported via a single `coreServices` object. For example, the `loggerServiceRef` is now accessed via `coreServices.logger` instead. diff --git a/.changeset/modern-camels-cheat.md b/.changeset/modern-camels-cheat.md new file mode 100644 index 0000000000..c0a80934d5 --- /dev/null +++ b/.changeset/modern-camels-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Removed `CatalogKindHeader` from `DefaultCatalogPage`. Deprecated `CatalogKindHeader` in favour of `EntityKindPicker`. diff --git a/.changeset/nice-apricots-love.md b/.changeset/nice-apricots-love.md new file mode 100644 index 0000000000..527b644d5f --- /dev/null +++ b/.changeset/nice-apricots-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Prevent duplicate feature flag components from rendering in the settings when using components diff --git a/.changeset/nice-pants-boil.md b/.changeset/nice-pants-boil.md new file mode 100644 index 0000000000..e8bdf0a00f --- /dev/null +++ b/.changeset/nice-pants-boil.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': patch +'@backstage/plugin-catalog-backend': patch +--- + +Adds MySQL support for the catalog-backend diff --git a/.changeset/old-sloths-warn.md b/.changeset/old-sloths-warn.md new file mode 100644 index 0000000000..8eca0a9292 --- /dev/null +++ b/.changeset/old-sloths-warn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Internal refactor to avoid usage of deprecated symbols diff --git a/.changeset/renovate-8c69aed.md b/.changeset/renovate-8c69aed.md new file mode 100644 index 0000000000..63a61d941a --- /dev/null +++ b/.changeset/renovate-8c69aed.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Updated dependency `@types/http-errors` to `^2.0.0`. diff --git a/.changeset/renovate-bf8dfb3.md b/.changeset/renovate-bf8dfb3.md new file mode 100644 index 0000000000..57cabf075b --- /dev/null +++ b/.changeset/renovate-bf8dfb3.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `@rollup/plugin-yaml` to `^4.0.0`. diff --git a/.changeset/renovate-ebb2ea1.md b/.changeset/renovate-ebb2ea1.md new file mode 100644 index 0000000000..010236c2f2 --- /dev/null +++ b/.changeset/renovate-ebb2ea1.md @@ -0,0 +1,8 @@ +--- +'@backstage/core-components': patch +'@backstage/plugin-gcp-projects': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Updated dependency `@react-hookz/web` to `^20.0.0`. diff --git a/.changeset/search-lieutenant-dangle.md b/.changeset/search-lieutenant-dangle.md new file mode 100644 index 0000000000..75f1bca915 --- /dev/null +++ b/.changeset/search-lieutenant-dangle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Fixed a bug that prevented indices from being cleaned up under some circumstances, which could have led to shard exhaustion. diff --git a/.changeset/selfish-lizards-invent.md b/.changeset/selfish-lizards-invent.md new file mode 100644 index 0000000000..fd486416d8 --- /dev/null +++ b/.changeset/selfish-lizards-invent.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-analytics-module-ga': patch +--- + +Added support for sending virtual pageviews on `search` events in order to enable +Site Search functionality in GA. For more information consult [README](/plugins/analytics-module-ga/README.md#enabling-site-search) diff --git a/.changeset/short-turtles-dream.md b/.changeset/short-turtles-dream.md new file mode 100644 index 0000000000..022ef35f58 --- /dev/null +++ b/.changeset/short-turtles-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stack-overflow-backend': patch +--- + +Added option to supply API Access Token. This is required in addition to an API key when trying to access the data for a private Stack Overflow Team. diff --git a/.changeset/strong-peaches-melt.md b/.changeset/strong-peaches-melt.md new file mode 100644 index 0000000000..43f819fd4d --- /dev/null +++ b/.changeset/strong-peaches-melt.md @@ -0,0 +1,6 @@ +--- +'@backstage/core-components': patch +'@backstage/test-utils': patch +--- + +Sync components in @backstage/core-components with the Component Design Guidelines diff --git a/.changeset/thin-flies-wink.md b/.changeset/thin-flies-wink.md new file mode 100644 index 0000000000..a31db67a1c --- /dev/null +++ b/.changeset/thin-flies-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Bump `json-schema-library` to version `^7.3.9` which does not pull in the `gson-pointer` library diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 81e3604ffe..27b26659cf 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -360,7 +360,7 @@ Fields of a link are: _NOTE_: The `icon` field value is meant to be a semantic key that will map to a specific icon that may be provided by an icon library (e.g. `material-ui` icons). These keys should be a sequence of `[a-z0-9A-Z]`, possibly separated by -one of `[-_.]`. Backstage may support some basic icons out of the box, but the +one of `[-_.]`. Backstage may support some basic icons out of the box such as those [defined in app-defaults](https://github.com/backstage/backstage/blob/master/packages/app-defaults/src/defaults/icons.tsx), but the Backstage integrator will ultimately be left to provide the appropriate icon component mappings. A generic fallback icon would be provided if a mapping cannot be resolved. diff --git a/microsite/data/plugins/cicd-statistics.yaml b/microsite/data/plugins/cicd-statistics.yaml new file mode 100644 index 0000000000..a43b56b0d9 --- /dev/null +++ b/microsite/data/plugins/cicd-statistics.yaml @@ -0,0 +1,13 @@ +--- +title: CI/CD Statistics +author: Spotify +authorUrl: https://github.com/spotify +category: CI/CD +description: Visualize CI/CD pipeline statistics such as build time or success and error rates. +documentation: https://github.com/backstage/backstage/tree/master/plugins/cicd-statistics +iconUrl: img/cicd-statistics.svg +npmPackageName: '@backstage/plugin-cicd-statistics' +tags: + - ci + - cd +addedDate: '2022-01-13' diff --git a/microsite/static/img/cicd-statistics.svg b/microsite/static/img/cicd-statistics.svg new file mode 100644 index 0000000000..302f5e8f98 --- /dev/null +++ b/microsite/static/img/cicd-statistics.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/microsite/yarn.lock b/microsite/yarn.lock index f0e6bd698a..0a26cf8ef6 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2940,9 +2940,9 @@ __metadata: linkType: hard "decode-uri-component@npm:^0.2.0": - version: 0.2.0 - resolution: "decode-uri-component@npm:0.2.0" - checksum: f3749344ab9305ffcfe4bfe300e2dbb61fc6359e2b736812100a3b1b6db0a5668cba31a05e4b45d4d63dbf1a18dfa354cd3ca5bb3ededddabb8cd293f4404f94 + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 95476a7d28f267292ce745eac3524a9079058bbb35767b76e3ee87d42e34cd0275d2eb19d9d08c3e167f97556e8a2872747f5e65cbebcac8b0c98d83e285f139 languageName: node linkType: hard diff --git a/packages/backend-app-api/src/services/implementations/cacheService.ts b/packages/backend-app-api/src/services/implementations/cacheService.ts index 7e15f031c6..df4a782a46 100644 --- a/packages/backend-app-api/src/services/implementations/cacheService.ts +++ b/packages/backend-app-api/src/services/implementations/cacheService.ts @@ -16,18 +16,16 @@ import { CacheManager } from '@backstage/backend-common'; import { - configServiceRef, + coreServices, createServiceFactory, - pluginMetadataServiceRef, - cacheServiceRef, } from '@backstage/backend-plugin-api'; /** @public */ export const cacheFactory = createServiceFactory({ - service: cacheServiceRef, + service: coreServices.cache, deps: { - config: configServiceRef, - plugin: pluginMetadataServiceRef, + config: coreServices.config, + plugin: coreServices.pluginMetadata, }, async factory({ config }) { const cacheManager = CacheManager.fromConfig(config); diff --git a/packages/backend-app-api/src/services/implementations/configService.ts b/packages/backend-app-api/src/services/implementations/configService.ts index 91327289df..f58a756c64 100644 --- a/packages/backend-app-api/src/services/implementations/configService.ts +++ b/packages/backend-app-api/src/services/implementations/configService.ts @@ -16,17 +16,16 @@ import { loadBackendConfig } from '@backstage/backend-common'; import { - configServiceRef, + coreServices, createServiceFactory, loggerToWinstonLogger, - rootLoggerServiceRef, } from '@backstage/backend-plugin-api'; /** @public */ export const configFactory = createServiceFactory({ - service: configServiceRef, + service: coreServices.config, deps: { - logger: rootLoggerServiceRef, + logger: coreServices.rootLogger, }, async factory({ logger }) { const config = await loadBackendConfig({ diff --git a/packages/backend-app-api/src/services/implementations/databaseService.ts b/packages/backend-app-api/src/services/implementations/databaseService.ts index f6401528e6..f33e825c9b 100644 --- a/packages/backend-app-api/src/services/implementations/databaseService.ts +++ b/packages/backend-app-api/src/services/implementations/databaseService.ts @@ -16,18 +16,16 @@ import { DatabaseManager } from '@backstage/backend-common'; import { - configServiceRef, + coreServices, createServiceFactory, - databaseServiceRef, - pluginMetadataServiceRef, } from '@backstage/backend-plugin-api'; /** @public */ export const databaseFactory = createServiceFactory({ - service: databaseServiceRef, + service: coreServices.database, deps: { - config: configServiceRef, - plugin: pluginMetadataServiceRef, + config: coreServices.config, + plugin: coreServices.pluginMetadata, }, async factory({ config }) { const databaseManager = DatabaseManager.fromConfig(config); diff --git a/packages/backend-app-api/src/services/implementations/discoveryService.ts b/packages/backend-app-api/src/services/implementations/discoveryService.ts index 7f35bf2447..0c04ec6bf0 100644 --- a/packages/backend-app-api/src/services/implementations/discoveryService.ts +++ b/packages/backend-app-api/src/services/implementations/discoveryService.ts @@ -16,16 +16,15 @@ import { SingleHostDiscovery } from '@backstage/backend-common'; import { - configServiceRef, + coreServices, createServiceFactory, - discoveryServiceRef, } from '@backstage/backend-plugin-api'; /** @public */ export const discoveryFactory = createServiceFactory({ - service: discoveryServiceRef, + service: coreServices.discovery, deps: { - config: configServiceRef, + config: coreServices.config, }, async factory({ config }) { const discovery = SingleHostDiscovery.fromConfig(config); diff --git a/packages/backend-app-api/src/services/implementations/httpRouterService.ts b/packages/backend-app-api/src/services/implementations/httpRouterService.ts index d9fc807b1a..2021589a50 100644 --- a/packages/backend-app-api/src/services/implementations/httpRouterService.ts +++ b/packages/backend-app-api/src/services/implementations/httpRouterService.ts @@ -16,9 +16,7 @@ import { createServiceFactory, - httpRouterServiceRef, - configServiceRef, - pluginMetadataServiceRef, + coreServices, } from '@backstage/backend-plugin-api'; import Router from 'express-promise-router'; import { Handler } from 'express'; @@ -36,10 +34,10 @@ export type HttpRouterFactoryOptions = { /** @public */ export const httpRouterFactory = createServiceFactory({ - service: httpRouterServiceRef, + service: coreServices.httpRouter, deps: { - config: configServiceRef, - plugin: pluginMetadataServiceRef, + config: coreServices.config, + plugin: coreServices.pluginMetadata, }, async factory({ config }, options?: HttpRouterFactoryOptions) { const defaultPluginId = options?.indexPlugin ?? 'app'; diff --git a/packages/backend-app-api/src/services/implementations/lifecycleService.ts b/packages/backend-app-api/src/services/implementations/lifecycleService.ts index 7d41a7a276..5616296e93 100644 --- a/packages/backend-app-api/src/services/implementations/lifecycleService.ts +++ b/packages/backend-app-api/src/services/implementations/lifecycleService.ts @@ -16,10 +16,8 @@ import { BackendLifecycle, createServiceFactory, - lifecycleServiceRef, + coreServices, loggerToWinstonLogger, - pluginMetadataServiceRef, - rootLoggerServiceRef, BackendLifecycleShutdownHook, } from '@backstage/backend-plugin-api'; import { Logger } from 'winston'; @@ -80,10 +78,10 @@ class PluginScopedLifecycleImpl implements BackendLifecycle { * Allows plugins to register shutdown hooks that are run when the process is about to exit. * @public */ export const lifecycleFactory = createServiceFactory({ - service: lifecycleServiceRef, + service: coreServices.lifecycle, deps: { - logger: rootLoggerServiceRef, - plugin: pluginMetadataServiceRef, + logger: coreServices.rootLogger, + plugin: coreServices.pluginMetadata, }, async factory({ logger }) { const rootLifecycle = new BackendLifecycleImpl( diff --git a/packages/backend-app-api/src/services/implementations/loggerService.ts b/packages/backend-app-api/src/services/implementations/loggerService.ts index ff72020140..52b391249d 100644 --- a/packages/backend-app-api/src/services/implementations/loggerService.ts +++ b/packages/backend-app-api/src/services/implementations/loggerService.ts @@ -16,17 +16,15 @@ import { createServiceFactory, - loggerServiceRef, - pluginMetadataServiceRef, - rootLoggerServiceRef, + coreServices, } from '@backstage/backend-plugin-api'; /** @public */ export const loggerFactory = createServiceFactory({ - service: loggerServiceRef, + service: coreServices.logger, deps: { - rootLogger: rootLoggerServiceRef, - plugin: pluginMetadataServiceRef, + rootLogger: coreServices.rootLogger, + plugin: coreServices.pluginMetadata, }, async factory({ rootLogger }) { return async ({ plugin }) => { diff --git a/packages/backend-app-api/src/services/implementations/permissionsService.ts b/packages/backend-app-api/src/services/implementations/permissionsService.ts index 32e8a1a9f9..d3f8d5783c 100644 --- a/packages/backend-app-api/src/services/implementations/permissionsService.ts +++ b/packages/backend-app-api/src/services/implementations/permissionsService.ts @@ -15,21 +15,18 @@ */ import { - configServiceRef, + coreServices, createServiceFactory, - discoveryServiceRef, - permissionsServiceRef, - tokenManagerServiceRef, } from '@backstage/backend-plugin-api'; import { ServerPermissionClient } from '@backstage/plugin-permission-node'; /** @public */ export const permissionsFactory = createServiceFactory({ - service: permissionsServiceRef, + service: coreServices.permissions, deps: { - config: configServiceRef, - discovery: discoveryServiceRef, - tokenManager: tokenManagerServiceRef, + config: coreServices.config, + discovery: coreServices.discovery, + tokenManager: coreServices.tokenManager, }, async factory({ config }) { return async ({ discovery, tokenManager }) => { diff --git a/packages/backend-app-api/src/services/implementations/rootLoggerService.ts b/packages/backend-app-api/src/services/implementations/rootLoggerService.ts index a65a8d9126..d2bb406adb 100644 --- a/packages/backend-app-api/src/services/implementations/rootLoggerService.ts +++ b/packages/backend-app-api/src/services/implementations/rootLoggerService.ts @@ -18,7 +18,7 @@ import { createRootLogger } from '@backstage/backend-common'; import { createServiceFactory, Logger, - rootLoggerServiceRef, + coreServices, } from '@backstage/backend-plugin-api'; import { Logger as WinstonLogger } from 'winston'; @@ -40,7 +40,7 @@ class BackstageLogger implements Logger { /** @public */ export const rootLoggerFactory = createServiceFactory({ - service: rootLoggerServiceRef, + service: coreServices.rootLogger, deps: {}, async factory() { return BackstageLogger.fromWinston(createRootLogger()); diff --git a/packages/backend-app-api/src/services/implementations/schedulerService.ts b/packages/backend-app-api/src/services/implementations/schedulerService.ts index 40676344ec..a6edec868a 100644 --- a/packages/backend-app-api/src/services/implementations/schedulerService.ts +++ b/packages/backend-app-api/src/services/implementations/schedulerService.ts @@ -15,19 +15,17 @@ */ import { - configServiceRef, + coreServices, createServiceFactory, - pluginMetadataServiceRef, - schedulerServiceRef, } from '@backstage/backend-plugin-api'; import { TaskScheduler } from '@backstage/backend-tasks'; /** @public */ export const schedulerFactory = createServiceFactory({ - service: schedulerServiceRef, + service: coreServices.scheduler, deps: { - config: configServiceRef, - plugin: pluginMetadataServiceRef, + config: coreServices.config, + plugin: coreServices.pluginMetadata, }, async factory({ config }) { const taskScheduler = TaskScheduler.fromConfig(config); diff --git a/packages/backend-app-api/src/services/implementations/tokenManagerService.ts b/packages/backend-app-api/src/services/implementations/tokenManagerService.ts index 92f42c10db..6a2d47ae14 100644 --- a/packages/backend-app-api/src/services/implementations/tokenManagerService.ts +++ b/packages/backend-app-api/src/services/implementations/tokenManagerService.ts @@ -15,20 +15,18 @@ */ import { - configServiceRef, - loggerServiceRef, + coreServices, createServiceFactory, - tokenManagerServiceRef, loggerToWinstonLogger, } from '@backstage/backend-plugin-api'; import { ServerTokenManager } from '@backstage/backend-common'; /** @public */ export const tokenManagerFactory = createServiceFactory({ - service: tokenManagerServiceRef, + service: coreServices.tokenManager, deps: { - config: configServiceRef, - logger: loggerServiceRef, + config: coreServices.config, + logger: coreServices.logger, }, async factory() { return async ({ config, logger }) => { diff --git a/packages/backend-app-api/src/services/implementations/urlReaderService.ts b/packages/backend-app-api/src/services/implementations/urlReaderService.ts index d7d7502a08..b68be74d8c 100644 --- a/packages/backend-app-api/src/services/implementations/urlReaderService.ts +++ b/packages/backend-app-api/src/services/implementations/urlReaderService.ts @@ -16,19 +16,17 @@ import { UrlReaders } from '@backstage/backend-common'; import { - configServiceRef, + coreServices, createServiceFactory, - loggerServiceRef, loggerToWinstonLogger, - urlReaderServiceRef, } from '@backstage/backend-plugin-api'; /** @public */ export const urlReaderFactory = createServiceFactory({ - service: urlReaderServiceRef, + service: coreServices.urlReader, deps: { - config: configServiceRef, - logger: loggerServiceRef, + config: coreServices.config, + logger: coreServices.logger, }, async factory() { return async ({ config, logger }) => { diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts index 897ed4e3cd..b878a6a75d 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts @@ -18,7 +18,7 @@ import { createServiceRef, createServiceFactory, ServiceRef, - pluginMetadataServiceRef, + coreServices, } from '@backstage/backend-plugin-api'; import { ServiceRegistry } from './ServiceRegistry'; @@ -172,7 +172,7 @@ describe('ServiceRegistry', () => { const ref = createServiceRef<{ pluginId: string }>({ id: 'x' }); const factory = createServiceFactory({ service: ref, - deps: { meta: pluginMetadataServiceRef }, + deps: { meta: coreServices.pluginMetadata }, async factory() { return async ({ meta }) => ({ pluginId: meta.getId() }); }, diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts index 007ca00c88..9ff73f4270 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -17,7 +17,7 @@ import { ServiceFactory, ServiceRef, - pluginMetadataServiceRef, + coreServices, } from '@backstage/backend-plugin-api'; import { stringifyError } from '@backstage/errors'; @@ -65,10 +65,10 @@ export class ServiceRegistry { pluginId: string, ): Promise | undefined { // Special case handling of the plugin metadata service, generating a custom factory for it each time - if (ref.id === pluginMetadataServiceRef.id) { + if (ref.id === coreServices.pluginMetadata.id) { return Promise.resolve({ scope: 'plugin', - service: pluginMetadataServiceRef, + service: coreServices.pluginMetadata, deps: {}, factory: async () => async () => ({ getId() { @@ -114,7 +114,7 @@ export class ServiceRegistry { #checkForMissingDeps(factory: ServiceFactory, pluginId: string) { const missingDeps = Object.values(factory.deps).filter(ref => { - if (ref.id === pluginMetadataServiceRef.id) { + if (ref.id === coreServices.pluginMetadata.id) { return false; } if (this.#providedFactories.get(ref.id)) { diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index eb9ab49521..d68d22b9a8 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -101,7 +101,7 @@ "@types/compression": "^1.7.0", "@types/concat-stream": "^2.0.0", "@types/fs-extra": "^9.0.3", - "@types/http-errors": "^1.6.3", + "@types/http-errors": "^2.0.0", "@types/minimist": "^1.2.0", "@types/mock-fs": "^4.13.0", "@types/morgan": "^1.9.0", diff --git a/packages/backend-common/src/database/util.ts b/packages/backend-common/src/database/util.ts index 0a7d3cd7f7..f5abcd7fba 100644 --- a/packages/backend-common/src/database/util.ts +++ b/packages/backend-common/src/database/util.ts @@ -29,6 +29,7 @@ export function isDatabaseConflictError(e: unknown) { typeof message === 'string' && (/SQLITE_CONSTRAINT(?:_UNIQUE)?: UNIQUE/.test(message) || /UNIQUE constraint failed:/.test(message) || - /unique constraint/.test(message)) + /unique constraint/.test(message) || + /Duplicate entry/.test(message)) // MySQL uniqueness error msg ); } diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index b73a85c32e..17cae29bff 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -76,10 +76,29 @@ export interface BackendRegistrationPoints { } // @public (undocumented) -export const cacheServiceRef: ServiceRef; +const cacheServiceRef: ServiceRef; // @public (undocumented) -export const configServiceRef: ServiceRef; +const configServiceRef: ServiceRef; + +declare namespace coreServices { + export { + configServiceRef as config, + httpRouterServiceRef as httpRouter, + loggerServiceRef as logger, + urlReaderServiceRef as urlReader, + cacheServiceRef as cache, + databaseServiceRef as database, + discoveryServiceRef as discovery, + tokenManagerServiceRef as tokenManager, + permissionsServiceRef as permissions, + schedulerServiceRef as scheduler, + rootLoggerServiceRef as rootLogger, + pluginMetadataServiceRef as pluginMetadata, + lifecycleServiceRef as lifecycle, + }; +} +export { coreServices }; // @public export function createBackendModule< @@ -146,10 +165,10 @@ export function createServiceRef(options: { }): ServiceRef; // @public (undocumented) -export const databaseServiceRef: ServiceRef; +const databaseServiceRef: ServiceRef; // @public (undocumented) -export const discoveryServiceRef: ServiceRef; +const discoveryServiceRef: ServiceRef; // @public export type ExtensionPoint = { @@ -166,10 +185,10 @@ export interface HttpRouterService { } // @public (undocumented) -export const httpRouterServiceRef: ServiceRef; +const httpRouterServiceRef: ServiceRef; // @public (undocumented) -export const lifecycleServiceRef: ServiceRef; +const lifecycleServiceRef: ServiceRef; // @public (undocumented) export interface Logger { @@ -180,7 +199,7 @@ export interface Logger { } // @public (undocumented) -export const loggerServiceRef: ServiceRef; +const loggerServiceRef: ServiceRef; // @public (undocumented) export function loggerToWinstonLogger( @@ -189,7 +208,7 @@ export function loggerToWinstonLogger( ): Logger_2; // @public (undocumented) -export const permissionsServiceRef: ServiceRef< +const permissionsServiceRef: ServiceRef< PermissionAuthorizer | PermissionEvaluator, 'plugin' >; @@ -201,13 +220,13 @@ export interface PluginMetadata { } // @public (undocumented) -export const pluginMetadataServiceRef: ServiceRef; +const pluginMetadataServiceRef: ServiceRef; // @public (undocumented) -export const rootLoggerServiceRef: ServiceRef; +const rootLoggerServiceRef: ServiceRef; // @public (undocumented) -export const schedulerServiceRef: ServiceRef; +const schedulerServiceRef: ServiceRef; // @public (undocumented) export type ServiceFactory = @@ -249,7 +268,7 @@ export type ServiceRef< }; // @public (undocumented) -export const tokenManagerServiceRef: ServiceRef; +const tokenManagerServiceRef: ServiceRef; // @public (undocumented) export type TypesToServiceRef = { @@ -257,5 +276,5 @@ export type TypesToServiceRef = { }; // @public (undocumented) -export const urlReaderServiceRef: ServiceRef; +const urlReaderServiceRef: ServiceRef; ``` diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts new file mode 100644 index 0000000000..d5a5953806 --- /dev/null +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -0,0 +1,29 @@ +/* + * 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 { configServiceRef as config } from './configServiceRef'; +export { httpRouterServiceRef as httpRouter } from './httpRouterServiceRef'; +export { loggerServiceRef as logger } from './loggerServiceRef'; +export { urlReaderServiceRef as urlReader } from './urlReaderServiceRef'; +export { cacheServiceRef as cache } from './cacheServiceRef'; +export { databaseServiceRef as database } from './databaseServiceRef'; +export { discoveryServiceRef as discovery } from './discoveryServiceRef'; +export { tokenManagerServiceRef as tokenManager } from './tokenManagerServiceRef'; +export { permissionsServiceRef as permissions } from './permissionsServiceRef'; +export { schedulerServiceRef as scheduler } from './schedulerServiceRef'; +export { rootLoggerServiceRef as rootLogger } from './rootLoggerServiceRef'; +export { pluginMetadataServiceRef as pluginMetadata } from './pluginMetadataServiceRef'; +export { lifecycleServiceRef as lifecycle } from './lifecycleServiceRef'; diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index cf997a344d..1ff33362fa 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -14,21 +14,11 @@ * limitations under the License. */ -export { configServiceRef } from './configServiceRef'; -export { httpRouterServiceRef } from './httpRouterServiceRef'; +import * as coreServices from './coreServices'; + +export { coreServices }; export type { HttpRouterService } from './httpRouterServiceRef'; -export { loggerServiceRef } from './loggerServiceRef'; export type { Logger } from './loggerServiceRef'; -export { urlReaderServiceRef } from './urlReaderServiceRef'; -export { cacheServiceRef } from './cacheServiceRef'; -export { databaseServiceRef } from './databaseServiceRef'; -export { discoveryServiceRef } from './discoveryServiceRef'; -export { tokenManagerServiceRef } from './tokenManagerServiceRef'; -export { permissionsServiceRef } from './permissionsServiceRef'; -export { schedulerServiceRef } from './schedulerServiceRef'; -export { rootLoggerServiceRef } from './rootLoggerServiceRef'; -export { pluginMetadataServiceRef } from './pluginMetadataServiceRef'; -export { lifecycleServiceRef } from './lifecycleServiceRef'; export type { BackendLifecycle, BackendLifecycleShutdownHook, diff --git a/packages/cli/package.json b/packages/cli/package.json index eef838d8ca..ca2bdfb56c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,7 +42,7 @@ "@rollup/plugin-commonjs": "^23.0.0", "@rollup/plugin-json": "^5.0.0", "@rollup/plugin-node-resolve": "^13.0.6", - "@rollup/plugin-yaml": "^3.1.0", + "@rollup/plugin-yaml": "^4.0.0", "@spotify/eslint-config-base": "^14.0.0", "@spotify/eslint-config-react": "^14.0.0", "@spotify/eslint-config-typescript": "^14.0.0", diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index c70b43581a..f81f382650 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -21,7 +21,7 @@ import { withLogCollector, } from '@backstage/test-utils'; import { render, screen } from '@testing-library/react'; -import React, { PropsWithChildren } from 'react'; +import React, { PropsWithChildren, ReactNode } from 'react'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import { configApiRef, @@ -34,9 +34,11 @@ import { createSubRouteRef, createRoutableExtension, analyticsApiRef, + useApi, } from '@backstage/core-plugin-api'; import { AppManager } from './AppManager'; import { AppComponents, AppIcons } from './types'; +import { FeatureFlagged } from '../routing/FeatureFlagged'; describe('Integration Test', () => { const noOpAnalyticsApi = createApiFactory( @@ -164,19 +166,20 @@ describe('Integration Test', () => { }; const icons = {} as AppIcons; + const themes = [ + { + id: 'light', + title: 'Light Theme', + variant: 'light' as const, + Provider: ({ children }: { children: ReactNode }) => <>{children}, + }, + ]; it('runs happy paths', async () => { const app = new AppManager({ apis: [noOpAnalyticsApi], defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - Provider: ({ children }) => <>{children}, - }, - ], + themes, icons, plugins: [], components, @@ -224,14 +227,7 @@ describe('Integration Test', () => { const app = new AppManager({ apis: [noOpAnalyticsApi], defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - Provider: ({ children }) => <>{children}, - }, - ], + themes, icons, plugins: [], components, @@ -282,14 +278,7 @@ describe('Integration Test', () => { const app = new AppManager({ apis, defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - Provider: ({ children }) => <>{children}, - }, - ], + themes, icons, plugins: [ createPlugin({ @@ -345,14 +334,7 @@ describe('Integration Test', () => { const app = new AppManager({ apis, defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - Provider: ({ children }) => <>{children}, - }, - ], + themes, icons, plugins: [ createPlugin({ @@ -415,20 +397,103 @@ describe('Integration Test', () => { }); }); + it('feature flags should be available immediately', async () => { + const app = new AppManager({ + apis: [ + createApiFactory({ + api: featureFlagsApiRef, + deps: { configApi: configApiRef }, + factory() { + return new LocalStorageFeatureFlags(); + }, + }), + ], + defaultApis: [], + themes, + icons, + plugins: [createPlugin({ id: 'test', featureFlags: [{ name: 'foo' }] })], + components, + configLoader: async () => [], + }); + + const Provider = app.getProvider(); + const Router = app.getRouter(); + + const FeatureFlags = () => { + const featureFlags = useApi(featureFlagsApiRef).getRegisteredFlags(); + return
Flags: {featureFlags.map(f => f.name).join(',')}
; + }; + + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText('Flags: foo')).toBeInTheDocument(); + }); + + it('should prevent duplicate feature flags from being rendered', async () => { + const p1 = createPlugin({ + id: 'p1', + featureFlags: [{ name: 'show-p1-feature' }], + }); + const p2 = createPlugin({ + id: 'p2', + featureFlags: [{ name: 'show-p2-feature' }], + }); + + const app = new AppManager({ + apis: [], + defaultApis: [], + themes, + icons, + plugins: [p1, p2], + components, + configLoader: async () => [], + }); + + const Provider = app.getProvider(); + const Router = app.getRouter(); + + function FeatureFlags() { + const featureFlags = useApi(featureFlagsApiRef); + return ( +
{`Flags: ${featureFlags + .getRegisteredFlags() + .map(f => f.name) + .join(',')}`}
+ ); + } + + await renderWithEffects( + + + +
My feature behind a flag
+
+ +
My feature behind a flag
+
+ +
+
, + ); + + expect( + screen.getByText('Flags: show-p1-feature,show-p2-feature'), + ).toBeInTheDocument(); + }); + it('should track route changes via analytics api', async () => { const mockAnalyticsApi = new MockAnalyticsApi(); const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)]; const app = new AppManager({ apis, defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - Provider: ({ children }) => <>{children}, - }, - ], + themes, icons, plugins: [], components, @@ -482,14 +547,7 @@ describe('Integration Test', () => { const app = new AppManager({ apis: [], defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - Provider: ({ children }) => <>{children}, - }, - ], + themes, icons, plugins: [], components, @@ -532,14 +590,7 @@ describe('Integration Test', () => { const app = new AppManager({ apis: [], defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - Provider: ({ children }) => <>{children}, - }, - ], + themes, icons, plugins: [], components, diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 4582a3c7cd..b7eafb0100 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -21,8 +21,8 @@ import React, { PropsWithChildren, ReactElement, useContext, - useEffect, useMemo, + useRef, useState, } from 'react'; import { Route, Routes } from 'react-router-dom'; @@ -230,6 +230,7 @@ export class AppManager implements BackstageApp { let routesHaveBeenValidated = false; const Provider = ({ children }: PropsWithChildren<{}>) => { + const needsFeatureFlagRegistrationRef = useRef(true); const appThemeApi = useMemo( () => AppThemeSelector.createWithStorage(this.themes), [], @@ -284,10 +285,21 @@ export class AppManager implements BackstageApp { this.configApi = api; } - useEffect(() => { - if (hasConfigApi) { - const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; + if ('node' in loadedConfig) { + // Loading or error + return loadedConfig.node; + } + // We can't register feature flags just after the element traversal, because the + // config API isn't available yet and implementations frequently depend on it. + // Instead we make it happen immediately, to make sure all flags are available + // for the first render. + if (hasConfigApi && needsFeatureFlagRegistrationRef.current) { + needsFeatureFlagRegistrationRef.current = false; + + const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!; + + if (featureFlagsApi) { for (const plugin of this.plugins.values()) { if ('getFeatureFlags' in plugin) { for (const flag of plugin.getFeatureFlags()) { @@ -310,15 +322,15 @@ export class AppManager implements BackstageApp { // Go through the featureFlags returned from the traversal and // register those now the configApi has been loaded + const registeredFlags = featureFlagsApi.getRegisteredFlags(); + const flagNames = new Set(registeredFlags.map(f => f.name)); for (const name of featureFlags) { - featureFlagsApi.registerFlag({ name, pluginId: '' }); + // Prevents adding duplicate feature flags + if (!flagNames.has(name)) { + featureFlagsApi.registerFlag({ name, pluginId: '' }); + } } } - }, [hasConfigApi, loadedConfig, featureFlags]); - - if ('node' in loadedConfig) { - // Loading or error - return loadedConfig.node; } const { ThemeProvider = AppThemeProvider } = this.components; diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 248b9e227c..9ed964db44 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -52,10 +52,10 @@ import { Theme } from '@material-ui/core/styles'; import { TooltipProps } from '@material-ui/core/Tooltip'; import { WithStyles } from '@material-ui/core/styles'; -// @public (undocumented) +// @public export function AlertDisplay(props: AlertDisplayProps): JSX.Element | null; -// @public +// @public (undocumented) export type AlertDisplayProps = { anchorOrigin?: { vertical: 'top' | 'bottom'; diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 56e124d094..8a6ffdf02d 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -41,7 +41,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", - "@react-hookz/web": "^19.0.0", + "@react-hookz/web": "^20.0.0", "@types/react-sparklines": "^1.7.0", "@types/react-text-truncate": "^0.14.0", "ansi-regex": "^6.0.1", diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx index c1f8f51db0..83c11647bb 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx @@ -13,14 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React, { useEffect, useState } from 'react'; -import Snackbar from '@material-ui/core/Snackbar'; +import { alertApiRef, AlertMessage, useApi } from '@backstage/core-plugin-api'; import IconButton from '@material-ui/core/IconButton'; +import Snackbar from '@material-ui/core/Snackbar'; +import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; import { Alert } from '@material-ui/lab'; -import { AlertMessage, useApi, alertApiRef } from '@backstage/core-plugin-api'; import pluralize from 'pluralize'; +import React, { useEffect, useState } from 'react'; + +// TODO: improve on this and promote to a shared component for use by all apps. + +/** @public */ +export type AlertDisplayProps = { + anchorOrigin?: { + vertical: 'top' | 'bottom'; + horizontal: 'left' | 'center' | 'right'; + }; +}; /** * Displays alerts from {@link @backstage/core-plugin-api#AlertApi} @@ -30,17 +40,6 @@ import pluralize from 'pluralize'; * * Shown as SnackBar at the center top of the page by default. Configurable with props. */ - -// TODO: improve on this and promote to a shared component for use by all apps. - -export type AlertDisplayProps = { - anchorOrigin?: { - vertical: 'top' | 'bottom'; - horizontal: 'left' | 'center' | 'right'; - }; -}; - -/** @public */ export function AlertDisplay(props: AlertDisplayProps) { const [messages, setMessages] = useState>([]); const alertApi = useApi(alertApiRef); @@ -82,7 +81,7 @@ export function AlertDisplay(props: AlertDisplayProps) { } severity={firstMessage.severity} > - + {String(firstMessage.message)} {messages.length > 1 && ( {` (${messages.length - 1} older ${pluralize( @@ -90,7 +89,7 @@ export function AlertDisplay(props: AlertDisplayProps) { messages.length - 1, )})`} )} - + ); diff --git a/packages/core-components/src/components/Avatar/Avatar.test.tsx b/packages/core-components/src/components/Avatar/Avatar.test.tsx index d7bbf712fe..4c726eb785 100644 --- a/packages/core-components/src/components/Avatar/Avatar.test.tsx +++ b/packages/core-components/src/components/Avatar/Avatar.test.tsx @@ -29,7 +29,9 @@ describe('', () => { it('generates a background color', async () => { const bgcolor = stringToColor('John Doe'); const { getByText } = render(); - expect(getByText('JD')).toHaveStyle(`background-color: ${bgcolor}`); + expect(getByText('JD').parentElement).toHaveStyle( + `background-color: ${bgcolor}`, + ); }); it('does not generate a background color when a picture is given', async () => { diff --git a/packages/core-components/src/components/Avatar/Avatar.tsx b/packages/core-components/src/components/Avatar/Avatar.tsx index af8dabf554..950f57668e 100644 --- a/packages/core-components/src/components/Avatar/Avatar.tsx +++ b/packages/core-components/src/components/Avatar/Avatar.tsx @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { CSSProperties } from 'react'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; import MaterialAvatar from '@material-ui/core/Avatar'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React, { CSSProperties } from 'react'; + import { extractInitials, stringToColor } from './utils'; /** @public */ @@ -28,6 +30,8 @@ const useStyles = makeStyles( width: '4rem', height: '4rem', color: '#fff', + }, + avatarText: { fontWeight: theme.typography.fontWeightBold, letterSpacing: '1px', textTransform: 'uppercase', @@ -68,6 +72,11 @@ export function Avatar(props: AvatarProps) { const { displayName, picture, customStyles } = props; const classes = useStyles(); let styles = { ...customStyles }; + const fontStyles = { + fontFamily: styles.fontFamily, + fontSize: styles.fontSize, + fontWeight: styles.fontWeight, + }; // We only calculate the background color if there's not an avatar // picture. If there is a picture, it might have a transparent // background and we don't know whether the calculated background @@ -85,7 +94,16 @@ export function Avatar(props: AvatarProps) { className={classes.avatar} style={styles} > - {displayName && extractInitials(displayName)} + {displayName && ( + + {extractInitials(displayName)} + + )} ); } diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx index fd83797a88..3270cac176 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx @@ -13,16 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React from 'react'; -import { useTheme } from '@material-ui/core/styles'; import { BackstageTheme } from '@backstage/theme'; -import { CopyTextButton } from '../CopyTextButton'; +import Box from '@material-ui/core/Box'; +import { useTheme } from '@material-ui/core/styles'; +import React from 'react'; import type {} from 'react-syntax-highlighter'; -import { default as LightAsync } from 'react-syntax-highlighter/dist/esm/light-async'; +import LightAsync from 'react-syntax-highlighter/dist/esm/light-async'; import dark from 'react-syntax-highlighter/dist/esm/styles/hljs/dark'; import docco from 'react-syntax-highlighter/dist/esm/styles/hljs/docco'; +import { CopyTextButton } from '../CopyTextButton'; + /** * Properties for {@link CodeSnippet} * @@ -87,7 +88,7 @@ export function CodeSnippet(props: CodeSnippetProps) { const highlightColor = theme.palette.type === 'dark' ? '#256bf3' : '#e6ffed'; return ( -
+ {showCopyCodeButton && ( -
+ -
+
)} -
+ ); } diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx index 99113fcdb1..c18f9b605f 100644 --- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx +++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx @@ -58,7 +58,7 @@ const useStyles = makeStyles( zIndex: 'unset', }, icon: { - fontSize: 20, + fontSize: theme.typography.h6.fontSize, }, content: { width: '100%', diff --git a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx index 59596e6d2b..1910d6e7fd 100644 --- a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx @@ -13,15 +13,16 @@ * 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 Button from '@material-ui/core/Button'; -import Typography from '@material-ui/core/Typography'; import { BackstageTheme } from '@backstage/theme'; +import Box from '@material-ui/core/Box'; +import Button from '@material-ui/core/Button'; +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; + +import { CodeSnippet } from '../CodeSnippet'; import { Link } from '../Link'; import { EmptyState } from './EmptyState'; -import { CodeSnippet } from '../CodeSnippet'; const COMPONENT_YAML_TEMPLATE = `apiVersion: backstage.io/v1alpha1 kind: Component @@ -109,7 +110,7 @@ export function MissingAnnotationEmptyState(props: Props) { Add the annotation to your component YAML as shown in the highlighted example below: -
+ -
+ diff --git a/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx b/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx index cac3c8166b..b967ee0a9f 100644 --- a/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx +++ b/packages/core-components/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import Box from '@material-ui/core/Box'; import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; @@ -26,6 +26,7 @@ import React, { useState, } from 'react'; import { createPortal } from 'react-dom'; + import { usePortal } from './lib/usePortal'; import { useShowCallout } from './lib/useShowCallout'; @@ -168,14 +169,14 @@ export function FeatureCalloutCircular(props: PropsWithChildren) { return ( <> -
+ {children} -
+ {createPortal( -
+ <> -
) { role="button" tabIndex={0} > -
-
-
+ + ) { {title} {description} -
+ -
, +
, portalElement, )} diff --git a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx index f128455549..c1c0bd4ec8 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx @@ -18,6 +18,8 @@ import classnames from 'classnames'; import { makeStyles } from '@material-ui/core/styles'; import LinkIcon from '@material-ui/icons/Link'; import { Link } from '../Link'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; export type IconLinkVerticalProps = { color?: 'primary' | 'secondary'; @@ -56,9 +58,8 @@ const useIconStyles = makeStyles( color: theme.palette.secondary.main, }, label: { - fontSize: '0.7rem', textTransform: 'uppercase', - fontWeight: 600, + fontWeight: theme.typography.fontWeightBold, letterSpacing: 1.2, }, }), @@ -79,10 +80,16 @@ export function IconLinkVertical({ if (disabled) { return ( -
+ {icon} - {label} -
+ + {label} + + ); } @@ -94,7 +101,9 @@ export function IconLinkVertical({ onClick={onClick} > {icon} - {label} + + {label} + ); } diff --git a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx index 4ab49596fe..c4c9dadb78 100644 --- a/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx +++ b/packages/core-components/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import Box from '@material-ui/core/Box'; import Grid from '@material-ui/core/Grid'; import IconButton from '@material-ui/core/IconButton'; import { makeStyles, Theme } from '@material-ui/core/styles'; @@ -227,7 +227,7 @@ export function HorizontalScrollGrid(props: PropsWithChildren) { }; return ( -
+ ) { > {children} -
-
) { )} -
+ ); } diff --git a/packages/core-components/src/components/Lifecycle/Lifecycle.tsx b/packages/core-components/src/components/Lifecycle/Lifecycle.tsx index f553129927..f0bbeaa7bd 100644 --- a/packages/core-components/src/components/Lifecycle/Lifecycle.tsx +++ b/packages/core-components/src/components/Lifecycle/Lifecycle.tsx @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React from 'react'; -import CSS from 'csstype'; import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import CSS from 'csstype'; +import React from 'react'; type Props = CSS.Properties & { shorthand?: boolean; @@ -47,15 +47,16 @@ export function Lifecycle(props: Props) { const classes = useStyles(props); const { shorthand, alpha } = props; return shorthand ? ( - {alpha ? <>α : <>β} - + ) : ( - + {alpha ? 'Alpha' : 'Beta'} - + ); } diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index dfa38be44e..440fcb811f 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -13,21 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - 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'; import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import classnames from 'classnames'; +import { trimEnd } from 'lodash'; import React, { ElementType } from 'react'; import { + createRoutesFromChildren, Link as RouterLink, LinkProps as RouterLinkProps, + Route, } from 'react-router-dom'; -import { trimEnd } from 'lodash'; -import { createRoutesFromChildren, Route } from 'react-router-dom'; export function isReactRouterBeta(): boolean { const [obj] = createRoutesFromChildren(} />); @@ -161,7 +162,9 @@ export const Link = React.forwardRef( className={classnames(classes.externalLink, props.className)} > {props.children} - , Opens in a new window + + , Opens in a new window + ) : ( // Interact with React Router for internal links diff --git a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx index ac66d7cdf5..59e0bea44b 100644 --- a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx +++ b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React, { useEffect, useMemo, useRef } from 'react'; -import { useLocation } from 'react-router-dom'; +import Box from '@material-ui/core/Box'; import IconButton from '@material-ui/core/IconButton'; import CopyIcon from '@material-ui/icons/FileCopy'; +import classnames from 'classnames'; +import React, { useEffect, useMemo, useRef } from 'react'; +import { useLocation } from 'react-router-dom'; import AutoSizer from 'react-virtualized-auto-sizer'; import { FixedSizeList } from 'react-window'; + import { AnsiProcessor } from './AnsiProcessor'; -import { HEADER_SIZE, useStyles } from './styles'; -import classnames from 'classnames'; import { LogLine } from './LogLine'; import { LogViewerControls } from './LogViewerControls'; +import { HEADER_SIZE, useStyles } from './styles'; import { useLogViewerSearch } from './useLogViewerSearch'; import { useLogViewerSelection } from './useLogViewerSelection'; @@ -69,10 +70,10 @@ export function RealLogViewer(props: RealLogViewerProps) { return ( {({ height, width }) => ( -
-
+ + -
+ -
+ ); }} -
+
)} ); diff --git a/packages/core-components/src/components/Progress/Progress.tsx b/packages/core-components/src/components/Progress/Progress.tsx index f199c5640f..eb17b9eb25 100644 --- a/packages/core-components/src/components/Progress/Progress.tsx +++ b/packages/core-components/src/components/Progress/Progress.tsx @@ -13,23 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React, { useState, useEffect, PropsWithChildren } from 'react'; +import { BackstageTheme } from '@backstage/theme'; +import Box from '@material-ui/core/Box'; import LinearProgress, { LinearProgressProps, } from '@material-ui/core/LinearProgress'; +import { useTheme } from '@material-ui/core/styles'; +import React, { PropsWithChildren, useEffect, useState } from 'react'; export function Progress(props: PropsWithChildren) { + const theme = useTheme(); const [isVisible, setIsVisible] = useState(false); useEffect(() => { - const handle = setTimeout(() => setIsVisible(true), 250); + const handle = setTimeout( + () => setIsVisible(true), + theme.transitions.duration.short, + ); return () => clearTimeout(handle); - }, []); + }, [theme.transitions.duration.short]); return isVisible ? ( ) : ( -
+ ); } diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 11f977b387..ca84a5f480 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -18,6 +18,7 @@ import { BackstagePalette, BackstageTheme } from '@backstage/theme'; import { makeStyles, useTheme } from '@material-ui/core/styles'; import { Circle } from 'rc-progress'; import React, { ReactNode, useEffect, useState } from 'react'; +import Box from '@material-ui/core/Box'; /** @public */ export type GaugeClassKey = @@ -38,8 +39,8 @@ const useStyles = makeStyles( top: '50%', left: '50%', transform: 'translate(-50%, -60%)', - fontSize: 45, - fontWeight: 'bold', + fontSize: theme.typography.pxToRem(45), + fontWeight: theme.typography.fontWeightBold, color: theme.palette.textContrast, }, description: { @@ -152,7 +153,7 @@ export function Gauge(props: GaugeProps) { }, [description, hoverRef]); return ( -
+ {description && isHovering ? ( -
{description}
+ {description} ) : ( -
+ {isNaN(value) ? 'N/A' : `${asActual}${unit}`} -
+
)} -
+
); } diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 1114890523..3442395380 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import Box from '@material-ui/core/Box'; import { makeStyles } from '@material-ui/core/styles'; import React, { ReactNode } from 'react'; + import { BottomLinkProps } from '../../layout/BottomLink'; import { InfoCard, InfoCardVariants } from '../../layout/InfoCard'; import { Gauge, GaugePropsGetColor } from './Gauge'; @@ -74,7 +75,7 @@ export function GaugeCard(props: Props) { }; return ( -
+ -
+ ); } diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx index 643c3ec2ac..5bfe1b7a61 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import React from 'react'; +import { BackstageTheme } from '@backstage/theme'; import { useTheme } from '@material-ui/core/styles'; import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; import { Line } from 'rc-progress'; -import { BackstageTheme } from '@backstage/theme'; -import { getProgressColor, GaugePropsGetColor } from './Gauge'; +import React from 'react'; + +import { GaugePropsGetColor, getProgressColor } from './Gauge'; type Props = { /** @@ -47,14 +48,14 @@ export function LinearGauge(props: Props) { }); return ( - + - + ); } diff --git a/packages/core-components/src/components/Select/Select.tsx b/packages/core-components/src/components/Select/Select.tsx index 7229f89653..9cb8822a05 100644 --- a/packages/core-components/src/components/Select/Select.tsx +++ b/packages/core-components/src/components/Select/Select.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import Box from '@material-ui/core/Box'; import Checkbox from '@material-ui/core/Checkbox'; import Chip from '@material-ui/core/Chip'; import ClickAwayListener from '@material-ui/core/ClickAwayListener'; @@ -30,6 +30,7 @@ import { } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import React, { useEffect, useState } from 'react'; + import ClosedDropdown from './static/ClosedDropdown'; import OpenedDropdown from './static/OpenedDropdown'; @@ -45,17 +46,17 @@ const BootstrapInput = withStyles( }, }, input: { - borderRadius: 4, + borderRadius: theme.shape.borderRadius, position: 'relative', backgroundColor: theme.palette.background.paper, border: '1px solid #ced4da', - fontSize: 16, - padding: '10px 26px 10px 12px', + fontSize: theme.typography.body1.fontSize, + padding: theme.spacing(1.25, 3.25, 1.25, 1.5), transition: theme.transitions.create(['border-color', 'box-shadow']), fontFamily: 'Helvetica Neue', '&:focus': { background: theme.palette.background.paper, - borderRadius: 4, + borderRadius: theme.shape.borderRadius, }, }, }), @@ -81,7 +82,7 @@ const useStyles = makeStyles( label: { transform: 'initial', fontWeight: 'bold', - fontSize: 14, + fontSize: theme.typography.body2.fontSize, fontFamily: theme.typography.fontFamily, color: theme.palette.text.primary, '&.Mui-focused': { @@ -91,7 +92,7 @@ const useStyles = makeStyles( formLabel: { transform: 'initial', fontWeight: 'bold', - fontSize: 14, + fontSize: theme.typography.body2.fontSize, fontFamily: theme.typography.fontFamily, color: theme.palette.text.primary, '&.Mui-focused': { @@ -196,7 +197,7 @@ export function SelectComponent(props: SelectProps) { }; return ( -
+ {label} @@ -217,7 +218,7 @@ export function SelectComponent(props: SelectProps) { tabIndex={0} renderValue={s => multiple && (value as any[]).length !== 0 ? ( -
+ {(s as string[]).map(selectedValue => ( el.value === selectedValue)?.value} @@ -229,7 +230,7 @@ export function SelectComponent(props: SelectProps) { className={classes.chip} /> ))} -
+
) : ( {(value as any[]).length === 0 @@ -279,6 +280,6 @@ export function SelectComponent(props: SelectProps) { -
+ ); } diff --git a/packages/core-components/src/components/Select/static/ClosedDropdown.tsx b/packages/core-components/src/components/Select/static/ClosedDropdown.tsx index 7b0516d017..812afe34eb 100644 --- a/packages/core-components/src/components/Select/static/ClosedDropdown.tsx +++ b/packages/core-components/src/components/Select/static/ClosedDropdown.tsx @@ -21,11 +21,11 @@ import SvgIcon from '@material-ui/core/SvgIcon'; export type ClosedDropdownClassKey = 'icon'; const useStyles = makeStyles( - () => + theme => createStyles({ icon: { position: 'absolute', - right: '4px', + right: theme.spacing(0.5), pointerEvents: 'none', }, }), diff --git a/packages/core-components/src/components/Select/static/OpenedDropdown.tsx b/packages/core-components/src/components/Select/static/OpenedDropdown.tsx index 617288ded6..b87a00c26a 100644 --- a/packages/core-components/src/components/Select/static/OpenedDropdown.tsx +++ b/packages/core-components/src/components/Select/static/OpenedDropdown.tsx @@ -20,11 +20,11 @@ import SvgIcon from '@material-ui/core/SvgIcon'; export type OpenedDropdownClassKey = 'icon'; const useStyles = makeStyles( - () => + theme => createStyles({ icon: { position: 'absolute', - right: '4px', + right: theme.spacing(0.5), pointerEvents: 'none', }, }), diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx index 0f7d604eb0..224b66533a 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useContext, ReactNode, PropsWithChildren } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; +import Box from '@material-ui/core/Box'; import Button from '@material-ui/core/Button'; -import { StepActions } from './types'; +import { makeStyles } from '@material-ui/core/styles'; +import React, { PropsWithChildren, ReactNode, useContext } from 'react'; + import { VerticalStepperContext } from './SimpleStepper'; +import { StepActions } from './types'; export type SimpleStepperFooterClassKey = 'root'; @@ -145,7 +147,7 @@ export const SimpleStepperFooter = ({ }; return ( -
+ {[undefined, true].includes(actions.showBack) && stepIndex !== 0 && ( )} {children} -
+ ); }; diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx index bb46163105..911ad2e73e 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepperStep.tsx @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { PropsWithChildren } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; +import Box from '@material-ui/core/Box'; import MuiStep from '@material-ui/core/Step'; import StepContent from '@material-ui/core/StepContent'; import StepLabel from '@material-ui/core/StepLabel'; +import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; +import React, { PropsWithChildren } from 'react'; + import { SimpleStepperFooter } from './SimpleStepperFooter'; import { StepProps } from './types'; @@ -40,11 +42,11 @@ export function SimpleStepperStep(props: PropsWithChildren) { // The end step is not a part of the stepper // It simply is the final screen with an option to have buttons such as reset or back return end ? ( -
+ {title} {children} -
+ ) : ( diff --git a/packages/core-components/src/components/Status/Status.tsx b/packages/core-components/src/components/Status/Status.tsx index a855fe3ebe..364193e383 100644 --- a/packages/core-components/src/components/Status/Status.tsx +++ b/packages/core-components/src/components/Status/Status.tsx @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { makeStyles } from '@material-ui/core/styles'; import { BackstageTheme } from '@backstage/theme'; +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; import classNames from 'classnames'; import React, { PropsWithChildren } from 'react'; @@ -31,12 +31,12 @@ export type StatusClassKey = const useStyles = makeStyles( theme => ({ status: { - fontWeight: 500, + fontWeight: theme.typography.fontWeightMedium, '&::before': { width: '0.7em', height: '0.7em', display: 'inline-block', - marginRight: 8, + marginRight: theme.spacing(1), borderRadius: '50%', content: '""', }, @@ -78,7 +78,8 @@ const useStyles = makeStyles( export function StatusOK(props: PropsWithChildren<{}>) { const classes = useStyles(props); return ( -