diff --git a/.changeset/techdocs-bob-law-blog.md b/.changeset/techdocs-bob-law-blog.md new file mode 100644 index 0000000000..581ab19bec --- /dev/null +++ b/.changeset/techdocs-bob-law-blog.md @@ -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. diff --git a/.changeset/techdocs-steve-holt.md b/.changeset/techdocs-steve-holt.md new file mode 100644 index 0000000000..c456d48cf8 --- /dev/null +++ b/.changeset/techdocs-steve-holt.md @@ -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. diff --git a/plugins/techdocs-addons-test-utils/api-report.md b/plugins/techdocs-addons-test-utils/api-report.md index 6374707b90..3808b65b84 100644 --- a/plugins/techdocs-addons-test-utils/api-report.md +++ b/plugins/techdocs-addons-test-utils/api-report.md @@ -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 >; - // (undocumented) static buildAddonsInTechDocs(addons: ReactElement[]): TechDocsAddonTester; - // (undocumented) renderWithEffects(): Promise< typeof screen_2 & { shadowRoot: ShadowRoot | null; } >; - // (undocumented) withApis(apis: TechdocsAddonTesterApis): this; - // (undocumented) withDom(dom: ReactElement): this; - // (undocumented) withEntity(entity: Partial): this; - // (undocumented) withMetadata(metadata: Partial): this; } ``` diff --git a/plugins/techdocs-addons-test-utils/src/test-utils.tsx b/plugins/techdocs-addons-test-utils/src/test-utils.tsx index 0bb46001f7..2c20199038 100644 --- a/plugins/techdocs-addons-test-utils/src/test-utils.tsx +++ b/plugins/techdocs-addons-test-utils/src/test-utils.tsx @@ -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([]) + * .withDom(TEST_CONTENT) + * .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(apis: TechdocsAddonTesterApis) { 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) { 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) { 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 = [ [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 } > { diff --git a/plugins/techdocs-module-addons-contrib/api-report.md b/plugins/techdocs-module-addons-contrib/api-report.md index 490b414141..19d2f70a2b 100644 --- a/plugins/techdocs-module-addons-contrib/api-report.md +++ b/plugins/techdocs-module-addons-contrib/api-report.md @@ -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, }: { diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx index 2fe8d604e6..c8caa5ddbc 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/ReportIssue.tsx @@ -49,10 +49,21 @@ type Style = { }; /** + * Props customizing the 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; }; diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/types.ts b/plugins/techdocs-module-addons-contrib/src/ReportIssue/types.ts index a4780535ca..0816b45d9d 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/types.ts +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/types.ts @@ -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 = ({ diff --git a/plugins/techdocs-react/api-report.md b/plugins/techdocs-react/api-report.md index d3803d6794..e96cbebd1c 100644 --- a/plugins/techdocs-react/api-report.md +++ b/plugins/techdocs-react/api-report.md @@ -14,21 +14,18 @@ import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { SetStateAction } from 'react'; -// @alpha +// @public export function createTechDocsAddonExtension( options: TechDocsAddonOptions, ): 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 = { name: string; location: keyof typeof TechDocsAddonLocations; component: ComponentType; }; -// @alpha +// @public export const TechDocsAddons: React_2.ComponentType; // @public @@ -133,20 +130,20 @@ export interface TechDocsStorageApi { // @public export const techdocsStorageApiRef: ApiRef; -// @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; ``` diff --git a/plugins/techdocs-react/src/addons.tsx b/plugins/techdocs-react/src/addons.tsx index 39d110bee3..49aa5b1bcc 100644 --- a/plugins/techdocs-react/src/addons.tsx +++ b/plugins/techdocs-react/src/addons.tsx @@ -31,13 +31,13 @@ export const TECHDOCS_ADDONS_KEY = 'techdocs.addons.addon.v1'; /** * Marks the 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( options: TechDocsAddonOptions, @@ -96,7 +96,7 @@ const getAllTechDocsAddonsData = (collection: ElementCollection) => { /** * hook to use addons in components - * @alpha + * @public */ export const useTechDocsAddons = () => { const node = useOutlet(); diff --git a/plugins/techdocs-react/src/context.tsx b/plugins/techdocs-react/src/context.tsx index 67bf416818..4f2fa26164 100644 --- a/plugins/techdocs-react/src/context.tsx +++ b/plugins/techdocs-react/src/context.tsx @@ -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); diff --git a/plugins/techdocs-react/src/hooks.ts b/plugins/techdocs-react/src/hooks.ts index cc141c9e15..560c548156 100644 --- a/plugins/techdocs-react/src/hooks.ts +++ b/plugins/techdocs-react/src/hooks.ts @@ -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(); diff --git a/plugins/techdocs-react/src/index.ts b/plugins/techdocs-react/src/index.ts index c1cc3a1791..d4de5f0d29 100644 --- a/plugins/techdocs-react/src/index.ts +++ b/plugins/techdocs-react/src/index.ts @@ -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, diff --git a/plugins/techdocs-react/src/types.ts b/plugins/techdocs-react/src/types.ts index e147cd1dd1..d1fcc91db0 100644 --- a/plugins/techdocs-react/src/types.ts +++ b/plugins/techdocs-react/src/types.ts @@ -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 = { name: string; diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx index fbab933571..7c76f75eff 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/TechDocsReaderPageContent.tsx @@ -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: { diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 7a373ab302..92bfbb2e14 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -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 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, diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/index.ts b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/index.ts index a7ea76a5d6..b0e2ff856a 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/index.ts +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/index.ts @@ -16,5 +16,3 @@ export { TechDocsReaderPageContent, Reader } from './TechDocsReaderPageContent'; export type { TechDocsReaderPageContentProps } from './TechDocsReaderPageContent'; -export * from './context'; -export * from './dom'; diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/context.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderProvider.tsx similarity index 73% rename from plugins/techdocs/src/reader/components/TechDocsReaderPageContent/context.tsx rename to plugins/techdocs/src/reader/components/TechDocsReaderProvider.tsx index 5779ade90c..8ddc4cad69 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/context.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderProvider.tsx @@ -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({} as ReaderState); -/** - * Note: this hook is currently being exported so that we can rapidly - * iterate on alternative 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 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 = (Component: ComponentType) => (props: T) => diff --git a/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx b/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx index 93694280d7..6967356194 100644 --- a/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx @@ -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 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(); diff --git a/plugins/techdocs/src/reader/components/index.ts b/plugins/techdocs/src/reader/components/index.ts index 406d623bda..f063501814 100644 --- a/plugins/techdocs/src/reader/components/index.ts +++ b/plugins/techdocs/src/reader/components/index.ts @@ -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';