diff --git a/.changeset/beige-moose-look.md b/.changeset/beige-moose-look.md new file mode 100644 index 0000000000..1676de210a --- /dev/null +++ b/.changeset/beige-moose-look.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Only serve static assets if there is a public folder during `app:serve` and `plugin:serve`. This fixes a common bug that would break `plugin:serve` with an `EBUSY` error. diff --git a/.changeset/breezy-foxes-wonder.md b/.changeset/breezy-foxes-wonder.md new file mode 100644 index 0000000000..57b5c52892 --- /dev/null +++ b/.changeset/breezy-foxes-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +When issuing a `full` update from an entity provider, entities with updates are now properly persisted. diff --git a/.changeset/good-cars-confess.md b/.changeset/good-cars-confess.md new file mode 100644 index 0000000000..12ec838544 --- /dev/null +++ b/.changeset/good-cars-confess.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Configuration schema is now also collected from the root `package.json` if it exists. diff --git a/.changeset/great-humans-return.md b/.changeset/great-humans-return.md new file mode 100644 index 0000000000..dd1cda0f5a --- /dev/null +++ b/.changeset/great-humans-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Enables late registration of plugins into the application by updating ApiHolder when additional plugins have been added in. diff --git a/.changeset/healthy-cycles-exercise.md b/.changeset/healthy-cycles-exercise.md new file mode 100644 index 0000000000..cd0e73a100 --- /dev/null +++ b/.changeset/healthy-cycles-exercise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fixed the URL for the "Click to copy documentation link to clipboard" action diff --git a/.changeset/late-kiwis-double.md b/.changeset/late-kiwis-double.md new file mode 100644 index 0000000000..b1ec53546e --- /dev/null +++ b/.changeset/late-kiwis-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': patch +--- + +Add option to collect configuration schemas from explicit package paths in addition to by package name. diff --git a/.changeset/late-news-chew.md b/.changeset/late-news-chew.md new file mode 100644 index 0000000000..5552caf8fb --- /dev/null +++ b/.changeset/late-news-chew.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Added `SearchBar` to allow filtering and a scroll bar to display hidden tech diff --git a/.changeset/little-cobras-think.md b/.changeset/little-cobras-think.md new file mode 100644 index 0000000000..e90c7e979c --- /dev/null +++ b/.changeset/little-cobras-think.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Improved ´plugin:diff´ check for the `package.json` `"files"` field. diff --git a/.changeset/red-hats-walk.md b/.changeset/red-hats-walk.md new file mode 100644 index 0000000000..2d6eb06666 --- /dev/null +++ b/.changeset/red-hats-walk.md @@ -0,0 +1,8 @@ +--- +'@backstage/core-components': minor +--- + +Checkbox tree filters are no longer available in the Table component: + +- Deleted the `CheckboxTree` component +- Removed the filter type `'checkbox-tree'` from the `TableFilter` types. diff --git a/.changeset/twenty-chairs-argue.md b/.changeset/twenty-chairs-argue.md new file mode 100644 index 0000000000..c4a0acedbe --- /dev/null +++ b/.changeset/twenty-chairs-argue.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +Add dashboard support for Rancher diff --git a/contrib/docker/frontend-with-nginx/docker/inject-config.sh b/contrib/docker/frontend-with-nginx/docker/inject-config.sh index a0cea96ff3..98797b65d3 100755 --- a/contrib/docker/frontend-with-nginx/docker/inject-config.sh +++ b/contrib/docker/frontend-with-nginx/docker/inject-config.sh @@ -23,7 +23,7 @@ function inject_config() { # escape ' and " twice, for both sed and json local config_escaped_1 - config_escaped_1="$(echo "$config" | jq -cM . | sed -e 's/[\\"\x27]/\\&/g')" # \x27 = ' + config_escaped_1="$(echo "$config" | jq -cM . | sed -e 's/[\\"'\'']/\\&/g')" # escape / and & for sed local config_escaped_2 config_escaped_2="$(echo "$config_escaped_1" | sed -e 's/[\/&]/\\&/g')" diff --git a/docs/conf/defining.md b/docs/conf/defining.md index f13beb9f77..6462f4f60a 100644 --- a/docs/conf/defining.md +++ b/docs/conf/defining.md @@ -13,9 +13,10 @@ which during validation is stitched together into a single schema. ## Schema Collection and Definition Schemas are collected from all packages and dependencies in each repo that are a -part of the Backstage ecosystem, including transitive dependencies. The current -definition of "part of the ecosystem" is that a package has at least one -dependency in the `@backstage` namespace, but this is subject to change. +part of the Backstage ecosystem, including the root package and transitive +dependencies. The current definition of "part of the ecosystem" is that a +package has at least one dependency in the `@backstage` namespace or a +`"configSchema"` field in `package.json`, but this is subject to change. Each package is searched for a schema at a single point of entry, a top-level `"configSchema"` field in `package.json`. The field can either contain an diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 8520164f91..70e65cb221 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -31,7 +31,6 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.3", - "@backstage/config": "^0.1.10", "@backstage/errors": "^0.1.2", "cross-fetch": "^3.0.6" }, diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 0bea531816..e648606f48 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -42,7 +42,6 @@ }, "devDependencies": { "@backstage/cli": "^0.7.13", - "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", "yaml": "^1.9.2" diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 2f8e3125c1..e580cf4985 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -50,10 +50,12 @@ export async function serveBundle(options: ServeOptions) { publicPath: config.output?.publicPath as string, stats: 'errors-warnings', }, - static: { - publicPath: config.output?.publicPath as string, - directory: paths.targetPublic ?? '/', - }, + static: paths.targetPublic + ? { + publicPath: config.output?.publicPath as string, + directory: paths.targetPublic, + } + : undefined, historyApiFallback: { // Paths with dots should still use the history fallback. // See https://github.com/facebookincubator/create-react-app/issues/387. diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index a93f040122..3688d60089 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -39,6 +39,8 @@ export async function loadCliConfig(options: Options) { const schema = await loadConfigSchema({ dependencies: localPackageNames, + // Include the package.json in the project root if it exists + packagePaths: [paths.resolveTargetRoot('package.json')], }); const appConfigs = await loadConfig({ diff --git a/packages/cli/src/lib/diff/handlers.ts b/packages/cli/src/lib/diff/handlers.ts index 4a17125e5a..f8bbfa09ce 100644 --- a/packages/cli/src/lib/diff/handlers.ts +++ b/packages/cli/src/lib/diff/handlers.ts @@ -85,6 +85,7 @@ class PackageJsonHandler { targetObj: any = this.targetPkg, prefix?: string, sort?: boolean, + optional?: boolean, ) { const fullFieldName = chalk.cyan( prefix ? `${prefix}[${fieldName}]` : fieldName, @@ -107,7 +108,7 @@ class PackageJsonHandler { } await this.write(); } - } else if (fieldName in obj) { + } else if (fieldName in obj && optional !== true) { if ( await this.prompt( `package.json is missing field ${fullFieldName}, set to ${coloredNewValue}?`, @@ -123,11 +124,41 @@ class PackageJsonHandler { } private async syncFiles() { - if (typeof this.targetPkg.configSchema === 'string') { - const files = [...this.pkg.files, this.targetPkg.configSchema]; - await this.syncField('files', { files }); + const { configSchema } = this.targetPkg; + const hasSchemaFile = typeof configSchema === 'string'; + + if (!this.targetPkg.files) { + const expected = hasSchemaFile ? ['dist', configSchema] : ['dist']; + if ( + await this.prompt( + `package.json is missing field "files", set to ${JSON.stringify( + expected, + )}?`, + ) + ) { + this.targetPkg.files = expected; + await this.write(); + } } else { - await this.syncField('files'); + const missing = []; + if (!this.targetPkg.files.includes('dist')) { + missing.push('dist'); + } + if (hasSchemaFile && !this.targetPkg.files.includes(configSchema)) { + missing.push(configSchema); + } + if (missing.length) { + if ( + await this.prompt( + `package.json is missing ${JSON.stringify( + missing, + )} in the "files" field, add?`, + ) + ) { + this.targetPkg.files.push(...missing); + await this.write(); + } + } } } @@ -200,7 +231,7 @@ class PackageJsonHandler { continue; } - await this.syncField(key, pkgDeps, targetDeps, fieldName, true); + await this.syncField(key, pkgDeps, targetDeps, fieldName, true, true); } } diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index bb3ae50d23..355839c5af 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -53,6 +53,7 @@ export function loadConfigSchema( export type LoadConfigSchemaOptions = | { dependencies: string[]; + packagePaths?: string[]; } | { serialized: JsonObject; diff --git a/packages/config-loader/src/lib/schema/collect.test.ts b/packages/config-loader/src/lib/schema/collect.test.ts index ef9d38faa9..3600991717 100644 --- a/packages/config-loader/src/lib/schema/collect.test.ts +++ b/packages/config-loader/src/lib/schema/collect.test.ts @@ -49,7 +49,7 @@ describe('collectConfigSchemas', () => { }), }); - await expect(collectConfigSchemas([])).resolves.toEqual([]); + await expect(collectConfigSchemas([], [])).resolves.toEqual([]); }); it('should find schema in a local package', async () => { @@ -64,7 +64,7 @@ describe('collectConfigSchemas', () => { }, }); - await expect(collectConfigSchemas(['a'])).resolves.toEqual([ + await expect(collectConfigSchemas(['a'], [])).resolves.toEqual([ { path: path.join('node_modules', 'a', 'package.json'), value: mockSchema, @@ -72,8 +72,34 @@ describe('collectConfigSchemas', () => { ]); }); - it('should find schema in transitive dependencies', async () => { + it('should find schema at explicit package path', async () => { mockFs({ + root: { + 'package.json': JSON.stringify({ + name: 'root', + configSchema: mockSchema, + }), + }, + }); + + await expect( + collectConfigSchemas([], [path.join('root', 'package.json')]), + ).resolves.toEqual([ + { + path: path.join('root', 'package.json'), + value: mockSchema, + }, + ]); + }); + + it('should find schema in transitive dependencies and explicit path', async () => { + mockFs({ + root: { + 'package.json': JSON.stringify({ + name: 'root', + configSchema: { ...mockSchema, title: 'root' }, + }), + }, node_modules: { a: { 'package.json': JSON.stringify({ @@ -124,20 +150,28 @@ describe('collectConfigSchemas', () => { }, }); - await expect(collectConfigSchemas(['a'])).resolves.toEqual([ - { - path: path.join('node_modules', 'b', 'package.json'), - value: { ...mockSchema, title: 'b' }, - }, - { - path: path.join('node_modules', 'c1', 'package.json'), - value: { ...mockSchema, title: 'c1' }, - }, - { - path: path.join('node_modules', 'd1', 'package.json'), - value: { ...mockSchema, title: 'd1' }, - }, - ]); + await expect( + collectConfigSchemas(['a'], [path.join('root', 'package.json')]), + ).resolves.toEqual( + expect.arrayContaining([ + { + path: path.join('node_modules', 'b', 'package.json'), + value: { ...mockSchema, title: 'b' }, + }, + { + path: path.join('node_modules', 'c1', 'package.json'), + value: { ...mockSchema, title: 'c1' }, + }, + { + path: path.join('node_modules', 'd1', 'package.json'), + value: { ...mockSchema, title: 'd1' }, + }, + { + path: path.join('root', 'package.json'), + value: { ...mockSchema, title: 'root' }, + }, + ]), + ); }); it('should schema of different types', async () => { @@ -171,7 +205,7 @@ describe('collectConfigSchemas', () => { [typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir), }); - await expect(collectConfigSchemas(['a', 'b', 'c'])).resolves.toEqual([ + await expect(collectConfigSchemas(['a', 'b', 'c'], [])).resolves.toEqual([ { path: path.join('node_modules', 'a', 'package.json'), value: { ...mockSchema, title: 'inline' }, @@ -210,7 +244,7 @@ describe('collectConfigSchemas', () => { }, }); - await expect(collectConfigSchemas(['a'])).rejects.toThrow( + await expect(collectConfigSchemas(['a'], [])).rejects.toThrow( 'Config schema files must be .json or .d.ts, got schema.yaml', ); }); @@ -230,7 +264,7 @@ describe('collectConfigSchemas', () => { [typescriptModuleDir]: (mockFs as any).load(typescriptModuleDir), }); - await expect(collectConfigSchemas(['a'])).rejects.toThrow( + await expect(collectConfigSchemas(['a'], [])).rejects.toThrow( `Invalid schema in ${path.join( 'node_modules', 'a', diff --git a/packages/config-loader/src/lib/schema/collect.ts b/packages/config-loader/src/lib/schema/collect.ts index b363e8e5f3..81a28e9d7b 100644 --- a/packages/config-loader/src/lib/schema/collect.ts +++ b/packages/config-loader/src/lib/schema/collect.ts @@ -26,8 +26,9 @@ import { getProgramFromFiles, generateSchema } from 'typescript-json-schema'; import { JsonObject } from '@backstage/config'; type Item = { - name: string; + name?: string; parentPath?: string; + packagePath?: string; }; const req = @@ -40,34 +41,47 @@ const req = */ export async function collectConfigSchemas( packageNames: string[], + packagePaths: string[], ): Promise { const visitedPackages = new Set(); const schemas = Array(); const tsSchemaPaths = Array(); const currentDir = await fs.realpath(process.cwd()); - async function processItem({ name, parentPath }: Item) { - // Ensures that we only process each package once. We don't bother with - // loading in schemas from duplicates of different versions, as that's not - // supported by Backstage right now anyway. We may want to change that in - // the future though, if it for example becomes possible to load in two - // different versions of e.g. @backstage/core at once. - if (visitedPackages.has(name)) { - return; - } - visitedPackages.add(name); + async function processItem(item: Item) { + let pkgPath = item.packagePath; - let pkgPath: string; - try { - pkgPath = req.resolve( - `${name}/package.json`, - parentPath && { - paths: [parentPath], - }, - ); - } catch { - // We can somewhat safely ignore packages that don't export package.json, - // as they are likely not part of the Backstage ecosystem anyway. + if (pkgPath) { + const pkgExists = await fs.pathExists(pkgPath); + if (!pkgExists) { + return; + } + } else if (item.name) { + const { name, parentPath } = item; + + // Ensures that we only process each package once. We don't bother with + // loading in schemas from duplicates of different versions, as that's not + // supported by Backstage right now anyway. We may want to change that in + // the future though, if it for example becomes possible to load in two + // different versions of e.g. @backstage/core at once. + if (visitedPackages.has(name)) { + return; + } + visitedPackages.add(name); + + try { + pkgPath = req.resolve( + `${name}/package.json`, + parentPath && { + paths: [parentPath], + }, + ); + } catch { + // We can somewhat safely ignore packages that don't export package.json, + // as they are likely not part of the Backstage ecosystem anyway. + } + } + if (!pkgPath) { return; } @@ -126,9 +140,10 @@ export async function collectConfigSchemas( ); } - await Promise.all( - packageNames.map(name => processItem({ name, parentPath: currentDir })), - ); + await Promise.all([ + ...packageNames.map(name => processItem({ name, parentPath: currentDir })), + ...packagePaths.map(path => processItem({ name: path, packagePath: path })), + ]); const tsSchemas = compileTsSchemas(tsSchemaPaths); diff --git a/packages/config-loader/src/lib/schema/load.ts b/packages/config-loader/src/lib/schema/load.ts index 4620961310..2d4af3454c 100644 --- a/packages/config-loader/src/lib/schema/load.ts +++ b/packages/config-loader/src/lib/schema/load.ts @@ -28,6 +28,7 @@ import { export type LoadConfigSchemaOptions = | { dependencies: string[]; + packagePaths?: string[]; } | { serialized: JsonObject; @@ -44,7 +45,10 @@ export async function loadConfigSchema( let schemas: ConfigSchemaPackageEntry[]; if ('dependencies' in options) { - schemas = await collectConfigSchemas(options.dependencies); + schemas = await collectConfigSchemas( + options.dependencies, + options.packagePaths ?? [], + ); } else { const { serialized } = options; if (serialized?.backstageConfigSchemaVersion !== 1) { diff --git a/packages/core-app-api/src/app/App.tsx b/packages/core-app-api/src/app/App.tsx index 7857c8b35f..f5fec7e624 100644 --- a/packages/core-app-api/src/app/App.tsx +++ b/packages/core-app-api/src/app/App.tsx @@ -198,6 +198,7 @@ export class PrivateAppImpl implements BackstageApp { private readonly bindRoutes: AppOptions['bindRoutes']; private readonly identityApi = new AppIdentity(); + private readonly apiFactoryRegistry: ApiFactoryRegistry; constructor(options: FullAppOptions) { this.apis = options.apis; @@ -208,6 +209,7 @@ export class PrivateAppImpl implements BackstageApp { this.configLoader = options.configLoader; this.defaultApis = options.defaultApis; this.bindRoutes = options.bindRoutes; + this.apiFactoryRegistry = new ApiFactoryRegistry(); } getPlugins(): BackstagePlugin[] { @@ -388,17 +390,27 @@ export class PrivateAppImpl implements BackstageApp { private getApiHolder(): ApiHolder { if (this.apiHolder) { + // Register additional plugins if they have been added. + // Routes paths, objects and others are already updated in the provider when children of it change + for (const plugin of this.plugins) { + for (const factory of plugin.getApis()) { + if (!this.apiFactoryRegistry.get(factory.api)) { + this.apiFactoryRegistry.register('default', factory); + } + } + } + ApiResolver.validateFactories( + this.apiFactoryRegistry, + this.apiFactoryRegistry.getAllApis(), + ); return this.apiHolder; } - - const registry = new ApiFactoryRegistry(); - - registry.register('static', { + this.apiFactoryRegistry.register('static', { api: appThemeApiRef, deps: {}, factory: () => AppThemeSelector.createWithStorage(this.themes), }); - registry.register('static', { + this.apiFactoryRegistry.register('static', { api: configApiRef, deps: {}, factory: () => { @@ -410,7 +422,7 @@ export class PrivateAppImpl implements BackstageApp { return this.configApi; }, }); - registry.register('static', { + this.apiFactoryRegistry.register('static', { api: identityApiRef, deps: {}, factory: () => this.identityApi, @@ -418,18 +430,18 @@ export class PrivateAppImpl implements BackstageApp { // It's possible to replace the feature flag API, but since we must have at least // one implementation we add it here directly instead of through the defaultApis. - registry.register('default', { + this.apiFactoryRegistry.register('default', { api: featureFlagsApiRef, deps: {}, factory: () => new LocalStorageFeatureFlags(), }); for (const factory of this.defaultApis) { - registry.register('default', factory); + this.apiFactoryRegistry.register('default', factory); } for (const plugin of this.plugins) { for (const factory of plugin.getApis()) { - if (!registry.register('default', factory)) { + if (!this.apiFactoryRegistry.register('default', factory)) { throw new Error( `Plugin ${plugin.getId()} tried to register duplicate or forbidden API factory for ${ factory.api @@ -440,17 +452,19 @@ export class PrivateAppImpl implements BackstageApp { } for (const factory of this.apis) { - if (!registry.register('app', factory)) { + if (!this.apiFactoryRegistry.register('app', factory)) { throw new Error( `Duplicate or forbidden API factory for ${factory.api} in app`, ); } } - ApiResolver.validateFactories(registry, registry.getAllApis()); - - this.apiHolder = new ApiResolver(registry); + ApiResolver.validateFactories( + this.apiFactoryRegistry, + this.apiFactoryRegistry.getAllApis(), + ); + this.apiHolder = new ApiResolver(this.apiFactoryRegistry); return this.apiHolder; } diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 72ce416549..78d4f18267 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1913,7 +1913,7 @@ export interface TableColumn extends Column { // @public (undocumented) export type TableFilter = { column: string; - type: 'select' | 'multiple-select' | /** @deprecated */ 'checkbox-tree'; + type: 'select' | 'multiple-select'; }; // Warning: (ae-missing-release-tag) "TableProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/packages/core-components/src/components/CheckboxTree/CheckboxTree.stories.tsx b/packages/core-components/src/components/CheckboxTree/CheckboxTree.stories.tsx deleted file mode 100644 index 48da8b92b2..0000000000 --- a/packages/core-components/src/components/CheckboxTree/CheckboxTree.stories.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 React, { useState } from 'react'; -import { CheckboxTree } from './CheckboxTree'; - -const CHECKBOX_TREE_ITEMS = [ - { - label: 'Generic subcategory name 1', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, - { - label: 'Generic subcategory name 2', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, - { - label: 'Generic subcategory name 3', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, -]; - -export default { - title: 'Inputs/CheckboxTree', - component: CheckboxTree, -}; - -export const Default = () => ( - {}} - label="default" - subCategories={CHECKBOX_TREE_ITEMS} - /> -); - -export const DynamicTree = () => { - function generateTree(showMore: boolean = false) { - const t = [ - { - label: 'Show more', - options: [], - }, - ]; - - if (showMore) { - t.push({ - label: 'More', - options: [], - }); - } - - return t; - } - - const [tree, setTree] = useState(generateTree()); - - return ( - { - setTree(generateTree(state.some(c => c.category === 'Show more'))); - }} - label="default" - subCategories={tree} - /> - ); -}; diff --git a/packages/core-components/src/components/CheckboxTree/CheckboxTree.test.tsx b/packages/core-components/src/components/CheckboxTree/CheckboxTree.test.tsx deleted file mode 100644 index 6c84a311d2..0000000000 --- a/packages/core-components/src/components/CheckboxTree/CheckboxTree.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { fireEvent, render } from '@testing-library/react'; -import React from 'react'; -import { CheckboxTree } from './CheckboxTree'; - -const CHECKBOX_TREE_ITEMS = [ - { - label: 'Generic subcategory name 1', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, -]; - -const minProps = { - onChange: jest.fn(), - label: 'Default', - subCategories: CHECKBOX_TREE_ITEMS, -}; - -describe('', () => { - it('renders without exploding', async () => { - const { getByText, getByTestId } = render(); - - expect(getByText('Generic subcategory name 1')).toBeInTheDocument(); - const checkbox = await getByTestId('expandable'); - - // Simulate click on expandable arrow - fireEvent.click(checkbox); - - // Simulate click on option - const option = getByText('Option 1'); - expect(getByText('Option 1')).toBeInTheDocument(); - fireEvent.click(option); - expect(minProps.onChange).toHaveBeenCalled(); - }); -}); diff --git a/packages/core-components/src/components/CheckboxTree/CheckboxTree.tsx b/packages/core-components/src/components/CheckboxTree/CheckboxTree.tsx deleted file mode 100644 index 71b5cf7659..0000000000 --- a/packages/core-components/src/components/CheckboxTree/CheckboxTree.tsx +++ /dev/null @@ -1,357 +0,0 @@ -/* - * 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. - */ -/* eslint-disable guard-for-in */ -import { - Checkbox, - Collapse, - List, - ListItem, - ListItemIcon, - ListItemText, - Typography, -} from '@material-ui/core'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import ExpandLess from '@material-ui/icons/ExpandLess'; -import ExpandMore from '@material-ui/icons/ExpandMore'; -import produce from 'immer'; -import { isEqual } from 'lodash'; -import React, { useEffect, useReducer } from 'react'; -import { usePrevious } from 'react-use'; - -type IndexedObject = { - [key: string]: T; -}; - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - width: '100%', - minWidth: 10, - maxWidth: 360, - backgroundColor: 'transparent', - '&:hover': { - backgroundColor: 'transparent', - }, - '&:active': { - animation: 'none', - transform: 'none', - }, - }, - nested: { - paddingLeft: theme.spacing(5), - height: '32px', - '&:hover': { - backgroundColor: 'transparent', - }, - }, - listItemIcon: { - minWidth: 10, - }, - listItem: { - '&:hover': { - backgroundColor: 'transparent', - }, - }, - text: { - '& span, & svg': { - fontWeight: 'normal', - fontSize: 14, - }, - }, - }), -); - -/* SUB_CATEGORY */ - -type SubCategory = { - label: string; - isChecked?: boolean; - isOpen?: boolean; - options?: Option[]; -}; - -type SubCategoryWithIndexedOptions = { - label: string; - isChecked?: boolean; - isOpen?: boolean; - options: IndexedObject