diff --git a/packages/core-api/src/lib/extensions.test.tsx b/packages/core-api/src/lib/extensions.test.tsx new file mode 100644 index 0000000000..92fcecebc4 --- /dev/null +++ b/packages/core-api/src/lib/extensions.test.tsx @@ -0,0 +1,72 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 from 'react'; +import { createPlugin } from '../plugin'; +import { createRouteRef } from '../routing'; +import { getComponentData } from './componentData'; +import { + createComponentExtension, + createReactExtension, + createRoutableExtension, +} from './extensions'; + +const plugin = createPlugin({ + id: 'my-plugin', +}); + +describe('extensions', () => { + it('should create a react extension with component data', () => { + const Component = () => null; + + const extension = createReactExtension({ + component: Component, + data: { + myData: { foo: 'bar' }, + }, + }); + + const ExtensionComponent = plugin.provide(extension); + const element = ; + + expect(getComponentData(element, 'core.plugin')).toBe(plugin); + expect(getComponentData(element, 'myData')).toEqual({ foo: 'bar' }); + }); + + it('should create react extensions of different types', () => { + const Component = () => null; + const routeRef = createRouteRef({ path: '/foo', title: 'Foo' }); + + const extension1 = createComponentExtension({ + component: Component, + }); + + const extension2 = createRoutableExtension({ + component: Component, + mountPoint: routeRef, + }); + + const ExtensionComponent1 = plugin.provide(extension1); + const ExtensionComponent2 = plugin.provide(extension2); + + const element1 = ; + const element2 = ; + + expect(getComponentData(element1, 'core.plugin')).toBe(plugin); + expect(getComponentData(element2, 'core.plugin')).toBe(plugin); + expect(getComponentData(element2, 'core.mountPoint')).toBe(routeRef); + }); +}); diff --git a/packages/core-api/src/lib/extensions.tsx b/packages/core-api/src/lib/extensions.tsx new file mode 100644 index 0000000000..c0a71a2a90 --- /dev/null +++ b/packages/core-api/src/lib/extensions.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { ExoticComponent, ComponentType } from 'react'; +import { RouteRef } from '../routing'; +import { attachComponentData } from './componentData'; +import { Extension, BackstagePlugin } from '../plugin/types'; + +export function createRoutableExtension(options: { + component: ComponentType; + mountPoint: RouteRef; +}): Extension> { + const { component, mountPoint } = options; + return createReactExtension({ + component, + data: { + 'core.mountPoint': mountPoint, + }, + }); +} + +export function createComponentExtension(options: { + component: ComponentType; +}): Extension> { + const { component } = options; + return createReactExtension({ component }); +} + +export function createReactExtension(options: { + component: ComponentType; + data?: Record; +}): Extension> { + const { component: Component, data = {} } = options; + return { + expose(plugin: BackstagePlugin): ExoticComponent { + const Result = (props: Props) => ; + + attachComponentData(Result, 'core.plugin', plugin); + for (const [key, value] of Object.entries(data)) { + attachComponentData(Result, key, value); + } + + return Result as ExoticComponent; + }, + }; +} diff --git a/packages/core-api/src/plugin/Plugin.tsx b/packages/core-api/src/plugin/Plugin.tsx index d69bb7e721..f477674f4d 100644 --- a/packages/core-api/src/plugin/Plugin.tsx +++ b/packages/core-api/src/plugin/Plugin.tsx @@ -14,10 +14,15 @@ * limitations under the License. */ -import { PluginConfig, PluginOutput, BackstagePlugin } from './types'; +import { + PluginConfig, + PluginOutput, + BackstagePlugin, + Extension, +} from './types'; import { AnyApiFactory } from '../apis'; -export class PluginImpl { +export class PluginImpl implements BackstagePlugin { private storedOutput?: PluginOutput[]; constructor(private readonly config: PluginConfig) {} @@ -65,6 +70,10 @@ export class PluginImpl { return this.storedOutput; } + provide(extension: Extension): T { + return extension.expose(this); + } + toString() { return `plugin{${this.config.id}}`; } diff --git a/packages/core-api/src/plugin/types.ts b/packages/core-api/src/plugin/types.ts index 855264c5f4..dd19948a0a 100644 --- a/packages/core-api/src/plugin/types.ts +++ b/packages/core-api/src/plugin/types.ts @@ -66,10 +66,15 @@ export type PluginOutput = | RedirectRouteOutput | FeatureFlagOutput; +export type Extension = { + expose(plugin: BackstagePlugin): T; +}; + export type BackstagePlugin = { getId(): string; output(): PluginOutput[]; getApis(): Iterable; + provide(extension: Extension): T; }; export type PluginConfig = {