From ea81a1f19cdeef9c41bdc8a57fc96df86a7656fb Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 11 Sep 2021 13:12:56 +0200 Subject: [PATCH] catalog-react: deprecate EntityContext and add AsyncEntityProvider Signed-off-by: Patrik Oldsberg --- .changeset/weak-drinks-promise.md | 5 + plugins/catalog-react/api-report.md | 41 +++- plugins/catalog-react/package.json | 1 + .../EntityProvider/EntityProvider.tsx | 35 ---- .../src/components/EntityProvider/index.ts | 16 -- plugins/catalog-react/src/components/index.ts | 1 - plugins/catalog-react/src/hooks/index.ts | 12 +- plugins/catalog-react/src/hooks/useEntity.ts | 70 ------- plugins/catalog-react/src/hooks/useEntity.tsx | 180 ++++++++++++++++++ 9 files changed, 230 insertions(+), 131 deletions(-) create mode 100644 .changeset/weak-drinks-promise.md delete mode 100644 plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx delete mode 100644 plugins/catalog-react/src/components/EntityProvider/index.ts delete mode 100644 plugins/catalog-react/src/hooks/useEntity.ts create mode 100644 plugins/catalog-react/src/hooks/useEntity.tsx diff --git a/.changeset/weak-drinks-promise.md b/.changeset/weak-drinks-promise.md new file mode 100644 index 0000000000..79853dd307 --- /dev/null +++ b/.changeset/weak-drinks-promise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Deprecated EntityContext in favor of using `useEntity`, `EntityProvider` and the new `AsyncEntityProvider` instead. This update also brings cross-version compatibility to `@backstage/catalog-react`, meaning that future versions can be used in parallel with this one. diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 5f65426ed3..f6ae4818f7 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -25,6 +25,29 @@ import { SystemEntity } from '@backstage/catalog-model'; import { TableColumn } from '@backstage/core-components'; import { UserEntity } from '@backstage/catalog-model'; +// @public +export const AsyncEntityProvider: ({ + children, + entity, + loading, + error, + refresh, +}: AsyncEntityProviderProps) => JSX.Element; + +// @public +export interface AsyncEntityProviderProps { + // (undocumented) + children: ReactNode; + // (undocumented) + entity?: Entity; + // (undocumented) + error?: Error; + // (undocumented) + loading: boolean; + // (undocumented) + refresh?: VoidFunction; +} + export { CATALOG_FILTER_EXISTS }; export { CatalogApi }; @@ -109,9 +132,8 @@ export type DefaultEntityFilters = { }; // Warning: (ae-forgotten-export) The symbol "EntityLoadingStatus" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "EntityContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public @deprecated (undocumented) export const EntityContext: Context; // Warning: (ae-missing-release-tag) "EntityFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -199,15 +221,20 @@ export class EntityOwnerFilter implements EntityFilter { // @public (undocumented) export const EntityOwnerPicker: () => JSX.Element | null; -// Warning: (ae-forgotten-export) The symbol "EntityProviderProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "EntityProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const EntityProvider: ({ entity, children, }: EntityProviderProps) => JSX.Element; +// @public +export interface EntityProviderProps { + // (undocumented) + children: ReactNode; + // (undocumented) + entity?: Entity; +} + // Warning: (ae-forgotten-export) The symbol "EntityRefLinkProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "EntityRefLink" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -726,8 +753,6 @@ export const UnregisterEntityDialog: ({ entity, }: Props_3) => JSX.Element; -// Warning: (ae-missing-release-tag) "useEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function useEntity(): { entity: T; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 3d9aed01c5..d7462f3765 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -34,6 +34,7 @@ "@backstage/core-components": "^0.4.0", "@backstage/core-plugin-api": "^0.1.6", "@backstage/integration": "^0.6.3", + "@backstage/version-bridge": "^0.1.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx b/plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx deleted file mode 100644 index 72cc9e4698..0000000000 --- a/plugins/catalog-react/src/components/EntityProvider/EntityProvider.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 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 { Entity } from '@backstage/catalog-model'; -import React, { ReactNode } from 'react'; -import { EntityContext } from '../../hooks'; - -type EntityProviderProps = { - entity: Entity; - children: ReactNode; -}; - -export const EntityProvider = ({ entity, children }: EntityProviderProps) => ( - - {children} - -); diff --git a/plugins/catalog-react/src/components/EntityProvider/index.ts b/plugins/catalog-react/src/components/EntityProvider/index.ts deleted file mode 100644 index ec7f4024e1..0000000000 --- a/plugins/catalog-react/src/components/EntityProvider/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2020 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. - */ -export { EntityProvider } from './EntityProvider'; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index 434514c4ed..0977e86d58 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -16,7 +16,6 @@ export * from './EntityKindPicker'; export * from './EntityLifecyclePicker'; export * from './EntityOwnerPicker'; -export * from './EntityProvider'; export * from './EntityRefLink'; export * from './EntitySearchBar'; export * from './EntityTable'; diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 2f3ce8411d..47bca8892a 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -13,7 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { EntityContext, useEntity, useEntityFromUrl } from './useEntity'; +export { + EntityContext, + useEntity, + useEntityFromUrl, + EntityProvider, + AsyncEntityProvider, +} from './useEntity'; +export type { + EntityProviderProps, + AsyncEntityProviderProps, +} from './useEntity'; export { useEntityCompoundName } from './useEntityCompoundName'; export { EntityListContext, diff --git a/plugins/catalog-react/src/hooks/useEntity.ts b/plugins/catalog-react/src/hooks/useEntity.ts deleted file mode 100644 index a7e6a09ae7..0000000000 --- a/plugins/catalog-react/src/hooks/useEntity.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2020 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 { Entity } from '@backstage/catalog-model'; -import { errorApiRef, useApi } from '@backstage/core-plugin-api'; -import { createContext, useContext, useEffect } from 'react'; -import { useNavigate } from 'react-router'; -import { useAsyncRetry } from 'react-use'; -import { catalogApiRef } from '../api'; -import { useEntityCompoundName } from './useEntityCompoundName'; - -type EntityLoadingStatus = { - entity?: Entity; - loading: boolean; - error?: Error; - refresh?: VoidFunction; -}; - -export const EntityContext = createContext({ - entity: undefined, - loading: true, - error: undefined, - refresh: () => {}, -}); - -export const useEntityFromUrl = (): EntityLoadingStatus => { - const { kind, namespace, name } = useEntityCompoundName(); - const navigate = useNavigate(); - const errorApi = useApi(errorApiRef); - const catalogApi = useApi(catalogApiRef); - - const { - value: entity, - error, - loading, - retry: refresh, - } = useAsyncRetry( - () => catalogApi.getEntityByName({ kind, namespace, name }), - [catalogApi, kind, namespace, name], - ); - - useEffect(() => { - if (!name) { - errorApi.post(new Error('No name provided!')); - navigate('/'); - } - }, [errorApi, navigate, error, loading, entity, name]); - - return { entity, loading, error, refresh }; -}; - -/** - * Grab the current entity from the context and its current loading state. - */ -export function useEntity() { - const { entity, loading, error, refresh } = useContext(EntityContext); - return { entity: entity as T, loading, error, refresh }; -} diff --git a/plugins/catalog-react/src/hooks/useEntity.tsx b/plugins/catalog-react/src/hooks/useEntity.tsx new file mode 100644 index 0000000000..4734039a4f --- /dev/null +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -0,0 +1,180 @@ +/* + * Copyright 2020 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 { Entity } from '@backstage/catalog-model'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { + createVersionedContext, + createVersionedValueMap, + useVersionedContext, +} from '@backstage/version-bridge'; +import React, { + ReactNode, + useEffect, + createContext, + useContext, + Provider, + Context, +} from 'react'; +import { useNavigate } from 'react-router'; +import { useAsyncRetry } from 'react-use'; +import { catalogApiRef } from '../api'; +import { useEntityCompoundName } from './useEntityCompoundName'; + +type EntityLoadingStatus = { + entity?: Entity; + loading: boolean; + error?: Error; + refresh?: VoidFunction; +}; + +/** + * @public + * @deprecated use `useEntity` and `EntityProvider` or `AsyncEntityProvider` instead. + */ +export const EntityContext: Context = + createContext({ + entity: undefined, + loading: true, + error: undefined, + refresh: () => {}, + }); +const OldEntityProvider = EntityContext.Provider; + +// This context has support for multiple concurrent versions of this package. +// It is currently used in parallel with the old context in order to provide +// a smooth transition, but will eventually be the only context we use. +const NewEntityContext = + createVersionedContext<{ 1: EntityLoadingStatus }>('entity-context'); + +/** + * Properties for the AsyncEntityProvider component. + * + * @public + */ +export interface AsyncEntityProviderProps { + children: ReactNode; + entity?: Entity; + loading: boolean; + error?: Error; + refresh?: VoidFunction; +} + +/** + * Provides a loaded entity to be picked up by the `useEntity` hook. + * + * @public + */ +export const AsyncEntityProvider = ({ + children, + entity, + loading, + error, + refresh, +}: AsyncEntityProviderProps) => { + const value = { entity, loading, error, refresh }; + return ( + + + {children} + + + ); +}; + +/** + * Properties for the EntityProvider component. + * + * @public + */ +export interface EntityProviderProps { + children: ReactNode; + entity?: Entity; +} + +/** + * Provides an entity to be picked up by the `useEntity` hook. + * + * @public + */ +export const EntityProvider = ({ entity, children }: EntityProviderProps) => ( + +); + +// This is used for forwards compatibility with the new entity context +EntityContext.Provider = + EntityProvider as unknown as Provider; + +export const useEntityFromUrl = (): EntityLoadingStatus => { + const { kind, namespace, name } = useEntityCompoundName(); + const navigate = useNavigate(); + const errorApi = useApi(errorApiRef); + const catalogApi = useApi(catalogApiRef); + + const { + value: entity, + error, + loading, + retry: refresh, + } = useAsyncRetry( + () => catalogApi.getEntityByName({ kind, namespace, name }), + [catalogApi, kind, namespace, name], + ); + + useEffect(() => { + if (!name) { + errorApi.post(new Error('No name provided!')); + navigate('/'); + } + }, [errorApi, navigate, error, loading, entity, name]); + + return { entity, loading, error, refresh }; +}; + +/** + * Grab the current entity from the context and its current loading state. + * + * @public + */ +export function useEntity() { + const versionedHolder = + useVersionedContext<{ 1: EntityLoadingStatus }>('entity-context'); + const oldContextValue = useContext(EntityContext); + + if (!versionedHolder) { + const { entity, loading, error, refresh } = oldContextValue; + return { entity: entity as T, loading, error, refresh }; + + // TODO(Rugvip): Throw this once we fully migrate to the new context + // throw new Error( + // 'Component can not be used outside of the context of an Entity', + // ); + } + + const value = versionedHolder.atVersion(1); + + if (!value) { + throw new Error('EntityContext v1 not available'); + } + + const { entity, loading, error, refresh } = value; + return { entity: entity as T, loading, error, refresh }; +}