Make utility hook responses async, allowing addons to handle errors independently.
Co-authored-by: Emma Indal <emma.indahl@gmail.com> Co-authored-by: Anders Näsman <andersn@spotify.com> Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
committed by
Emma Indal
parent
d8db6db3d7
commit
e328e3d31b
@@ -5,6 +5,7 @@
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
import { AsyncState } from 'react-use/lib/useAsyncFn';
|
||||
import { ComponentType } from 'react';
|
||||
import { CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { Extension } from '@backstage/core-plugin-api';
|
||||
@@ -17,6 +18,9 @@ export function createTechDocsAddon<TComponentProps>(
|
||||
options: TechDocsAddonOptions<TComponentProps>,
|
||||
): Extension<ComponentType<TComponentProps>>;
|
||||
|
||||
// @public
|
||||
export type TechDocsAddonAsyncMetadata<TValue> = AsyncState<TValue | undefined>;
|
||||
|
||||
// @public
|
||||
export enum TechDocsAddonLocations {
|
||||
COMPONENT = 'component',
|
||||
@@ -48,16 +52,18 @@ export type TechDocsReaderPageProps = {
|
||||
};
|
||||
|
||||
// @public
|
||||
export const useEntityMetadata: () => TechDocsEntityMetadata | undefined;
|
||||
|
||||
// @public
|
||||
export const useMetadata: () => TechDocsMetadata | undefined;
|
||||
export const useEntityMetadata: () => TechDocsAddonAsyncMetadata<TechDocsEntityMetadata>;
|
||||
|
||||
// @public
|
||||
export const useShadowRoot: () => ShadowRoot | undefined;
|
||||
|
||||
// @public
|
||||
export const useShadowRootElements: <T extends HTMLElement = HTMLElement>(
|
||||
export const useShadowRootElements: <
|
||||
TReturnedElement extends HTMLElement = HTMLElement,
|
||||
>(
|
||||
selectors: string[],
|
||||
) => T[];
|
||||
) => TReturnedElement[];
|
||||
|
||||
// @public
|
||||
export const useTechDocsMetadata: () => TechDocsAddonAsyncMetadata<TechDocsMetadata>;
|
||||
```
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ import React, { useEffect } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
|
||||
import { useTechDocsAddons } from '../../addons';
|
||||
import { useMetadata, useTechDocsReaderPage } from '../../context';
|
||||
import { useTechDocsMetadata, useTechDocsReaderPage } from '../../context';
|
||||
import { TechDocsAddonLocations as locations } from '../../types';
|
||||
|
||||
const skeleton = <Skeleton animation="wave" variant="text" height={40} />;
|
||||
@@ -31,7 +31,7 @@ export const TechDocsReaderPageHeader = () => {
|
||||
const addons = useTechDocsAddons();
|
||||
const configApi = useApi(configApiRef);
|
||||
|
||||
const metadata = useMetadata();
|
||||
const { value: metadata } = useTechDocsMetadata();
|
||||
|
||||
const { title, setTitle, subtitle, setSubtitle } = useTechDocsReaderPage();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { useApi, useApp } from '@backstage/core-plugin-api';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
techdocsApiRef,
|
||||
TechDocsEntityMetadata,
|
||||
@@ -30,30 +30,33 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { TechDocsAddonAsyncMetadata } from './types';
|
||||
|
||||
type PropsWithEntityName = PropsWithChildren<{ entityName: CompoundEntityRef }>;
|
||||
|
||||
const TechDocsMetadataContext = createContext<TechDocsMetadata | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const initialContextValue = {
|
||||
loading: true,
|
||||
error: undefined,
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
const TechDocsMetadataContext =
|
||||
createContext<TechDocsAddonAsyncMetadata<TechDocsMetadata>>(
|
||||
initialContextValue,
|
||||
);
|
||||
|
||||
export const TechDocsMetadataProvider = ({
|
||||
entityName,
|
||||
children,
|
||||
}: PropsWithEntityName) => {
|
||||
const { NotFoundErrorPage } = useApp().getComponents();
|
||||
const techdocsApi = useApi(techdocsApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const metadataResponse = useAsync(async () => {
|
||||
return await techdocsApi.getTechDocsMetadata(entityName);
|
||||
}, []);
|
||||
|
||||
if (!loading && error) {
|
||||
return <NotFoundErrorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TechDocsMetadataContext.Provider value={value}>
|
||||
<TechDocsMetadataContext.Provider value={metadataResponse}>
|
||||
{children}
|
||||
</TechDocsMetadataContext.Provider>
|
||||
);
|
||||
@@ -64,31 +67,27 @@ export const TechDocsMetadataProvider = ({
|
||||
* current TechDocs site.
|
||||
* @public
|
||||
*/
|
||||
export const useMetadata = () => {
|
||||
export const useTechDocsMetadata = () => {
|
||||
return useContext(TechDocsMetadataContext);
|
||||
};
|
||||
|
||||
const TechDocsEntityContext = createContext<TechDocsEntityMetadata | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const TechDocsEntityContext =
|
||||
createContext<TechDocsAddonAsyncMetadata<TechDocsEntityMetadata>>(
|
||||
initialContextValue,
|
||||
);
|
||||
|
||||
export const TechDocsEntityProvider = ({
|
||||
entityName,
|
||||
children,
|
||||
}: PropsWithEntityName) => {
|
||||
const { NotFoundErrorPage } = useApp().getComponents();
|
||||
const techdocsApi = useApi(techdocsApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const metadataResponse = useAsync(async () => {
|
||||
return await techdocsApi.getEntityMetadata(entityName);
|
||||
}, []);
|
||||
|
||||
if (!loading && error) {
|
||||
return <NotFoundErrorPage />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TechDocsEntityContext.Provider value={value}>
|
||||
<TechDocsEntityContext.Provider value={metadataResponse}>
|
||||
{children}
|
||||
</TechDocsEntityContext.Provider>
|
||||
);
|
||||
@@ -180,13 +179,15 @@ export const useShadowRoot = () => {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const useShadowRootElements = <T extends HTMLElement = HTMLElement>(
|
||||
export const useShadowRootElements = <
|
||||
TReturnedElement extends HTMLElement = HTMLElement,
|
||||
>(
|
||||
selectors: string[],
|
||||
): T[] => {
|
||||
): TReturnedElement[] => {
|
||||
const shadowRoot = useShadowRoot();
|
||||
if (!shadowRoot) return [];
|
||||
return selectors
|
||||
.map(selector => shadowRoot?.querySelectorAll<T>(selector))
|
||||
.map(selector => shadowRoot?.querySelectorAll<TReturnedElement>(selector))
|
||||
.filter(nodeList => nodeList.length)
|
||||
.map(nodeList => Array.from(nodeList))
|
||||
.flat();
|
||||
|
||||
@@ -24,8 +24,12 @@ export { createTechDocsAddon, TechDocsAddons } from './addons';
|
||||
export * from './components';
|
||||
export {
|
||||
useEntityMetadata,
|
||||
useMetadata,
|
||||
useTechDocsMetadata,
|
||||
useShadowRoot,
|
||||
useShadowRootElements,
|
||||
} from './context';
|
||||
export type { TechDocsAddonLocations, TechDocsAddonOptions } from './types';
|
||||
export type {
|
||||
TechDocsAddonAsyncMetadata,
|
||||
TechDocsAddonLocations,
|
||||
TechDocsAddonOptions,
|
||||
} from './types';
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ComponentType } from 'react';
|
||||
import { AsyncState } from 'react-use/lib/useAsyncFn';
|
||||
|
||||
/**
|
||||
* Locations for which TechDocs addons may be declared and rendered.
|
||||
@@ -70,3 +71,9 @@ export type TechDocsAddonOptions<TAddonProps = {}> = {
|
||||
location: TechDocsAddonLocations;
|
||||
component: ComponentType<TAddonProps>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Common response envelope for addon-related hooks.
|
||||
* @public
|
||||
*/
|
||||
export type TechDocsAddonAsyncMetadata<TValue> = AsyncState<TValue | undefined>;
|
||||
|
||||
Reference in New Issue
Block a user