Allow addons to be created and registered.

Co-authored-by: Emma Indal <emma.indahl@gmail.com>
Co-authored-by: Camila Belo <camilaibs@gmail.com>
Co-authored-by: Otto Sichert <git@ottosichert.de>

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2022-03-15 13:15:25 +01:00
committed by Emma Indal
parent 19c0db60aa
commit 30d2ca2669
5 changed files with 193 additions and 3 deletions
+58 -2
View File
@@ -1,5 +1,61 @@
# @backstage/plugin-techdocs-addons
Package encapsulating the TechDocs Addons framework.
Package encapsulating the TechDocs Addon framework.
todo(backstage/techdocs-core): Fill in with real documentation!
## What is an addon?
An addon is a isolated piece of functionality that one can use to augment the
TechDocs experience at render-time. For example: an issue counter showing the
number of issues reported on the documentation, or the top contributors to the
documentation.
## Create a new addon
To create a new addon, you can use the `createTechDocsAddon` factory exported
from this plugin. Normally, addons are provided by Backstage plugins, which can
then be composed within a Backstage app.
When you create a new Addon, it requires three things.
1. A `name` for debugging and analytics purposes)
2. A `location`, indicating where/how the addon will be rendered
3. A `component`, encapsulating the addon's logic and functionality
```tsx
import {
createTechDocsAddon,
TechDocsAddonLocations,
} from '@backstage/plugin-techdocs-addons';
import { StackOverflowSecondarySidebarAddon } from './components';
export const StackOverflowSecondarySidebar = yourBackstagePlugin.provide(
createTechDocsAddon({
name: 'StackOverflowSecondarySidebar',
type: TechDocsAddonLocations.SECONDARY_SIDEBAR,
component: StackOverflowSecondarySidebarAddon,
}),
);
```
## Compose your app with addons
To configure which addons will augment the TechDocs experience in your
Backstage app, you need two things:
- The `TechDocsAddons` component, which is responsible for registering the
addons.
- A list of the addons themselves, as exported by their respective plugins.
```tsx
import {
TechDocsAddons,
TechDocsReaderPage,
} from '@backstage/plugin-techdocs-addons';
import { StackOverflowSecondarySidebar } from '@backstage/plugin-soe';
<Route path="/docs/:namespace/:kind/:name/*" element={<TechDocsReaderPage />}>
<TechDocsAddons>
<StackOverflowSecondarySidebar />
</TechDocsAddons>
</Route>;
```
+10
View File
@@ -4,6 +4,13 @@
```ts
import { ComponentType } from 'react';
import { Extension } from '@backstage/core-plugin-api';
import { default as React_2 } from 'react';
// @public
export function createTechDocsAddon<TComponentProps>(
options: TechDocsAddonOptions<TComponentProps>,
): Extension<ComponentType<TComponentProps>>;
// @public
export enum TechDocsAddonLocations {
@@ -21,4 +28,7 @@ export type TechDocsAddonOptions<TAddonProps = {}> = {
location: TechDocsAddonLocations;
component: ComponentType<TAddonProps>;
};
// @public
export const TechDocsAddons: React_2.ComponentType;
```
+4 -1
View File
@@ -21,7 +21,10 @@
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {},
"dependencies": {
"@backstage/core-plugin-api": "^0.8.0",
"react-router-dom": "6.0.0-beta.0"
},
"peerDependencies": {
"@types/react": "^16.13.1 || ^17.0.0",
"react": "^16.13.1 || ^17.0.0"
+120
View File
@@ -0,0 +1,120 @@
/*
* Copyright 2022 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 {
attachComponentData,
createReactExtension,
ElementCollection,
Extension,
useElementFilter,
} from '@backstage/core-plugin-api';
import React, { ComponentType, useCallback } from 'react';
import { useOutlet } from 'react-router-dom';
import { TechDocsAddonLocations, TechDocsAddonOptions } from './types';
export const TECHDOCS_ADDONS_KEY = 'techdocs.addons.addon.v1';
export const TECHDOCS_ADDONS_WRAPPER_KEY = 'techdocs.addons.wrapper.v1';
/**
* TechDocs Addon registry.
* @public
*/
export const TechDocsAddons: React.ComponentType = () => null;
attachComponentData(TechDocsAddons, TECHDOCS_ADDONS_WRAPPER_KEY, true);
const getDataKeyByName = (name: string) => {
return `${TECHDOCS_ADDONS_KEY}.${name.toLocaleLowerCase('en-US')}`;
};
/**
* Create a TechDocs addon.
* @public
*/
export function createTechDocsAddon<TComponentProps>(
options: TechDocsAddonOptions<TComponentProps>,
): Extension<ComponentType<TComponentProps>> {
const { name, component: TechDocsAddon } = options;
return createReactExtension({
name,
component: {
sync: (props: TComponentProps) => <TechDocsAddon {...props} />,
},
data: {
[TECHDOCS_ADDONS_KEY]: options,
[getDataKeyByName(name)]: true,
},
});
}
const getTechDocsAddonByName = (collection: ElementCollection, key: string) => {
return collection.selectByComponentData({ key }).getElements()[0];
};
const getAllTechDocsAddons = (collection: ElementCollection) => {
return collection
.selectByComponentData({
key: TECHDOCS_ADDONS_WRAPPER_KEY,
})
.selectByComponentData({
key: TECHDOCS_ADDONS_KEY,
});
};
const getAllTechDocsAddonsData = (collection: ElementCollection) => {
return collection
.selectByComponentData({
key: TECHDOCS_ADDONS_WRAPPER_KEY,
})
.findComponentData<TechDocsAddonOptions>({
key: TECHDOCS_ADDONS_KEY,
});
};
export const useTechDocsAddons = () => {
const node = useOutlet();
const collection = useElementFilter(node, getAllTechDocsAddons);
const options = useElementFilter(node, getAllTechDocsAddonsData);
const findAddonByData = useCallback(
(data: TechDocsAddonOptions | undefined) => {
if (!collection || !data) return null;
const nameKey = getDataKeyByName(data.name);
return getTechDocsAddonByName(collection, nameKey) ?? null;
},
[collection],
);
const renderComponentWithName = useCallback(
(name: string) => {
const data = options.find(option => option.name === name);
return data ? findAddonByData(data) : null;
},
[options, findAddonByData],
);
const renderComponentsWithLocation = useCallback(
(location: TechDocsAddonLocations) => {
const data = options.filter(option => option.location === location);
return data.length ? data.map(findAddonByData) : null;
},
[options, findAddonByData],
);
return { renderComponentWithName, renderComponentsWithLocation };
};
+1
View File
@@ -20,4 +20,5 @@
* @packageDocumentation
*/
export { createTechDocsAddon, TechDocsAddons } from './addons';
export type { TechDocsAddonLocations, TechDocsAddonOptions } from './types';