diff --git a/.changeset/eighty-stingrays-type.md b/.changeset/eighty-stingrays-type.md new file mode 100644 index 0000000000..731c401b77 --- /dev/null +++ b/.changeset/eighty-stingrays-type.md @@ -0,0 +1,5 @@ +--- +'@backstage/core': patch +--- + +Add forwardRef to the SidebarItem diff --git a/.changeset/weak-ducks-tan.md b/.changeset/weak-ducks-tan.md new file mode 100644 index 0000000000..64b1c3f117 --- /dev/null +++ b/.changeset/weak-ducks-tan.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-common': minor +'example-backend': patch +'@backstage/cli': patch +'@backstage/create-app': patch +--- + +Change loadBackendConfig to return the config directly diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 490070f80a..c86793fc08 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -15,12 +15,18 @@ */ import { findPaths } from '@backstage/cli-common'; +import { Config, ConfigReader } from '@backstage/config'; import { loadConfig } from '@backstage/config-loader'; +import { Logger } from 'winston'; + +type Options = { + logger: Logger; +}; /** * Load configuration for a Backend */ -export async function loadBackendConfig() { +export async function loadBackendConfig(options: Options): Promise { /* eslint-disable-next-line no-restricted-syntax */ const paths = findPaths(__dirname); const configs = await loadConfig({ @@ -28,5 +34,10 @@ export async function loadBackendConfig() { rootPaths: [paths.targetRoot, paths.targetDir], shouldReadSecrets: true, }); - return configs; + + options.logger.info( + `Loaded config from ${configs.map(c => c.context).join(', ')}`, + ); + + return ConfigReader.fromConfigs(configs); } diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index 5ff7ed4271..710de6f1d7 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ConfigReader } from '@backstage/config'; +import { Config } from '@backstage/config'; import compression from 'compression'; import cors from 'cors'; import express, { Router } from 'express'; @@ -77,7 +77,7 @@ export class ServiceBuilderImpl implements ServiceBuilder { this.module = moduleRef; } - loadConfig(config: ConfigReader): ServiceBuilder { + loadConfig(config: Config): ServiceBuilder { const backendConfig = config.getOptionalConfig('backend'); if (!backendConfig) { return this; diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 18fd44f089..cbeaf4315c 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -33,7 +33,7 @@ import { SingleHostDiscovery, UrlReaders, } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; +import { Config } from '@backstage/config'; import healthcheck from './plugins/healthcheck'; import auth from './plugins/auth'; import catalog from './plugins/catalog'; @@ -47,7 +47,7 @@ import graphql from './plugins/graphql'; import app from './plugins/app'; import { PluginEnvironment } from './types'; -function makeCreateEnv(config: ConfigReader) { +function makeCreateEnv(config: Config) { const root = getRootLogger(); const reader = UrlReaders.default({ logger: root, config }); const discovery = SingleHostDiscovery.fromConfig(config); @@ -64,9 +64,8 @@ function makeCreateEnv(config: ConfigReader) { } async function main() { - const configs = await loadBackendConfig(); - const configReader = ConfigReader.fromConfigs(configs); - const createEnv = makeCreateEnv(configReader); + const config = await loadBackendConfig({ logger: getRootLogger() }); + const createEnv = makeCreateEnv(config); const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck')); const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); @@ -93,7 +92,7 @@ async function main() { apiRouter.use(notFoundHandler()); const service = createServiceBuilder(module) - .loadConfig(configReader) + .loadConfig(config) .addRouter('', await healthcheck(healthcheckEnv)) .addRouter('/api', apiRouter) .addRouter('', await app(appEnv)); diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index ae67616515..5a9d2cdf3a 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -26,6 +26,11 @@ export default async (cmd: Command) => { env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production', rootPaths: [paths.targetRoot, paths.targetDir], }); + + console.log( + `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, + ); + await buildBundle({ entry: 'src/index', parallel: parseParallel(process.env[PARALLEL_ENV_VAR]), diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index 031214c264..6524eb3acf 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -25,6 +25,11 @@ export default async (cmd: Command) => { env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development', rootPaths: [paths.targetRoot, paths.targetDir], }); + + console.log( + `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, + ); + const waitForExit = await serveBundle({ entry: 'src/index', checksEnabled: cmd.check, diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts index ca76f19da3..d7b5ca2924 100644 --- a/packages/cli/src/commands/backend/dev.ts +++ b/packages/cli/src/commands/backend/dev.ts @@ -26,6 +26,10 @@ export default async (cmd: Command) => { rootPaths: [paths.targetRoot, paths.targetDir], }); + console.log( + `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, + ); + const waitForExit = await serveBackend({ entry: 'src/index', checksEnabled: cmd.check, diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 526f4d70b1..693724e4d9 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -28,6 +28,10 @@ export default async (cmd: Command) => { rootPaths: [paths.targetRoot, paths.targetDir], }); + console.log( + `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, + ); + const flatConfig = ConfigReader.fromConfigs(appConfigs).get(); if (cmd.format === 'json') { diff --git a/packages/cli/src/commands/plugin/export.ts b/packages/cli/src/commands/plugin/export.ts index 4fe9cb3c03..39865f3d32 100644 --- a/packages/cli/src/commands/plugin/export.ts +++ b/packages/cli/src/commands/plugin/export.ts @@ -25,6 +25,11 @@ export default async (cmd: Command) => { env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production', rootPaths: [paths.targetRoot, paths.targetDir], }); + + console.log( + `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, + ); + await buildBundle({ entry: 'dev/index', statsJsonEnabled: cmd.stats, diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index bae63f2fda..7c92c8f312 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -25,6 +25,11 @@ export default async (cmd: Command) => { env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development', rootPaths: [paths.targetRoot, paths.targetDir], }); + + console.log( + `Loaded config from ${appConfigs.map(c => c.context).join(', ')}`, + ); + const waitForExit = await serveBundle({ entry: 'dev/index', checksEnabled: cmd.check, diff --git a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts index a5e2af5974..6433cb6116 100644 --- a/packages/cli/src/commands/remove-plugin/removePlugin.test.ts +++ b/packages/cli/src/commands/remove-plugin/removePlugin.test.ts @@ -114,7 +114,7 @@ describe('removePlugin', () => { it('removes plugin exports from /packages/app/src/package.json', async () => { const testFilePath = path.join(tempDir, 'test.ts'); const pluginsFilePaths = path.join(appPath, 'src', 'plugins.ts'); - createTestPluginFile(testFilePath, pluginsFilePaths); + await createTestPluginFile(testFilePath, pluginsFilePaths); try { await removeReferencesFromPluginsFile(testFilePath, testPluginName); const testFileContent = removeEmptyLines( diff --git a/packages/core/src/layout/Sidebar/Items.tsx b/packages/core/src/layout/Sidebar/Items.tsx index 2eca89f873..bb47701280 100644 --- a/packages/core/src/layout/Sidebar/Items.tsx +++ b/packages/core/src/layout/Sidebar/Items.tsx @@ -25,7 +25,14 @@ import { BackstageTheme } from '@backstage/theme'; import { IconComponent } from '@backstage/core-api'; import SearchIcon from '@material-ui/icons/Search'; import clsx from 'clsx'; -import React, { FC, useContext, useState, KeyboardEventHandler } from 'react'; +import React, { + FC, + useContext, + useState, + KeyboardEventHandler, + forwardRef, + ReactNode, +} from 'react'; import { NavLink } from 'react-router-dom'; import { sidebarConfig, SidebarContext } from './config'; @@ -115,74 +122,93 @@ type SidebarItemProps = { to?: string; hasNotifications?: boolean; onClick?: () => void; + children?: ReactNode; }; -export const SidebarItem: FC = ({ - icon: Icon, - text, - to, - hasNotifications = false, - onClick, - children, -}) => { - const classes = useStyles(); - // XXX (@koroeskohr): unsure this is optimal. But I just really didn't want to have the item component - // depend on the current location, and at least have it being optionally forced to selected. - // Still waiting on a Q answered to fine tune the implementation - const { isOpen } = useContext(SidebarContext); +export const SidebarItem = forwardRef( + ( + { icon: Icon, text, to, hasNotifications = false, onClick, children }, + ref, + ) => { + const classes = useStyles(); + // XXX (@koroeskohr): unsure this is optimal. But I just really didn't want to have the item component + // depend on the current location, and at least have it being optionally forced to selected. + // Still waiting on a Q answered to fine tune the implementation + const { isOpen } = useContext(SidebarContext); - const itemIcon = ( - - - - ); + const itemIcon = ( + + + + ); - const childProps = { - onClick, - className: clsx(classes.root, isOpen ? classes.open : classes.closed), - }; + const childProps = { + onClick, + className: clsx(classes.root, isOpen ? classes.open : classes.closed), + }; + + if (!isOpen) { + if (to === undefined) { + return ( +
+ {itemIcon} +
+ ); + } + + return ( + + {itemIcon} + + ); + } + + const content = ( + <> +
+ {itemIcon} +
+ {text && ( + + {text} + + )} +
{children}
+ + ); - if (!isOpen) { if (to === undefined) { - return
{itemIcon}
; + return ( +
+ {content} +
+ ); } return ( - - {itemIcon} + + {content} ); - } - - const content = ( - <> -
- {itemIcon} -
- {text && ( - - {text} - - )} -
{children}
- - ); - - if (to === undefined) { - return
{content}
; - } - - return ( - - {content} - - ); -}; + }, +); type SidebarSearchFieldProps = { onSearch: (input: string) => void; diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index d58ebe4e42..18b6cfa647 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -17,7 +17,7 @@ import { SingleHostDiscovery, UrlReaders, } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; +import { Config } from '@backstage/config'; import auth from './plugins/auth'; import catalog from './plugins/catalog'; import scaffolder from './plugins/scaffolder'; @@ -25,7 +25,7 @@ import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; import { PluginEnvironment } from './types'; -function makeCreateEnv(config: ConfigReader) { +function makeCreateEnv(config: Config) { const root = getRootLogger(); const reader = UrlReaders.default({ logger: root, config }); const discovery = SingleHostDiscovery.fromConfig(config); @@ -42,9 +42,8 @@ function makeCreateEnv(config: ConfigReader) { } async function main() { - const configs = await loadBackendConfig(); - const configReader = ConfigReader.fromConfigs(configs); - const createEnv = makeCreateEnv(configReader); + const config = await loadBackendConfig({logger: getRootLogger()}); + const createEnv = makeCreateEnv(config); const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder')); @@ -61,7 +60,7 @@ async function main() { apiRouter.use(notFoundHandler()); const service = createServiceBuilder(module) - .loadConfig(configReader) + .loadConfig(config) .addRouter('/api', apiRouter) await service.start().catch(err => { diff --git a/plugins/auth-backend/src/service/standaloneServer.ts b/plugins/auth-backend/src/service/standaloneServer.ts index d00e7d523d..a3c64c6e8e 100644 --- a/plugins/auth-backend/src/service/standaloneServer.ts +++ b/plugins/auth-backend/src/service/standaloneServer.ts @@ -17,7 +17,6 @@ import Knex from 'knex'; import { Server } from 'http'; import { Logger } from 'winston'; -import { ConfigReader } from '@backstage/config'; import { createRouter } from './router'; import { createServiceBuilder, @@ -34,7 +33,7 @@ export async function startStandaloneServer( options: ServerOptions, ): Promise { const logger = options.logger.child({ service: 'auth-backend' }); - const config = ConfigReader.fromConfigs(await loadBackendConfig()); + const config = await loadBackendConfig({ logger }); const discovery = SingleHostDiscovery.fromConfig(config); const database = useHotMemoize(module, () => { diff --git a/plugins/catalog-backend/src/service/standaloneServer.ts b/plugins/catalog-backend/src/service/standaloneServer.ts index 713a5783ba..23f9795036 100644 --- a/plugins/catalog-backend/src/service/standaloneServer.ts +++ b/plugins/catalog-backend/src/service/standaloneServer.ts @@ -20,7 +20,6 @@ import { UrlReaders, useHotMemoize, } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; import { Server } from 'http'; import { Logger } from 'winston'; import { DatabaseManager } from '../database'; @@ -37,7 +36,7 @@ export async function startStandaloneServer( options: ServerOptions, ): Promise { const logger = options.logger.child({ service: 'catalog-backend' }); - const config = ConfigReader.fromConfigs(await loadBackendConfig()); + const config = await loadBackendConfig({ logger }); const reader = UrlReaders.default({ logger, config }); const db = useHotMemoize(module, () => DatabaseManager.createInMemoryDatabaseConnection(), diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index 012adaed93..42e723e54a 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -16,7 +16,6 @@ import { buildMiddleware, createRouter } from './router'; import * as winston from 'winston'; -import { ConfigReader } from '@backstage/config'; import { loadBackendConfig, SingleHostDiscovery, @@ -42,7 +41,7 @@ const mockCreateProxyMiddleware = createProxyMiddleware as jest.MockedFunction< describe('createRouter', () => { it('works', async () => { const logger = winston.createLogger(); - const config = ConfigReader.fromConfigs(await loadBackendConfig()); + const config = await loadBackendConfig({ logger }); const discovery = SingleHostDiscovery.fromConfig(config); const router = await createRouter({ config, diff --git a/plugins/proxy-backend/src/service/standaloneServer.ts b/plugins/proxy-backend/src/service/standaloneServer.ts index 83980729d8..160ed1db86 100644 --- a/plugins/proxy-backend/src/service/standaloneServer.ts +++ b/plugins/proxy-backend/src/service/standaloneServer.ts @@ -22,7 +22,6 @@ import { import { Server } from 'http'; import { Logger } from 'winston'; import { createRouter } from './router'; -import { ConfigReader } from '@backstage/config'; export interface ServerOptions { port: number; @@ -37,7 +36,7 @@ export async function startStandaloneServer( logger.debug('Creating application...'); - const config = ConfigReader.fromConfigs(await loadBackendConfig()); + const config = await loadBackendConfig({ logger }); const discovery = SingleHostDiscovery.fromConfig(config); const router = await createRouter({ config, diff --git a/plugins/rollbar-backend/src/service/standaloneServer.ts b/plugins/rollbar-backend/src/service/standaloneServer.ts index a96d30d836..1be2599724 100644 --- a/plugins/rollbar-backend/src/service/standaloneServer.ts +++ b/plugins/rollbar-backend/src/service/standaloneServer.ts @@ -20,7 +20,6 @@ import { createServiceBuilder, loadBackendConfig, } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; import { createRouter } from './router'; export interface ServerOptions { @@ -33,7 +32,7 @@ export async function startStandaloneServer( options: ServerOptions, ): Promise { const logger = options.logger.child({ service: 'rollbar-backend' }); - const config = ConfigReader.fromConfigs(await loadBackendConfig()); + const config = await loadBackendConfig({ logger }); logger.debug('Creating application...'); diff --git a/plugins/techdocs-backend/src/default-branch.ts b/plugins/techdocs-backend/src/default-branch.ts index ac6931396c..6ed12348ac 100644 --- a/plugins/techdocs-backend/src/default-branch.ts +++ b/plugins/techdocs-backend/src/default-branch.ts @@ -15,8 +15,8 @@ */ import fetch from 'cross-fetch'; import parseGitUrl from 'git-url-parse'; -import { ConfigReader, Config } from '@backstage/config'; -import { loadBackendConfig } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import { getRootLogger, loadBackendConfig } from '@backstage/backend-common'; interface IGitlabBranch { name: string; @@ -230,7 +230,8 @@ async function getAzureDefaultBranch( export const getDefaultBranch = async ( repositoryUrl: string, ): Promise => { - const config = ConfigReader.fromConfigs(await loadBackendConfig()); + // TODO(Rugvip): Config should not be loaded here, pass it in instead + const config = await loadBackendConfig({ logger: getRootLogger() }); const typeMapping = [ { url: /github*/g, type: 'github' }, { url: /gitlab*/g, type: 'gitlab' },