chore: getting the tests working

Co-authored-by: Patrik Oldsberg <poldsberg@gmail.com>
Co-authored-by: Fredrik Adelöw <freben@users.noreply.github.com>
Co-authored-by: Camila Belo <camilaibs@users.noreply.github.com>
Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-08-09 15:12:47 +02:00
parent 4bc224cc83
commit e6c763f76b
38 changed files with 578 additions and 281 deletions
+348 -62
View File
@@ -189,6 +189,27 @@ export type AnyRoutes = {
[name in string]: RouteRef | SubRouteRef;
};
// @public (undocumented)
export const ApiBlueprint: ExtensionBlueprint<
'api',
undefined,
undefined,
{
factory: AnyApiFactory;
},
ConfigurableExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>,
{},
{},
{},
{
factory: ConfigurableExtensionDataRef<
AnyApiFactory,
'core.api.factory',
{}
>;
}
>;
export { ApiFactory };
export { ApiHolder };
@@ -240,6 +261,50 @@ export interface AppNodeSpec {
readonly source?: BackstagePlugin;
}
// @public (undocumented)
export const AppRootElementBlueprint: ExtensionBlueprint<
'app-root-element',
undefined,
undefined,
{
element: JSX.Element | (() => JSX.Element);
},
ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>,
{},
{},
{},
never
>;
// @public (undocumented)
export const AppRootWrapperBlueprint: ExtensionBlueprint<
'app-root-wrapper',
undefined,
undefined,
{
Component: ComponentType<PropsWithChildren<{}>>;
},
ConfigurableExtensionDataRef<
React_2.ComponentType<{
children?: React_2.ReactNode;
}>,
'app.root.wrapper',
{}
>,
{},
{},
{},
{
component: ConfigurableExtensionDataRef<
React_2.ComponentType<{
children?: React_2.ReactNode;
}>,
'app.root.wrapper',
{}
>;
}
>;
export { AppTheme };
export { AppThemeApi };
@@ -381,7 +446,7 @@ export type CoreNotFoundErrorPageProps = {
// @public (undocumented)
export type CoreProgressProps = {};
// @public (undocumented)
// @public @deprecated (undocumented)
export function createApiExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
@@ -425,7 +490,7 @@ export { createApiFactory };
export { createApiRef };
// @public
// @public @deprecated
export function createAppRootElementExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
@@ -447,7 +512,7 @@ export function createAppRootElementExtension<
}) => JSX_2.Element);
}): ExtensionDefinition<TConfig>;
// @public
// @public @deprecated
export function createAppRootWrapperExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
@@ -675,7 +740,7 @@ export type CreateExtensionBlueprintOptions<
},
> = {
kind: TKind;
namespace?: TNamespace | ((params: TParams) => TNamespace);
namespace?: TNamespace;
attachTo: {
id: string;
input: string;
@@ -683,9 +748,9 @@ export type CreateExtensionBlueprintOptions<
disabled?: boolean;
inputs?: TInputs;
output: Array<UOutput>;
name?: TName | ((params: TParams) => TName);
name?: TName;
config?: {
schema: TConfigSchema | ((params: TParams) => TConfigSchema);
schema: TConfigSchema;
};
factory(
params: TParams,
@@ -824,7 +889,7 @@ export function createExternalRouteRef<
}
>;
// @public
// @public @deprecated
export function createNavItemExtension(options: {
namespace?: string;
name?: string;
@@ -859,7 +924,7 @@ export namespace createNavItemExtension {
>;
}
// @public
// @public @deprecated
export function createNavLogoExtension(options: {
name?: string;
namespace?: string;
@@ -888,7 +953,7 @@ export namespace createNavLogoExtension {
>;
}
// @public
// @public @deprecated
export function createPageExtension<
TConfig extends {
path: string;
@@ -958,7 +1023,7 @@ export function createRouteRef<
}
>;
// @public
// @public @deprecated
export function createRouterExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
@@ -997,7 +1062,7 @@ export function createSchemaFromZod<TOutput, TInput>(
schemaCreator: (zImpl: typeof z) => ZodSchema<TOutput, ZodTypeDef, TInput>,
): PortableSchema<TOutput, TInput>;
// @public (undocumented)
// @public @deprecated (undocumented)
export function createSignInPageExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
@@ -1036,7 +1101,7 @@ export function createSubRouteRef<
parent: RouteRef<ParentParams>;
}): MakeSubRouteRef<PathParams<Path>, ParentParams>;
// @public (undocumented)
// @public @deprecated (undocumented)
export function createThemeExtension(
theme: AppTheme,
): ExtensionDefinition<
@@ -1049,7 +1114,7 @@ export function createThemeExtension(
string | undefined
>;
// @public (undocumented)
// @public @deprecated (undocumented)
export namespace createThemeExtension {
const // (undocumented)
themeDataRef: ConfigurableExtensionDataRef<
@@ -1059,7 +1124,7 @@ export namespace createThemeExtension {
>;
}
// @public (undocumented)
// @public @deprecated (undocumented)
export function createTranslationExtension(options: {
name?: string;
resource: TranslationResource | TranslationMessages;
@@ -1073,7 +1138,7 @@ export function createTranslationExtension(options: {
string | undefined
>;
// @public (undocumented)
// @public @deprecated (undocumented)
export namespace createTranslationExtension {
const // (undocumented)
translationDataRef: ConfigurableExtensionDataRef<
@@ -1153,9 +1218,31 @@ export interface ExtensionBlueprint<
> {
// (undocumented)
dataRefs: TDataRefs;
// (undocumented)
make<
TNewNamespace extends string | undefined,
TNewName extends string | undefined,
>(args: {
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: {
id: string;
input: string;
};
disabled?: boolean;
params: TParams;
}): ExtensionDefinition<
TConfig,
TConfigInput,
UOutput,
TInputs,
TKind,
string | undefined extends TNewNamespace ? TNamespace : TNewNamespace,
string | undefined extends TNewName ? TName : TNewName
>;
makeWithOverrides<
TNewNamespace extends string | undefined,
TNewName extends string | undefined,
TExtensionConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
@@ -1170,55 +1257,48 @@ export interface ExtensionBlueprint<
}
>;
},
>(
args: {
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: {
id: string;
input: string;
>(args: {
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: {
id: string;
input: string;
};
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
factory(
originalFactory: (
params: TParams,
context?: {
config?: TConfig;
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) => ExtensionDataContainer<UOutput>,
context: {
node: AppNode;
config: TConfig & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
};
} & (
| ({
factory(
originalFactory: (
params: TParams,
context?: {
config?: TConfig;
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) => ExtensionDataContainer<UOutput>,
context: {
node: AppNode;
config: TConfig & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
},
): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
UFactoryOutput
>)
| {
params: TParams;
}
),
): ExtensionDefinition<
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
},
): Iterable<UFactoryOutput> &
VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
UFactoryOutput
>;
}): ExtensionDefinition<
{
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
@@ -1616,6 +1696,73 @@ export interface LegacyExtensionInput<
export { microsoftAuthApiRef };
// @public (undocumented)
export const NavItemBlueprint: ExtensionBlueprint<
'nav-item',
undefined,
undefined,
{
title: string;
icon: IconComponent_2;
routeRef: RouteRef<undefined>;
},
ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent_2;
routeRef: RouteRef<undefined>;
},
'core.nav-item.target',
{}
>,
{},
{},
{},
{
target: ConfigurableExtensionDataRef<
{
title: string;
icon: IconComponent_2;
routeRef: RouteRef<undefined>;
},
'core.nav-item.target',
{}
>;
}
>;
// @public (undocumented)
export const NavLogoBlueprint: ExtensionBlueprint<
'nav-logo',
undefined,
undefined,
{
logoIcon: JSX.Element;
logoFull: JSX.Element;
},
ConfigurableExtensionDataRef<
{
logoIcon?: JSX.Element | undefined;
logoFull?: JSX.Element | undefined;
},
'core.nav-logo.logo-elements',
{}
>,
{},
{},
{},
{
logoElements: ConfigurableExtensionDataRef<
{
logoIcon?: JSX.Element | undefined;
logoFull?: JSX.Element | undefined;
},
'core.nav-logo.logo-elements',
{}
>;
}
>;
export { OAuthApi };
export { OAuthRequestApi };
@@ -1634,6 +1781,35 @@ export { oneloginAuthApiRef };
export { OpenIdConnectApi };
// @public (undocumented)
export const PageBlueprint: ExtensionBlueprint<
'page',
undefined,
undefined,
{
defaultPath: string;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef<AnyRouteRefParams> | undefined;
},
| ConfigurableExtensionDataRef<React_2.JSX.Element, 'core.reactElement', {}>
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
| ConfigurableExtensionDataRef<
RouteRef<AnyRouteRefParams>,
'core.routing.ref',
RouteRef<AnyRouteRefParams> & {
optional: true;
}
>,
{},
{
path: string | undefined;
},
{
path?: string | undefined;
},
never
>;
export { PendingOAuthRequest };
// @public (undocumented)
@@ -1699,6 +1875,35 @@ export type RouteFunc<TParams extends AnyRouteRefParams> = (
: readonly [params: TParams]
) => string;
// @public (undocumented)
export const RouterBlueprint: ExtensionBlueprint<
'app-router-component',
undefined,
undefined,
{
Component: ComponentType<PropsWithChildren<{}>>;
},
ConfigurableExtensionDataRef<
ComponentType<{
children?: ReactNode;
}>,
'app.router.wrapper',
{}
>,
{},
{},
{},
{
component: ConfigurableExtensionDataRef<
ComponentType<{
children?: ReactNode;
}>,
'app.router.wrapper',
{}
>;
}
>;
// @public
export interface RouteRef<
TParams extends AnyRouteRefParams = AnyRouteRefParams,
@@ -1733,6 +1938,31 @@ export { SessionApi };
export { SessionState };
// @public (undocumented)
export const SignInPageBlueprint: ExtensionBlueprint<
'sign-in-page',
undefined,
undefined,
{
loader: () => Promise<ComponentType<SignInPageProps>>;
},
ConfigurableExtensionDataRef<
React_2.ComponentType<SignInPageProps>,
'core.sign-in-page.component',
{}
>,
{},
{},
{},
{
component: ConfigurableExtensionDataRef<
React_2.ComponentType<SignInPageProps>,
'core.sign-in-page.component',
{}
>;
}
>;
export { StorageApi };
export { storageApiRef };
@@ -1751,6 +1981,62 @@ export interface SubRouteRef<
readonly T: TParams;
}
// @public (undocumented)
export const ThemeBlueprint: ExtensionBlueprint<
'theme',
'app',
undefined,
{
theme: AppTheme;
},
ConfigurableExtensionDataRef<AppTheme, 'core.theme.theme', {}>,
{},
{},
{},
{
theme: ConfigurableExtensionDataRef<AppTheme, 'core.theme.theme', {}>;
}
>;
// @public (undocumented)
export const TranslationBlueprint: ExtensionBlueprint<
'translation',
undefined,
undefined,
{
resource: TranslationResource | TranslationMessages;
},
ConfigurableExtensionDataRef<
| TranslationResource<string>
| TranslationMessages<
string,
{
[x: string]: string;
},
boolean
>,
'core.translation.translation',
{}
>,
{},
{},
{},
{
translation: ConfigurableExtensionDataRef<
| TranslationResource<string>
| TranslationMessages<
string,
{
[x: string]: string;
},
boolean
>,
'core.translation.translation',
{}
>;
}
>;
export { TranslationMessages };
export { TranslationMessagesOptions };
@@ -30,6 +30,7 @@ describe('ApiBlueprint', () => {
params: {
factory,
},
namespace: 'test',
});
expect(extension).toMatchInlineSnapshot(`
@@ -60,7 +61,7 @@ describe('ApiBlueprint', () => {
const api = createApiRef<{ foo: string }>({ id: 'test' });
const factory = jest.fn(() => ({ foo: 'bar' }));
const extension = ApiBlueprint.make({
const extension = ApiBlueprint.makeWithOverrides({
config: {
schema: {
test: z => z.string().default('test'),
@@ -14,9 +14,10 @@
* limitations under the License.
*/
import { createExtensionBlueprint } from '../wiring';
import { createApiExtension } from './createApiExtension';
import { createApiExtension } from '../extensions/createApiExtension';
import { AnyApiFactory } from '@backstage/core-plugin-api';
/** @public */
export const ApiBlueprint = createExtensionBlueprint({
kind: 'api',
attachTo: { id: 'app', input: 'apis' },
@@ -27,5 +28,4 @@ export const ApiBlueprint = createExtensionBlueprint({
*factory(params: { factory: AnyApiFactory }) {
yield createApiExtension.factoryDataRef(params.factory);
},
namespace: ({ factory }) => factory.api.id,
});
@@ -15,6 +15,7 @@
*/
import { coreExtensionData, createExtensionBlueprint } from '../wiring';
/** @public */
export const AppRootElementBlueprint = createExtensionBlueprint({
kind: 'app-root-element',
attachTo: { id: 'app/root', input: 'elements' },
@@ -78,7 +78,7 @@ describe('AppRootWrapperBlueprint', () => {
});
it('should render the complex component wrapper', async () => {
const extension = AppRootWrapperBlueprint.make({
const extension = AppRootWrapperBlueprint.makeWithOverrides({
namespace: 'ns',
name: 'test',
config: {
@@ -94,6 +94,9 @@ describe('AppRootWrapperBlueprint', () => {
Component: ({ children }) => (
<div data-testid={`${config.name}-${inputs.children.length}`}>
{children}
{inputs.children.flatMap(c =>
c.get(coreExtensionData.reactElement),
)}
</div>
),
});
@@ -119,9 +122,9 @@ describe('AppRootWrapperBlueprint', () => {
.render();
await waitFor(() => {
expect(getByText('Its Me')).toBeInTheDocument();
expect(getByText('Hi')).toBeInTheDocument();
expect(getByTestId('Robin-1')).toBeInTheDocument();
expect(getByText('Its Me')).toBeInTheDocument();
});
});
});
@@ -17,8 +17,9 @@
import React from 'react';
import { ComponentType, PropsWithChildren } from 'react';
import { createExtensionBlueprint } from '../wiring';
import { createAppRootWrapperExtension } from './createAppRootWrapperExtension';
import { createAppRootWrapperExtension } from '../extensions/createAppRootWrapperExtension';
/** @public */
export const AppRootWrapperBlueprint = createExtensionBlueprint({
kind: 'app-root-wrapper',
attachTo: { id: 'app/root', input: 'wrappers' },
@@ -44,7 +44,6 @@ describe('NavItemBlueprint', () => {
"additionalProperties": false,
"properties": {
"title": {
"default": "TEST",
"type": "string",
},
},
@@ -17,8 +17,9 @@
import { IconComponent } from '@backstage/core-plugin-api';
import { RouteRef } from '../routing';
import { createExtensionBlueprint } from '../wiring';
import { createNavItemExtension } from './createNavItemExtension';
import { createNavItemExtension } from '../extensions/createNavItemExtension';
/** @public */
export const NavItemBlueprint = createExtensionBlueprint({
kind: 'nav-item',
attachTo: { id: 'app/nav', input: 'items' },
@@ -30,6 +31,7 @@ export const NavItemBlueprint = createExtensionBlueprint({
{
icon,
routeRef,
title,
}: {
title: string;
icon: IconComponent;
@@ -38,14 +40,14 @@ export const NavItemBlueprint = createExtensionBlueprint({
{ config },
) => [
createNavItemExtension.targetDataRef({
title: config.title,
title: config.title ?? title,
icon,
routeRef,
}),
],
config: {
schema: ({ title }) => ({
title: z => z.string().default(title),
}),
schema: {
title: z => z.string().optional(),
},
},
});
@@ -15,8 +15,9 @@
*/
import { createExtensionBlueprint } from '../wiring';
import { createNavLogoExtension } from './createNavLogoExtension';
import { createNavLogoExtension } from '../extensions/createNavLogoExtension';
/** @public */
export const NavLogoBlueprint = createExtensionBlueprint({
kind: 'nav-logo',
attachTo: { id: 'app/nav', input: 'logos' },
@@ -106,7 +106,7 @@ describe('PageBlueprint', () => {
});
it('should allow defining additional inputs to the extension', async () => {
const myPage = PageBlueprint.make({
const myPage = PageBlueprint.makeWithOverrides({
name: 'test-page',
inputs: {
cards: createExtensionInput([coreExtensionData.reactElement], {
@@ -18,6 +18,7 @@ import { RouteRef } from '../routing';
import { coreExtensionData, createExtensionBlueprint } from '../wiring';
import { ExtensionBoundary } from '../components';
/** @public */
export const PageBlueprint = createExtensionBlueprint({
kind: 'page',
attachTo: { id: 'app/routes', input: 'routes' },
@@ -37,7 +38,7 @@ export const PageBlueprint = createExtensionBlueprint({
loader,
routeRef,
}: {
defaultPath?: string;
defaultPath: string;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef;
},
@@ -47,7 +48,7 @@ export const PageBlueprint = createExtensionBlueprint({
loader().then(element => ({ default: () => element })),
);
yield coreExtensionData.routePath(config.path ?? defaultPath!);
yield coreExtensionData.routePath(config.path ?? defaultPath);
yield coreExtensionData.reactElement(
<ExtensionBoundary node={node}>
<ExtensionComponent />
@@ -97,7 +97,7 @@ describe('RouterBlueprint', () => {
});
it('should work with complex options and props', async () => {
const extension = RouterBlueprint.make({
const extension = RouterBlueprint.makeWithOverrides({
namespace: 'test',
name: 'test',
config: {
@@ -15,8 +15,9 @@
*/
import { ComponentType, PropsWithChildren } from 'react';
import { createExtensionBlueprint } from '../wiring';
import { createRouterExtension } from './createRouterExtension';
import { createRouterExtension } from '../extensions/createRouterExtension';
/** @public */
export const RouterBlueprint = createExtensionBlueprint({
kind: 'app-router-component',
attachTo: { id: 'app/root', input: 'router' },
@@ -15,10 +15,11 @@
*/
import React, { ComponentType, lazy } from 'react';
import { createExtensionBlueprint } from '../wiring';
import { createSignInPageExtension } from './createSignInPageExtension';
import { createSignInPageExtension } from '../extensions/createSignInPageExtension';
import { SignInPageProps } from '@backstage/core-plugin-api';
import { ExtensionBoundary } from '../components';
/** @public */
export const SignInPageBlueprint = createExtensionBlueprint({
kind: 'sign-in-page',
attachTo: { id: 'app/root', input: 'signInPage' },
@@ -27,7 +27,8 @@ describe('ThemeBlueprint', () => {
} as AppTheme;
it('should create an extension with sensible defaults', () => {
expect(ThemeBlueprint.make({ params: { theme } })).toMatchInlineSnapshot(`
expect(ThemeBlueprint.make({ name: 'light', params: { theme } }))
.toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"attachTo": {
@@ -16,12 +16,12 @@
import { AppTheme } from '@backstage/core-plugin-api';
import { createExtensionBlueprint } from '../wiring';
import { createThemeExtension } from './createThemeExtension';
import { createThemeExtension } from '../extensions/createThemeExtension';
/** @public */
export const ThemeBlueprint = createExtensionBlueprint({
kind: 'theme',
namespace: 'app',
name: ({ theme }) => theme.id,
attachTo: { id: 'app', input: 'themes' },
output: [createThemeExtension.themeDataRef],
dataRefs: {
@@ -56,7 +56,7 @@ describe('TranslationBlueprint', () => {
"inputs": {},
"kind": "translation",
"name": "blob",
"namespace": "translationRefId",
"namespace": undefined,
"output": [
[Function],
],
@@ -15,12 +15,12 @@
*/
import { createExtensionBlueprint } from '../wiring';
import { createTranslationExtension } from './createTranslationExtension';
import { createTranslationExtension } from '../extensions/createTranslationExtension';
import { TranslationMessages, TranslationResource } from '../translation';
/** @public */
export const TranslationBlueprint = createExtensionBlueprint({
kind: 'translation',
namespace: ({ resource }) => resource.id,
attachTo: { id: 'app', input: 'translations' },
output: [createTranslationExtension.translationDataRef],
dataRefs: {
@@ -0,0 +1,27 @@
/*
* 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.
*/
export { ApiBlueprint } from './ApiBlueprint';
export { AppRootElementBlueprint } from './AppRootElementBlueprint';
export { AppRootWrapperBlueprint } from './AppRootWrapperBlueprint';
export { IconBundleBlueprint } from './IconBundleBlueprint';
export { NavItemBlueprint } from './NavItemBlueprint';
export { NavLogoBlueprint } from './NavLogoBlueprint';
export { PageBlueprint } from './PageBlueprint';
export { RouterBlueprint } from './RouterBlueprint';
export { SignInPageBlueprint } from './SignInPageBlueprint';
export { ThemeBlueprint } from './ThemeBlueprint';
export { TranslationBlueprint } from './TranslationBlueprint';
@@ -24,7 +24,10 @@ import {
import { AnyExtensionInputMap } from '../wiring/createExtension';
import { Expand } from '../types';
/** @public */
/**
* @public
* @deprecated Use {@link ApiBlueprint} instead.
*/
export function createApiExtension<
TConfig extends {},
TInputs extends AnyExtensionInputMap,
@@ -30,6 +30,7 @@ import {
* the app layout. This is useful for example for shared popups and similar.
*
* @public
* @deprecated Use {@link AppRootElementBlueprint} instead.
*/
export function createAppRootElementExtension<
TConfig extends {},
@@ -31,6 +31,7 @@ import { Expand } from '../types';
* and similar.
*
* @public
* @deprecated Use {@link AppRootWrapperBlueprint} instead.
*/
export function createAppRootWrapperExtension<
TConfig extends {},
@@ -21,7 +21,9 @@ import { RouteRef } from '../routing';
/**
* Helper for creating extensions for a nav item.
*
* @public
* @deprecated Use {@link NavItemBlueprint} instead.
*/
export function createNavItemExtension(options: {
namespace?: string;
@@ -18,7 +18,9 @@ import { createExtension, createExtensionDataRef } from '../wiring';
/**
* Helper for creating extensions for a nav logos.
*
* @public
* @deprecated Use {@link NavLogoBlueprint} instead.
*/
export function createNavLogoExtension(options: {
name?: string;
@@ -31,6 +31,7 @@ import { ExtensionDefinition } from '../wiring/createExtension';
* Helper for creating extensions for a routable React page component.
*
* @public
* @deprecated Use {@link PageBlueprint} instead.
*/
export function createPageExtension<
TConfig extends { path: string },
@@ -31,6 +31,7 @@ import { Expand } from '../types';
* MemoryRouter in tests, or to add additional props to a BrowserRouter.
*
* @public
* @deprecated Use {@link RouterBlueprint} instead.
*/
export function createRouterExtension<
TConfig extends {},
@@ -30,6 +30,7 @@ import { SignInPageProps } from '@backstage/core-plugin-api';
/**
*
* @public
* @deprecated Use {@link SignInPageBlueprint} instead.
*/
export function createSignInPageExtension<
TConfig extends {},
@@ -17,7 +17,10 @@
import { createExtension, createExtensionDataRef } from '../wiring';
import { AppTheme } from '@backstage/core-plugin-api';
/** @public */
/**
* @public
* @deprecated Use {@link ThemeBlueprint} instead.
*/
export function createThemeExtension(theme: AppTheme) {
return createExtension({
kind: 'theme',
@@ -31,7 +34,10 @@ export function createThemeExtension(theme: AppTheme) {
});
}
/** @public */
/**
* @public
* @deprecated Use {@link ThemeBlueprint} instead.
*/
export namespace createThemeExtension {
export const themeDataRef = createExtensionDataRef<AppTheme>().with({
id: 'core.theme.theme',
@@ -17,7 +17,10 @@
import { TranslationMessages, TranslationResource } from '../translation';
import { createExtension, createExtensionDataRef } from '../wiring';
/** @public */
/**
* @public
* @deprecated Use {@link TranslationBlueprint} instead.
*/
export function createTranslationExtension(options: {
name?: string;
resource: TranslationResource | TranslationMessages;
@@ -34,7 +37,10 @@ export function createTranslationExtension(options: {
});
}
/** @public */
/**
* @public
* @deprecated Use {@link TranslationBlueprint} instead.
*/
export namespace createTranslationExtension {
export const translationDataRef = createExtensionDataRef<
TranslationResource | TranslationMessages
@@ -25,4 +25,3 @@ export { createSignInPageExtension } from './createSignInPageExtension';
export { createThemeExtension } from './createThemeExtension';
export { createComponentExtension } from './createComponentExtension';
export { createTranslationExtension } from './createTranslationExtension';
export { IconBundleBlueprint } from './IconBundleBlueprint';
@@ -22,6 +22,7 @@
export * from './analytics';
export * from './apis';
export * from './blueprints';
export * from './components';
export * from './extensions';
export * from './icons';
@@ -127,7 +127,7 @@ describe('createExtensionBlueprint', () => {
},
});
const extension = TestExtensionBlueprint.make({
const extension = TestExtensionBlueprint.makeWithOverrides({
name: 'my-extension',
factory(origFactory) {
return origFactory({
@@ -185,11 +185,8 @@ describe('createExtensionBlueprint', () => {
},
});
const extension = TestExtensionBlueprint.make({
const extension = TestExtensionBlueprint.makeWithOverrides({
name: 'my-extension',
params: {
text: 'Hello, world!',
},
config: {
schema: {
something: z => z.string(),
@@ -236,7 +233,7 @@ describe('createExtensionBlueprint', () => {
},
});
TestExtensionBlueprint.make({
TestExtensionBlueprint.makeWithOverrides({
name: 'my-extension',
params: {
text: 'Hello, world!',
@@ -266,11 +263,8 @@ describe('createExtensionBlueprint', () => {
},
});
const extension = TestExtensionBlueprint.make({
const extension = TestExtensionBlueprint.makeWithOverrides({
name: 'my-extension',
params: {
text: 'Hello, world!',
},
config: {
schema: {
something: z => z.string(),
@@ -341,65 +335,6 @@ describe('createExtensionBlueprint', () => {
expect(true).toBe(true);
});
it('should allow providing callback for properties to set with params', () => {
type TestParams = { test: string };
const Blueprint = createExtensionBlueprint({
kind: 'test-extension',
attachTo: { id: 'test', input: 'default' },
name: (params: TestParams) => `${params.test}-name`,
output: [coreExtensionData.reactElement],
namespace: (props: TestParams) => props.test,
config: {
schema: (props: TestParams) => ({
test: z => z.string().default(props.test),
}),
},
factory(params: TestParams) {
return [coreExtensionData.reactElement(<div>{params.test}</div>)];
},
});
expect(Blueprint.make({ params: { test: 'hello' } }))
.toMatchInlineSnapshot(`
{
"$$type": "@backstage/ExtensionDefinition",
"attachTo": {
"id": "test",
"input": "default",
},
"configSchema": {
"parse": [Function],
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"additionalProperties": false,
"properties": {
"test": {
"default": "hello",
"type": "string",
},
},
"type": "object",
},
},
"disabled": false,
"factory": [Function],
"inputs": {},
"kind": "test-extension",
"name": "hello-name",
"namespace": "hello",
"output": [
[Function],
],
"override": [Function],
"toString": [Function],
"version": "v2",
}
`);
expect(true).toBe(true);
});
it('should allow merging of inputs', () => {
const blueprint = createExtensionBlueprint({
kind: 'test-extension',
@@ -418,7 +353,7 @@ describe('createExtensionBlueprint', () => {
},
});
blueprint.make({
blueprint.makeWithOverrides({
inputs: {
test2: createExtensionInput([coreExtensionData.reactElement], {
singleton: true,
@@ -453,7 +388,7 @@ describe('createExtensionBlueprint', () => {
},
});
blueprint.make({
blueprint.makeWithOverrides({
inputs: {
// @ts-expect-error
test: createExtensionInput([]), // Overrides are not allowed
@@ -480,7 +415,7 @@ describe('createExtensionBlueprint', () => {
});
const ext = toInternalExtensionDefinition(
blueprint.make({
blueprint.makeWithOverrides({
output: [testDataRef2],
factory(origFactory) {
const parent = origFactory({});
@@ -509,7 +444,7 @@ describe('createExtensionBlueprint', () => {
expect(
factoryOutput(
blueprint.make({
blueprint.makeWithOverrides({
output: [testDataRef1, testDataRef2],
*factory(origFactory) {
yield* origFactory({});
@@ -521,7 +456,7 @@ describe('createExtensionBlueprint', () => {
expect(
factoryOutput(
blueprint.make({
blueprint.makeWithOverrides({
output: [testDataRef1, testDataRef2],
factory(origFactory) {
return [...origFactory({}), testDataRef2('bar')];
@@ -544,9 +479,9 @@ describe('createExtensionBlueprint', () => {
},
});
// @ts-expect-error
blueprint.make({
blueprint.makeWithOverrides({
output: [testDataRef2.optional()],
// @ts-expect-error
*factory() {
yield testDataRef1('foo');
yield testDataRef2('bar');
@@ -555,7 +490,7 @@ describe('createExtensionBlueprint', () => {
expect(
factoryOutput(
blueprint.make({
blueprint.makeWithOverrides({
output: [testDataRef2.optional()],
*factory() {
yield testDataRef2('bar');
@@ -578,9 +513,9 @@ describe('createExtensionBlueprint', () => {
},
});
// @ts-expect-error
blueprint.make({
blueprint.makeWithOverrides({
output: [testDataRef1, testDataRef2],
// @ts-expect-error
*factory(origFactory) {
yield* origFactory({});
},
@@ -588,7 +523,7 @@ describe('createExtensionBlueprint', () => {
expect(
factoryOutput(
blueprint.make({
blueprint.makeWithOverrides({
output: [testDataRef1, testDataRef2],
*factory(origFactory) {
yield* origFactory({});
@@ -52,14 +52,14 @@ export type CreateExtensionBlueprintOptions<
TDataRefs extends { [name in string]: AnyExtensionDataRef },
> = {
kind: TKind;
namespace?: TNamespace | ((params: TParams) => TNamespace);
namespace?: TNamespace;
attachTo: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
output: Array<UOutput>;
name?: TName | ((params: TParams) => TName);
name?: TName;
config?: {
schema: TConfigSchema | ((params: TParams) => TConfigSchema);
schema: TConfigSchema;
};
factory(
params: TParams,
@@ -96,13 +96,32 @@ export interface ExtensionBlueprint<
> {
dataRefs: TDataRefs;
make<
TNewNamespace extends string | undefined,
TNewName extends string | undefined,
>(args: {
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
params: TParams;
}): ExtensionDefinition<
TConfig,
TConfigInput,
UOutput,
TInputs,
TKind,
string | undefined extends TNewNamespace ? TNamespace : TNewNamespace,
string | undefined extends TNewName ? TName : TNewName
>;
/**
* Creates a new extension from the blueprint.
*
* You must either pass `params` directly, or define a `factory` that can
* optionally call the original factory with the same params.
*/
make<
makeWithOverrides<
TNewNamespace extends string | undefined,
TNewName extends string | undefined,
TExtensionConfigSchema extends {
@@ -116,52 +135,45 @@ export interface ExtensionBlueprint<
{ optional: boolean; singleton: boolean }
>;
},
>(
args: {
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
>(args: {
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TExtraInputs & {
[KName in keyof TInputs]?: `Error: Input '${KName &
string}' is already defined in parent definition`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
output?: Array<UNewOutput>;
config?: {
schema: TExtensionConfigSchema & {
[KName in keyof TConfig]?: `Error: Config key '${KName &
string}' is already defined in parent schema`;
};
factory(
originalFactory: (
params: TParams,
context?: {
config?: TConfig;
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) => ExtensionDataContainer<UOutput>,
context: {
node: AppNode;
config: TConfig & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
};
} & (
| ({
factory(
originalFactory: (
params: TParams,
context?: {
config?: TConfig;
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
) => ExtensionDataContainer<UOutput>,
context: {
node: AppNode;
config: TConfig & {
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
>;
};
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
},
): Iterable<UFactoryOutput>;
} & VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
UFactoryOutput
>)
| {
params: TParams;
}
),
): ExtensionDefinition<
inputs: Expand<ResolvedExtensionInputs<TInputs & TExtraInputs>>;
},
): Iterable<UFactoryOutput> &
VerifyExtensionFactoryOutput<
AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput,
UFactoryOutput
>;
}): ExtensionDefinition<
{
[key in keyof TExtensionConfigSchema]: z.infer<
ReturnType<TExtensionConfigSchema[key]>
@@ -243,7 +255,7 @@ class ExtensionBlueprintImpl<
dataRefs: TDataRefs;
public make<
public makeWithOverrides<
TExtensionConfigSchema extends {
[key in string]: (zImpl: typeof z) => z.ZodType;
},
@@ -264,11 +276,10 @@ class ExtensionBlueprintImpl<
disabled?: boolean;
inputs?: TExtraInputs;
output?: Array<UNewOutput>;
params?: TParams;
config?: {
schema: TExtensionConfigSchema;
};
factory?(
factory(
originalFactory: (
params: TParams,
context?: {
@@ -312,77 +323,82 @@ class ExtensionBlueprintImpl<
>
>
> {
const optionsSchema = // can remove this args.params check with the split apart of .make
typeof this.options.config?.schema === 'function' && args.params
? this.options.config?.schema(args.params)
: this.options.config?.schema;
const schema = {
...optionsSchema,
...this.options.config?.schema,
...args.config?.schema,
} as TConfigSchema & TExtensionConfigSchema;
const namespace =
typeof this.options.namespace === 'function' && args.params
? this.options.namespace(args.params)
: this.options.namespace;
const name =
typeof this.options.name === 'function' && args.params
? this.options.name(args.params)
: this.options.name;
return createExtension({
kind: this.options.kind,
namespace: args.namespace ?? namespace,
name: args.name ?? name,
namespace: args.namespace ?? this.options.namespace,
name: args.name ?? this.options.name,
attachTo: args.attachTo ?? this.options.attachTo,
disabled: args.disabled ?? this.options.disabled,
inputs: { ...args.inputs, ...this.options.inputs },
output: args.output ?? this.options.output,
config: Object.keys(schema).length === 0 ? undefined : { schema },
factory: ({ node, config, inputs }) => {
if (args.factory) {
return args.factory(
(
innerParams: TParams,
innerContext?: {
config?: {
[key in keyof TConfigSchema]: z.infer<
ReturnType<TConfigSchema[key]>
>;
};
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
): ExtensionDataContainer<UOutput> => {
return createDataContainer<UOutput>(
this.options.factory(innerParams, {
node,
config: innerContext?.config ?? config,
inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden
}),
);
return args.factory(
(
innerParams: TParams,
innerContext?: {
config?: {
[key in keyof TConfigSchema]: z.infer<
ReturnType<TConfigSchema[key]>
>;
};
inputs?: Expand<ResolvedExtensionInputs<TInputs>>;
},
{
node,
config,
inputs,
},
);
} else if (args.params) {
return this.options.factory(args.params, {
): ExtensionDataContainer<UOutput> => {
return createDataContainer<UOutput>(
this.options.factory(innerParams, {
node,
config: innerContext?.config ?? config,
inputs: (innerContext?.inputs ?? inputs) as any, // TODO: Fix the way input values are overridden
}),
);
},
{
node,
config,
// TODO: Figure out types once legacy data map input type is gone
inputs: inputs as unknown as Expand<
ResolvedExtensionInputs<TInputs>
>,
});
}
throw new Error('Either params or factory must be provided');
inputs,
},
);
},
} as CreateExtensionOptions<TKind, string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, string | undefined extends TNewName ? TName : TNewName, AnyExtensionDataRef extends UNewOutput ? UOutput : UNewOutput, TInputs & TExtraInputs, TConfigSchema & TExtensionConfigSchema, UFactoryOutput>);
}
public make<
TNewNamespace extends string | undefined = undefined,
TNewName extends string | undefined = undefined,
>(args: {
namespace?: TNewNamespace;
name?: TNewName;
attachTo?: { id: string; input: string };
disabled?: boolean;
params: TParams;
}): ExtensionDefinition<
{
[key in keyof TConfigSchema]: z.infer<ReturnType<TConfigSchema[key]>>;
},
z.input<
z.ZodObject<{
[key in keyof TConfigSchema]: ReturnType<TConfigSchema[key]>;
}>
>
> {
return createExtension({
kind: this.options.kind,
namespace: args.namespace ?? this.options.namespace,
name: args.name ?? this.options.name,
attachTo: args.attachTo ?? this.options.attachTo,
disabled: args.disabled ?? this.options.disabled,
inputs: this.options.inputs,
output: this.options.output,
config: this.options.config,
factory: ctx => this.options.factory(args.params, ctx),
} as CreateExtensionOptions<TKind, string | undefined extends TNewNamespace ? TNamespace : TNewNamespace, string | undefined extends TNewName ? TName : TNewName, UOutput, TInputs, TConfigSchema, any>);
}
}
/**
@@ -19,7 +19,6 @@ import { MemoryRouter, Link } from 'react-router-dom';
import { RenderResult, render } from '@testing-library/react';
import { createSpecializedApp } from '@backstage/frontend-app-api';
import {
ExtensionDataValue,
AppNode,
AppTree,
Extension,
@@ -169,9 +168,7 @@ export class ExtensionTester {
: [...internal.output, coreExtensionData.routePath],
factory: params => {
const parentOutput = Array.from(
internal.factory(params as any) as Iterable<
ExtensionDataValue<any, any>
>,
internal.factory(params as any),
).filter(val => val.id !== coreExtensionData.routePath.id);
return [...parentOutput, coreExtensionData.routePath('/')];