From 0bfe7efa2284b31592c9966278f6d5ad5850c27a Mon Sep 17 00:00:00 2001 From: blam Date: Thu, 18 Jul 2024 14:54:24 +0200 Subject: [PATCH 1/8] chore: wip Signed-off-by: blam Signed-off-by: blam --- .../frontend-app-api/src/wiring/createApp.tsx | 1 + .../src/extensions/IconBundleBlueprint.ts | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index cf6ea6dab4..ae7283f301 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -171,6 +171,7 @@ export interface CreateAppFeatureLoader { /** @public */ export function createApp(options?: { + /** @deprecated - TODO */ icons?: { [key in string]: IconComponent }; features?: (FrontendFeature | CreateAppFeatureLoader)[]; configLoader?: () => Promise<{ config: ConfigApi }>; diff --git a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts new file mode 100644 index 0000000000..7008dba5a1 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2024 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 { IconComponent } from '../icons'; +import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; + +const iconsDataRef = + createExtensionDataRef<{ [key in string]: IconComponent }>('core.icons'); + +/** @public */ +export const IconBundleBlueprint = createExtensionBlueprint({ + kind: 'icon-bundle', + namespace: 'app', + attachTo: { id: 'app', input: 'icons' }, + output: { + icons: iconsDataRef, + }, + factory: (_, params: { icons: { [key in string]: IconComponent } }) => params, +}); From dc37210a290aa28ed4e12d4d22a3f4696c693af1 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 22 Jul 2024 15:38:22 +0200 Subject: [PATCH 2/8] feat: add support for collecting icons using the new extension blueprint Signed-off-by: blam --- packages/frontend-app-api/src/extensions/App.tsx | 4 ++++ packages/frontend-app-api/src/wiring/createApp.tsx | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/frontend-app-api/src/extensions/App.tsx b/packages/frontend-app-api/src/extensions/App.tsx index 82e7600178..7794aceb79 100644 --- a/packages/frontend-app-api/src/extensions/App.tsx +++ b/packages/frontend-app-api/src/extensions/App.tsx @@ -24,6 +24,7 @@ import { createExtensionInput, createThemeExtension, createTranslationExtension, + IconBundleBlueprint, } from '@backstage/frontend-plugin-api'; export const App = createExtension({ @@ -42,6 +43,9 @@ export const App = createExtension({ translations: createExtensionInput({ translation: createTranslationExtension.translationDataRef, }), + icons: createExtensionInput({ + icon: IconBundleBlueprint.dataRefs.icons, + }), root: createExtensionInput( { element: coreExtensionData.reactElement, diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index ae7283f301..4d929b206f 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -25,6 +25,7 @@ import { createThemeExtension, createTranslationExtension, FrontendFeature, + IconBundleBlueprint, iconsApiRef, RouteResolutionApi, routeResolutionApiRef, @@ -247,6 +248,7 @@ export function createApp(options?: { * @public */ export function createSpecializedApp(options?: { + /** @deprecated - TODO */ icons?: { [key in string]: IconComponent }; features?: FrontendFeature[]; config?: ConfigApi; @@ -374,6 +376,11 @@ function createApiHolder( (x): x is typeof createTranslationExtension.translationDataRef.T => !!x, ) ?? []; + const extensionIcons = tree.root.edges.attachments + .get('icons') + ?.map(e => e.instance?.getData(IconBundleBlueprint.dataRefs.icons)) + .reduce((acc, bundle) => ({ ...acc, ...bundle }), {}); + for (const factory of pluginApis) { factoryRegistry.register('default', factory); } @@ -414,7 +421,8 @@ function createApiHolder( factoryRegistry.register('static', { api: iconsApiRef, deps: {}, - factory: () => new DefaultIconsApi({ ...defaultIcons, ...icons }), + factory: () => + new DefaultIconsApi({ ...defaultIcons, ...extensionIcons, ...icons }), }); factoryRegistry.register('static', { From 4a18c942be1d49700540005f9d6a1566d0268f98 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 22 Jul 2024 15:39:33 +0200 Subject: [PATCH 3/8] feat: support exporting of dataRefs through blueprints Signed-off-by: blam --- .../src/extensions/IconBundleBlueprint.ts | 10 ++++--- .../src/extensions/index.ts | 1 + .../src/wiring/createExtensionBlueprint.ts | 27 ++++++++++++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts index 7008dba5a1..2ad1404102 100644 --- a/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts +++ b/packages/frontend-plugin-api/src/extensions/IconBundleBlueprint.ts @@ -16,8 +16,9 @@ import { IconComponent } from '../icons'; import { createExtensionBlueprint, createExtensionDataRef } from '../wiring'; -const iconsDataRef = - createExtensionDataRef<{ [key in string]: IconComponent }>('core.icons'); +const iconsDataRef = createExtensionDataRef<{ + [key in string]: IconComponent; +}>().with({ id: 'core.icons' }); /** @public */ export const IconBundleBlueprint = createExtensionBlueprint({ @@ -27,5 +28,8 @@ export const IconBundleBlueprint = createExtensionBlueprint({ output: { icons: iconsDataRef, }, - factory: (_, params: { icons: { [key in string]: IconComponent } }) => params, + factory: (params: { icons: { [key in string]: IconComponent } }) => params, + dataRefs: { + icons: iconsDataRef, + }, }); diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index 562cb728cb..267201db81 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -25,3 +25,4 @@ export { createSignInPageExtension } from './createSignInPageExtension'; export { createThemeExtension } from './createThemeExtension'; export { createComponentExtension } from './createComponentExtension'; export { createTranslationExtension } from './createTranslationExtension'; +export { IconBundleBlueprint } from './IconBundleBlueprint'; diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts index 6c79017a0a..b504db5f03 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.ts @@ -34,6 +34,7 @@ export interface CreateExtensionBlueprintOptions< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfig, + TDataRefs extends AnyExtensionDataMap, > { kind: string; namespace?: string; @@ -50,6 +51,8 @@ export interface CreateExtensionBlueprintOptions< inputs: Expand>; }, ): Expand>; + + dataRefs?: TDataRefs; } /** @@ -60,7 +63,10 @@ export interface ExtensionBlueprint< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfig, + TDataRefs extends AnyExtensionDataMap, > { + dataRefs: TDataRefs; + make(args: { namespace?: string; name?: string; @@ -97,15 +103,21 @@ class ExtensionBlueprintImpl< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfig, + TDataRefs extends AnyExtensionDataMap, > { constructor( private readonly options: CreateExtensionBlueprintOptions< TParams, TInputs, TOutput, - TConfig + TConfig, + TDataRefs >, - ) {} + ) { + this.dataRefs = options.dataRefs!; + } + + dataRefs: TDataRefs; public make(args: { namespace?: string; @@ -184,8 +196,15 @@ export function createExtensionBlueprint< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfig, + TDataRefs extends AnyExtensionDataMap = never, >( - options: CreateExtensionBlueprintOptions, -): ExtensionBlueprint { + options: CreateExtensionBlueprintOptions< + TParams, + TInputs, + TOutput, + TConfig, + TDataRefs + >, +): ExtensionBlueprint { return new ExtensionBlueprintImpl(options); } From 7777b5fb40b96fcefe6a418c0d3d657b3ae6b6a1 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 22 Jul 2024 15:45:01 +0200 Subject: [PATCH 4/8] chore: added changeset Signed-off-by: blam --- .changeset/forty-ties-agree.md | 18 ++++++++++++++++++ .changeset/forty-ties-disagree.md | 5 +++++ 2 files changed, 23 insertions(+) create mode 100644 .changeset/forty-ties-agree.md create mode 100644 .changeset/forty-ties-disagree.md diff --git a/.changeset/forty-ties-agree.md b/.changeset/forty-ties-agree.md new file mode 100644 index 0000000000..caa45841a2 --- /dev/null +++ b/.changeset/forty-ties-agree.md @@ -0,0 +1,18 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons. + +```tsx +import { IconBundleBlueprint } from '@backstage/frontend-plugin-api'; + +const exampleIconBundle = IconBundleBlueprint.make({ + name: 'example-bundle', + params: { + icons: { + user: MyOwnUserIcon, + }, + }, +}); +``` diff --git a/.changeset/forty-ties-disagree.md b/.changeset/forty-ties-disagree.md new file mode 100644 index 0000000000..214097b35d --- /dev/null +++ b/.changeset/forty-ties-disagree.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Support icon overriding with the new `IconBundleBlueprint` API. From cf9da6dafbe6c2fd038d9b8176a2f28816b76e10 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 22 Jul 2024 16:10:05 +0200 Subject: [PATCH 5/8] chore: added test for dataRef exporting Signed-off-by: blam --- .../wiring/createExtensionBlueprint.test.tsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx index c7b077bdbd..87b3cec152 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx +++ b/packages/frontend-plugin-api/src/wiring/createExtensionBlueprint.test.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { coreExtensionData } from './coreExtensionData'; import { createExtensionBlueprint } from './createExtensionBlueprint'; import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { createExtensionDataRef } from './createExtensionDataRef'; describe('createExtensionBlueprint', () => { it('should allow creation of extension blueprints', () => { @@ -102,4 +103,28 @@ describe('createExtensionBlueprint', () => { const { container } = createExtensionTester(extension).render(); expect(container.querySelector('h2')).toHaveTextContent('Hello, world!'); }); + + it('should allow exporting the dataRefs from the extension blueprint', () => { + const dataRef = createExtensionDataRef().with({ id: 'test.data' }); + + const TestExtensionBlueprint = createExtensionBlueprint({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: { + element: coreExtensionData.reactElement, + }, + dataRefs: { + data: dataRef, + }, + factory(params: { text: string }) { + return { + element:

{params.text}

, + }; + }, + }); + + expect(TestExtensionBlueprint.dataRefs).toEqual({ + data: dataRef, + }); + }); }); From b1f3c44e42a8f880698e2b4e5c59be133c000738 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 22 Jul 2024 16:31:09 +0200 Subject: [PATCH 6/8] chore: api-reports Signed-off-by: blam --- packages/frontend-plugin-api/api-report.md | 46 +++++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 65445b5c4d..3c082a7694 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -522,9 +522,16 @@ export function createExtensionBlueprint< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfig, + TDataRefs extends AnyExtensionDataMap = never, >( - options: CreateExtensionBlueprintOptions, -): ExtensionBlueprint; + options: CreateExtensionBlueprintOptions< + TParams, + TInputs, + TOutput, + TConfig, + TDataRefs + >, +): ExtensionBlueprint; // @public (undocumented) export interface CreateExtensionBlueprintOptions< @@ -532,6 +539,7 @@ export interface CreateExtensionBlueprintOptions< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfig, + TDataRefs extends AnyExtensionDataMap, > { // (undocumented) attachTo: { @@ -541,6 +549,8 @@ export interface CreateExtensionBlueprintOptions< // (undocumented) configSchema?: PortableSchema; // (undocumented) + dataRefs?: TDataRefs; + // (undocumented) disabled?: boolean; // (undocumented) factory( @@ -917,7 +927,10 @@ export interface ExtensionBlueprint< TInputs extends AnyExtensionInputMap, TOutput extends AnyExtensionDataMap, TConfig, + TDataRefs extends AnyExtensionDataMap, > { + // (undocumented) + dataRefs: TDataRefs; // (undocumented) make(args: { namespace?: string; @@ -1085,6 +1098,35 @@ export { gitlabAuthApiRef }; export { googleAuthApiRef }; +// @public (undocumented) +export const IconBundleBlueprint: ExtensionBlueprint< + { + icons: { + [x: string]: IconComponent; + }; + }, + AnyExtensionInputMap, + { + icons: ConfigurableExtensionDataRef< + 'core.icons', + { + [x: string]: IconComponent; + }, + {} + >; + }, + unknown, + { + icons: ConfigurableExtensionDataRef< + 'core.icons', + { + [x: string]: IconComponent; + }, + {} + >; + } +>; + // @public export type IconComponent = ComponentType< | { From 97abfa22b9e98ad3c8b950628e19b4240243cb52 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Jul 2024 08:01:34 +0200 Subject: [PATCH 7/8] chore: added some docs and cleanup the todos Signed-off-by: blam --- .../building-apps/03-built-in-extensions.md | 1 + .../building-apps/08-migrating.md | 25 +++++++++++++++++++ .../building-plugins/03-extension-types.md | 4 +++ .../frontend-app-api/src/wiring/createApp.tsx | 4 +-- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/frontend-system/building-apps/03-built-in-extensions.md b/docs/frontend-system/building-apps/03-built-in-extensions.md index 4f8417f74f..a5e6476eb0 100644 --- a/docs/frontend-system/building-apps/03-built-in-extensions.md +++ b/docs/frontend-system/building-apps/03-built-in-extensions.md @@ -45,6 +45,7 @@ This extension is the first extension attached to the extension tree. It is resp | themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) | | components | The app components list. | [createComponentExtension.componentDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension.componentdataref) | false | See [default components](#default-components-extensions). | [createComponentExtension](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension) | | translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) | +| icons | The app icons list. | [IconBundleBlueprint.dataRefs.icons](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint.dataRefs.icons) | true | - | [IconBundleBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint) | #### Default theme extensions diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 0abdd2aac3..716e0da772 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -314,6 +314,31 @@ const app = createApp({ }); ``` +### `icons` + +Icons are now installed as extensions, using the `IconBundleBlueprint` to make new instances which can be added to the app. + +```ts +import { IconBundleBlueprint } from '@backstage/frontend-plugin-api'; + +const exampleIconBundle = IconBundleBlueprint.make({ + name: 'example-bundle', + params: { + icons: { + user: MyOwnUserIcon, + }, + }, +}); + +const app = createApp({ + features: [ + createExtensionOverrides({ + extensions: [exampleIconBundle], + }), + ], +}); +``` + ### `bindRoutes` Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/07-routes.md#binding-external-route-references) for more information. diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md index 2bea073ed6..1f3baaa710 100644 --- a/docs/frontend-system/building-plugins/03-extension-types.md +++ b/docs/frontend-system/building-plugins/03-extension-types.md @@ -38,6 +38,10 @@ Sign-in page extension have a single purpose - to implement a custom sign-in pag Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. +### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md) + +Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app. + ### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md) Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 4d929b206f..8d39cb65aa 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -172,7 +172,7 @@ export interface CreateAppFeatureLoader { /** @public */ export function createApp(options?: { - /** @deprecated - TODO */ + /** @deprecated - Please use {@link @backstage/frontend-app-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ icons?: { [key in string]: IconComponent }; features?: (FrontendFeature | CreateAppFeatureLoader)[]; configLoader?: () => Promise<{ config: ConfigApi }>; @@ -248,7 +248,7 @@ export function createApp(options?: { * @public */ export function createSpecializedApp(options?: { - /** @deprecated - TODO */ + /** @deprecated - Please use {@link @backstage/frontend-app-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ icons?: { [key in string]: IconComponent }; features?: FrontendFeature[]; config?: ConfigApi; From 663568ebb6946f09a16458feca290059cb2ae79a Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Jul 2024 08:28:46 +0200 Subject: [PATCH 8/8] chore: fixing todo package Signed-off-by: blam --- packages/frontend-app-api/src/wiring/createApp.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 8d39cb65aa..d82d0ca782 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -172,7 +172,7 @@ export interface CreateAppFeatureLoader { /** @public */ export function createApp(options?: { - /** @deprecated - Please use {@link @backstage/frontend-app-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ + /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ icons?: { [key in string]: IconComponent }; features?: (FrontendFeature | CreateAppFeatureLoader)[]; configLoader?: () => Promise<{ config: ConfigApi }>; @@ -248,7 +248,7 @@ export function createApp(options?: { * @public */ export function createSpecializedApp(options?: { - /** @deprecated - Please use {@link @backstage/frontend-app-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ + /** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */ icons?: { [key in string]: IconComponent }; features?: FrontendFeature[]; config?: ConfigApi;