Merge pull request #11459 from backstage/techdocs/addons-out-of-alpha

[TechDocs] Mark the Addon Framework as generally available
This commit is contained in:
Eric Peterson
2022-05-11 18:10:05 +02:00
committed by GitHub
19 changed files with 119 additions and 93 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/plugin-techdocs-react': major
'@backstage/plugin-techdocs-addons-test-utils': major
'@backstage/plugin-techdocs-module-addons-contrib': major
---
The TechDocs Addon framework is now generally available.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Hidden exports related to experimental TechDocs reader functionality have been removed and can no longer be imported. In the unlikely event you were using these exports, you can now take advantage of the officially supported and generally available TechDocs Addon framework instead.
@@ -10,31 +10,23 @@ import { screen as screen_2 } from 'testing-library__dom';
import { TechDocsEntityMetadata } from '@backstage/plugin-techdocs-react';
import { TechDocsMetadata } from '@backstage/plugin-techdocs-react';
// @public (undocumented)
// @public
export class TechDocsAddonTester {
protected constructor(addons: ReactElement[]);
// (undocumented)
atPath(path: string): this;
// (undocumented)
build(): React_2.ReactElement<
any,
string | React_2.JSXElementConstructor<any>
>;
// (undocumented)
static buildAddonsInTechDocs(addons: ReactElement[]): TechDocsAddonTester;
// (undocumented)
renderWithEffects(): Promise<
typeof screen_2 & {
shadowRoot: ShadowRoot | null;
}
>;
// (undocumented)
withApis<T extends any[]>(apis: TechdocsAddonTesterApis<T>): this;
// (undocumented)
withDom(dom: ReactElement): this;
// (undocumented)
withEntity(entity: Partial<TechDocsEntityMetadata>): this;
// (undocumented)
withMetadata(metadata: Partial<TechDocsMetadata>): this;
}
```
@@ -113,13 +113,27 @@ const defaultDom = (
);
/**
* Utility class for rendering TechDocs Addons end-to-end within the TechDocs
* reader page, with a set of givens (e.g. page DOM, metadata, etc).
*
* @example
* ```tsx
* const { getByText } = await TechDocsAddonTester.buildAddonsInTechDocs([<AnAddon />])
* .withDom(<body>TEST_CONTENT</body>)
* .renderWithEffects();
*
* expect(getByText('TEST_CONTENT')).toBeInTheDocument();
* ```
*
* @public
*/
export class TechDocsAddonTester {
private options: TechDocsAddonTesterOptions = defaultOptions;
private addons: ReactElement[];
/**
* Get a TechDocsAddonTester instance for a given set of Addons.
*/
static buildAddonsInTechDocs(addons: ReactElement[]) {
return new TechDocsAddonTester(addons);
}
@@ -129,6 +143,9 @@ export class TechDocsAddonTester {
this.addons = addons;
}
/**
* Provide mock API implementations if your Addon expects any.
*/
withApis<T extends any[]>(apis: TechdocsAddonTesterApis<T>) {
const refs = apis.map(([ref]) => ref);
this.options.apis = this.options.apis
@@ -137,26 +154,44 @@ export class TechDocsAddonTester {
return this;
}
/**
* Provide mock HTML if your Addon expects it in the shadow DOM.
*/
withDom(dom: ReactElement) {
this.options.dom = dom;
return this;
}
/**
* Provide mock techdocs_metadata.json values if your Addon needs it.
*/
withMetadata(metadata: Partial<TechDocsMetadata>) {
this.options.metadata = metadata;
return this;
}
/**
* Provide a mock entity if your Addon needs it. This also controls the base
* path at which the Addon is rendered.
*/
withEntity(entity: Partial<TechDocsEntityMetadata>) {
this.options.entity = entity;
return this;
}
/**
* Provide the TechDocs page path at which the Addon is rendered (e.g. the
* part of the path after the entity namespace/kind/name).
*/
atPath(path: string) {
this.options.path = path;
return this;
}
/**
* Return a fully configured and mocked TechDocs reader page within a test
* App instance, using the given Addon(s).
*/
build() {
const apis: TechdocsAddonTesterApis<any[]> = [
[techdocsApiRef, techdocsApi],
@@ -223,11 +258,19 @@ export class TechDocsAddonTester {
});
}
// Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async
// act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit
// cleaner, since act doesn't return the result of the evaluated function.
// https://github.com/testing-library/react-testing-library/issues/281
// https://github.com/facebook/react/pull/14853
/**
* Render the Addon within a fully configured and mocked TechDocs reader.
*
* @remarks
* Components using useEffect to perform an asynchronous action (such as
* fetch) must be rendered within an async act call to properly get the final
* state, even with mocked responses. This utility method makes the signature
* a bit cleaner, since act doesn't return the result of the evaluated
* function.
*
* @see https://github.com/testing-library/react-testing-library/issues/281
* @see https://github.com/facebook/react/pull/14853
*/
async renderWithEffects(): Promise<
typeof screen & { shadowRoot: ShadowRoot | null }
> {
@@ -10,19 +10,19 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
// @public
export const ReportIssue: (props: ReportIssueProps) => JSX.Element | null;
// @public (undocumented)
// @public
export type ReportIssueProps = {
debounceTime?: number;
templateBuilder?: ReportIssueTemplateBuilder;
};
// @public (undocumented)
// @public
export type ReportIssueTemplate = {
title: string;
body: string;
};
// @public (undocumented)
// @public
export type ReportIssueTemplateBuilder = ({
selection,
}: {
@@ -49,10 +49,21 @@ type Style = {
};
/**
* Props customizing the <ReportIssue /> Addon.
*
* @public
*/
export type ReportIssueProps = {
/**
* Number of milliseconds after a user highlights some text before the report
* issue link appears above the highlighted text. Defaults to 500ms.
*/
debounceTime?: number;
/**
* An optional function defining how a custom issue title and body should be
* constructed, given some selected text.
*/
templateBuilder?: ReportIssueTemplateBuilder;
};
@@ -15,14 +15,26 @@
*/
/**
* Properties for creating an issue in a remote issue tracker.
*
* @public
*/
export type ReportIssueTemplate = {
/**
* The title of the issue.
*/
title: string;
/**
* The body or description of the issue.
*/
body: string;
};
/**
* A function for returning a custom issue template, given a selection of text
* on a TechDocs page.
*
* @public
*/
export type ReportIssueTemplateBuilder = ({
+10 -13
View File
@@ -14,21 +14,18 @@ import { default as React_2 } from 'react';
import { ReactNode } from 'react';
import { SetStateAction } from 'react';
// @alpha
// @public
export function createTechDocsAddonExtension<TComponentProps>(
options: TechDocsAddonOptions<TComponentProps>,
): Extension<(props: TComponentProps) => JSX.Element | null>;
// @alpha (undocumented)
export const defaultTechDocsReaderPageValue: TechDocsReaderPageValue;
// @public
export type SyncResult = 'cached' | 'updated';
// @alpha
// @public
export const TECHDOCS_ADDONS_WRAPPER_KEY = 'techdocs.addons.wrapper.v1';
// @alpha
// @public
export const TechDocsAddonLocations: Readonly<{
readonly Header: 'Header';
readonly Subheader: 'Subheader';
@@ -38,14 +35,14 @@ export const TechDocsAddonLocations: Readonly<{
readonly Content: 'Content';
}>;
// @alpha
// @public
export type TechDocsAddonOptions<TAddonProps = {}> = {
name: string;
location: keyof typeof TechDocsAddonLocations;
component: ComponentType<TAddonProps>;
};
// @alpha
// @public
export const TechDocsAddons: React_2.ComponentType;
// @public
@@ -133,20 +130,20 @@ export interface TechDocsStorageApi {
// @public
export const techdocsStorageApiRef: ApiRef<TechDocsStorageApi>;
// @alpha
// @public
export const useShadowRoot: () => ShadowRoot | undefined;
// @alpha
// @public
export const useShadowRootElements: <
TReturnedElement extends HTMLElement = HTMLElement,
>(
selectors: string[],
) => TReturnedElement[];
// @alpha
// @public
export const useShadowRootSelection: (wait?: number) => Selection | null;
// @alpha
// @public
export const useTechDocsAddons: () => {
renderComponentByName: (name: string) => JSX.Element | null;
renderComponentsByLocation: (
@@ -154,6 +151,6 @@ export const useTechDocsAddons: () => {
) => (JSX.Element | null)[] | null;
};
// @alpha
// @public
export const useTechDocsReaderPage: () => TechDocsReaderPageValue;
```
+4 -4
View File
@@ -31,13 +31,13 @@ export const TECHDOCS_ADDONS_KEY = 'techdocs.addons.addon.v1';
/**
* Marks the <TechDocsAddons> registry component.
* @alpha
* @public
*/
export const TECHDOCS_ADDONS_WRAPPER_KEY = 'techdocs.addons.wrapper.v1';
/**
* TechDocs Addon registry.
* @alpha
* @public
*/
export const TechDocsAddons: React.ComponentType = () => null;
@@ -49,7 +49,7 @@ const getDataKeyByName = (name: string) => {
/**
* Create a TechDocs addon.
* @alpha
* @public
*/
export function createTechDocsAddonExtension<TComponentProps>(
options: TechDocsAddonOptions<TComponentProps>,
@@ -96,7 +96,7 @@ const getAllTechDocsAddonsData = (collection: ElementCollection) => {
/**
* hook to use addons in components
* @alpha
* @public
*/
export const useTechDocsAddons = () => {
const node = useOutlet();
+2 -5
View File
@@ -66,10 +66,7 @@ export type TechDocsReaderPageValue = {
onReady?: () => void;
};
/**
* @alpha
*/
export const defaultTechDocsReaderPageValue: TechDocsReaderPageValue = {
const defaultTechDocsReaderPageValue: TechDocsReaderPageValue = {
title: '',
subtitle: '',
setTitle: () => {},
@@ -153,7 +150,7 @@ export const TechDocsReaderPageProvider = memo(
/**
* Hook used to get access to shared state between reader page components.
* @alpha
* @public
*/
export const useTechDocsReaderPage = () => {
const versionedContext = useContext(TechDocsReaderPageContext);
+3 -3
View File
@@ -20,7 +20,7 @@ import { useTechDocsReaderPage } from './context';
/**
* Hook for use within TechDocs addons that provides access to the underlying
* shadow root of the current page, allowing the DOM within to be mutated.
* @alpha
* @public
*/
export const useShadowRoot = () => {
const { shadowRoot } = useTechDocsReaderPage();
@@ -31,7 +31,7 @@ export const useShadowRoot = () => {
* Convenience hook for use within TechDocs addons that provides access to
* elements that match a given selector within the shadow root.
*
* @alpha
* @public
*/
export const useShadowRootElements = <
TReturnedElement extends HTMLElement = HTMLElement,
@@ -58,7 +58,7 @@ const isValidSelection = (newSelection: Selection) => {
/**
* Hook for retreiving a selection within the ShadowRoot.
* @alpha
* @public
*/
export const useShadowRootSelection = (wait: number = 0) => {
const shadowRoot = useShadowRoot();
+1 -5
View File
@@ -28,11 +28,7 @@ export {
} from './addons';
export { techdocsApiRef, techdocsStorageApiRef } from './api';
export type { SyncResult, TechDocsApi, TechDocsStorageApi } from './api';
export {
defaultTechDocsReaderPageValue,
TechDocsReaderPageProvider,
useTechDocsReaderPage,
} from './context';
export { TechDocsReaderPageProvider, useTechDocsReaderPage } from './context';
export type {
TechDocsReaderPageProviderProps,
TechDocsReaderPageProviderRenderFunction,
+2 -2
View File
@@ -38,7 +38,7 @@ export type TechDocsEntityMetadata = Entity & {
/**
* Locations for which TechDocs addons may be declared and rendered.
* @alpha
* @public
*/
export const TechDocsAddonLocations = Object.freeze({
/**
@@ -108,7 +108,7 @@ export const TechDocsAddonLocations = Object.freeze({
/**
* Options for creating a TechDocs addon.
* @alpha
* @public
*/
export type TechDocsAddonOptions<TAddonProps = {}> = {
name: string;
@@ -32,7 +32,7 @@ import { TechDocsSearch } from '../../../search';
import { TechDocsStateIndicator } from '../TechDocsStateIndicator';
import { useTechDocsReaderDom } from './dom';
import { withTechDocsReaderProvider } from './context';
import { withTechDocsReaderProvider } from '../TechDocsReaderProvider';
const useStyles = makeStyles({
search: {
@@ -28,7 +28,7 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { techdocsStorageApiRef } from '@backstage/plugin-techdocs-react';
import { useTechDocsReader } from './context';
import { useTechDocsReader } from '../TechDocsReaderProvider';
import {
addBaseUrl,
@@ -58,14 +58,6 @@ const headings: TypographyHeadingsKeys[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
* Hook that encapsulates the behavior of getting raw HTML and applying
* transforms to it in order to make it function at a basic level in the
* Backstage UI.
*
* Note: this hook is currently being exported so that we can rapidly iterate
* on alternative <Reader /> implementations that extend core functionality.
* There is no guarantee that this hook will continue to be exported by the
* package in the future!
*
* todo: Make public or stop exporting (see others: "altReaderExperiments")
* @internal
*/
export const useTechDocsReaderDom = (
entityRef: CompoundEntityRef,
@@ -16,5 +16,3 @@
export { TechDocsReaderPageContent, Reader } from './TechDocsReaderPageContent';
export type { TechDocsReaderPageContentProps } from './TechDocsReaderPageContent';
export * from './context';
export * from './dom';
@@ -23,20 +23,10 @@ import React, {
import { useParams } from 'react-router-dom';
import { useTechDocsReaderPage } from '@backstage/plugin-techdocs-react';
import { useReaderState, ReaderState } from '../useReaderState';
import { useReaderState, ReaderState } from './useReaderState';
const TechDocsReaderContext = createContext<ReaderState>({} as ReaderState);
/**
* Note: this hook is currently being exported so that we can rapidly
* iterate on alternative <Reader /> implementations that extend core
* functionality. There is no guarantee that this hook will continue to be
* exported by the package in the future!
*
* todo: Make public or stop exporting (ctrl+f "altReaderExperiments")
* @internal
*/
export const useTechDocsReader = () => useContext(TechDocsReaderContext);
/**
@@ -73,15 +63,6 @@ export const TechDocsReaderProvider = ({
);
};
/**
* Note: this HOC is currently being exported so that we can rapidly
* iterate on alternative <Reader /> implementations that extend core
* functionality. There is no guarantee that this HOC will continue to be
* exported by the package in the future!
*
* todo: Make public or stop exporting (ctrl+f "altReaderExperiments")
* @internal
*/
export const withTechDocsReaderProvider =
<T extends {}>(Component: ComponentType<T>) =>
(props: T) =>
@@ -21,7 +21,7 @@ import { Alert } from '@material-ui/lab';
import { TechDocsBuildLogs } from './TechDocsBuildLogs';
import { TechDocsNotFound } from './TechDocsNotFound';
import { useTechDocsReader } from './TechDocsReaderPageContent';
import { useTechDocsReader } from './TechDocsReaderProvider';
const useStyles = makeStyles(theme => ({
root: {
@@ -35,15 +35,6 @@ const useStyles = makeStyles(theme => ({
},
}));
/**
* Note: this component is currently being exported so that we can rapidly
* iterate on alternative <Reader /> implementations that extend core
* functionality. There is no guarantee that this component will continue to be
* exported by the package in the future!
*
* todo: Make public or stop exporting (ctrl+f "altReaderExperiments")
* @internal
*/
export const TechDocsStateIndicator = () => {
let StateAlert: JSX.Element | null = null;
const classes = useStyles();
@@ -14,6 +14,11 @@
* limitations under the License.
*/
export { TechDocsReaderProvider } from './TechDocsReaderProvider';
export type {
TechDocsReaderProviderRenderFunction,
TechDocsReaderProviderProps,
} from './TechDocsReaderProvider';
export type {
TechDocsReaderPageProps,
TechDocsReaderLayoutProps,
@@ -22,5 +27,4 @@ export { TechDocsReaderLayout } from './TechDocsReaderPage';
export * from './TechDocsReaderPageHeader';
export * from './TechDocsReaderPageContent';
export * from './TechDocsReaderPageSubheader';
export * from './TechDocsStateIndicator';
export type { ReaderState, ContentStateTypes } from './useReaderState';