diff --git a/.changeset/sweet-turtles-remain.md b/.changeset/sweet-turtles-remain.md new file mode 100644 index 0000000000..30cd1cd700 --- /dev/null +++ b/.changeset/sweet-turtles-remain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Deprecated `Router` in favor of using the plugin extensions. 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.test.tsx b/plugins/catalog-react/src/hooks/useEntity.test.tsx new file mode 100644 index 0000000000..9e73ed1475 --- /dev/null +++ b/plugins/catalog-react/src/hooks/useEntity.test.tsx @@ -0,0 +1,140 @@ +/* + * Copyright 2021 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 React from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { + useEntity, + EntityProvider, + AsyncEntityProvider, + EntityContext, +} from './useEntity'; +import { Entity } from '@backstage/catalog-model'; + +describe('EntityProvider', () => { + it('should provide no entity', async () => { + const { result } = renderHook(() => useEntity(), { + wrapper: ({ children }) => , + }); + + expect(result.current.entity).toBe(undefined); + expect(result.current.loading).toBe(true); + expect(result.current.error).toBe(undefined); + expect(result.current.refresh).toBe(undefined); + }); + + it('should provide an entity', async () => { + const entity = { kind: 'MyEntity' } as Entity; + const { result } = renderHook(() => useEntity(), { + wrapper: ({ children }) => ( + + ), + }); + + expect(result.current.entity).toBe(entity); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + expect(result.current.refresh).toBe(undefined); + }); +}); + +describe('AsyncEntityProvider', () => { + it('should provide no entity', async () => { + const { result } = renderHook(() => useEntity(), { + wrapper: ({ children }) => ( + + ), + }); + + expect(result.current.entity).toBe(undefined); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + expect(result.current.refresh).toBe(undefined); + }); + + it('should provide an entity', async () => { + const entity = { kind: 'MyEntity' } as Entity; + const refresh = () => {}; + const { result } = renderHook(() => useEntity(), { + wrapper: ({ children }) => ( + + ), + }); + + expect(result.current.entity).toBe(entity); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + expect(result.current.refresh).toBe(refresh); + }); + + it('should provide an error', async () => { + const error = new Error('oh no'); + const { result } = renderHook(() => useEntity(), { + wrapper: ({ children }) => ( + + ), + }); + + expect(result.current.entity).toBe(undefined); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(error); + expect(result.current.refresh).toBe(undefined); + }); +}); + +describe('EntityContext.Provider', () => { + it('should provide no entity', async () => { + const { result } = renderHook(() => useEntity(), { + wrapper: ({ children }) => ( + + ), + }); + + expect(result.current.entity).toBe(undefined); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + expect(result.current.refresh).toBe(undefined); + }); + + it('should provide an entity', async () => { + const entity = { kind: 'MyEntity' } as Entity; + const { result } = renderHook(() => useEntity(), { + wrapper: ({ children }) => ( + + ), + }); + + expect(result.current.entity).toBe(entity); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + expect(result.current.refresh).toBe(undefined); + }); +}); 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..bcb38bb807 --- /dev/null +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -0,0 +1,190 @@ +/* + * 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, + 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: () => {}, + }); +// We grab this for use in the new provider, since we're overriding it later on +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 }; + // We provide both the old and the new context, since + // consumers might be doing things like `useContext(EntityContext)` + 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 +const CompatibilityProvider = ({ + value, + children, +}: { + value: EntityLoadingStatus; + children: ReactNode; +}) => { + return ; +}; +EntityContext.Provider = CompatibilityProvider 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'); + + if (!versionedHolder) { + // TODO(Rugvip): Throw this once we fully migrate to the new context + // throw new Error('Entity context is not available'); + + return { + entity: undefined as unknown as T, + loading: true, + error: undefined, + refresh: () => {}, + }; + } + + 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 }; +} diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 689c617158..6034031fae 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -401,7 +401,7 @@ export const isOrphan: (entity: Entity) => boolean; // Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public @deprecated (undocumented) export const Router: ({ EntityPage, }: { diff --git a/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx b/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx index 37c2ec7139..94b9f5eee4 100644 --- a/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx +++ b/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx @@ -16,12 +16,13 @@ import React from 'react'; import { Outlet } from 'react-router'; -import { EntityLoaderProvider } from '../EntityLoaderProvider'; +import { + useEntityFromUrl, + AsyncEntityProvider, +} from '@backstage/plugin-catalog-react'; -export const CatalogEntityPage = () => { - return ( - - - - ); -}; +export const CatalogEntityPage = () => ( + + + +); diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx index b61d8bc10c..218e55fb5f 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx @@ -15,7 +15,11 @@ */ import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; -import { catalogApiRef, EntityContext } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + EntityProvider, + AsyncEntityProvider, +} from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; import React from 'react'; @@ -26,16 +30,12 @@ import { EntityLayout } from './EntityLayout'; import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; -const mockEntityData = { - loading: false, - error: undefined, - entity: { - kind: 'MyKind', - metadata: { - name: 'my-entity', - }, - } as Entity, -}; +const mockEntity = { + kind: 'MyKind', + metadata: { + name: 'my-entity', + }, +} as Entity; const mockApis = ApiRegistry.with(catalogApiRef, {} as CatalogApi).with( alertApiRef, @@ -46,13 +46,13 @@ describe('EntityLayout', () => { it('renders simplest case', async () => { const rendered = await renderInTestApp( - +
tabbed-test-content
-
+
, ); @@ -92,20 +92,15 @@ describe('EntityLayout', () => { }); it('renders error message when entity is not found', async () => { - const noEntityData = { - ...mockEntityData, - entity: undefined, - }; - const rendered = await renderInTestApp( - +
tabbed-test-content
-
+
, ); @@ -122,7 +117,7 @@ describe('EntityLayout', () => { path="/*" element={ - +
tabbed-test-content
@@ -134,7 +129,7 @@ describe('EntityLayout', () => {
tabbed-test-content-2
-
+
} /> @@ -159,7 +154,7 @@ describe('EntityLayout', () => { const rendered = await renderInTestApp( - +
tabbed-test-content
@@ -179,7 +174,7 @@ describe('EntityLayout', () => {
tabbed-test-content-3
-
+
, ); diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx index 5e99adf2a2..04ee1dc99e 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx @@ -15,7 +15,7 @@ */ import { Entity, EntityLink } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import React from 'react'; import { EntityLinksCard } from './EntityLinksCard'; @@ -40,17 +40,12 @@ describe('EntityLinksCard', () => { it('should render a link', async () => { const links: EntityLink[] = [createLink()]; - const entityContextValue = { - entity: createEntity(links), - loading: false, - error: undefined, - }; const { queryByText } = await renderWithEffects( wrapInTestApp( - + - , + , ), ); @@ -59,17 +54,11 @@ describe('EntityLinksCard', () => { }); it('should show empty state', async () => { - const entityContextValue = { - entity: createEntity([]), - loading: false, - error: undefined, - }; - const { queryByText } = await renderWithEffects( wrapInTestApp( - + - , + , ), ); diff --git a/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx b/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx deleted file mode 100644 index 8d2feae7da..0000000000 --- a/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx +++ /dev/null @@ -1,30 +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 { - EntityContext, - useEntityFromUrl, -} from '@backstage/plugin-catalog-react'; -import React, { ReactNode } from 'react'; - -export const EntityLoaderProvider = ({ children }: { children: ReactNode }) => { - const { entity, loading, error, refresh } = useEntityFromUrl(); - - return ( - - {children} - - ); -}; diff --git a/plugins/catalog/src/components/EntityLoaderProvider/index.ts b/plugins/catalog/src/components/EntityLoaderProvider/index.ts deleted file mode 100644 index 8422e6dc4b..0000000000 --- a/plugins/catalog/src/components/EntityLoaderProvider/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 { EntityLoaderProvider } from './EntityLoaderProvider'; diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx index 1a87b83600..f80a66b9bb 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { render } from '@testing-library/react'; import React from 'react'; import { isKind } from './conditions'; @@ -46,15 +46,9 @@ describe('EntitySwitch', () => { const rendered = render( - + {content} - + , ); @@ -64,15 +58,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} - + , ); @@ -82,15 +70,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} - + , ); @@ -100,20 +82,16 @@ describe('EntitySwitch', () => { }); it('should switch child when filters switch', () => { - const entityContextValue = { - entity: { kind: 'component' } as Entity, - loading: false, - error: undefined, - }; + const entity = { kind: 'component' } as Entity; const rendered = render( - + - + , ); @@ -122,12 +100,12 @@ describe('EntitySwitch', () => { rendered.rerender( - + - + , ); diff --git a/plugins/catalog/src/components/Router.tsx b/plugins/catalog/src/components/Router.tsx index d0e0d5dcce..9116c0b551 100644 --- a/plugins/catalog/src/components/Router.tsx +++ b/plugins/catalog/src/components/Router.tsx @@ -15,15 +15,16 @@ */ import { ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { + AsyncEntityProvider, entityRoute, rootRoute, useEntity, + useEntityFromUrl, } from '@backstage/plugin-catalog-react'; import { Link, Typography } from '@material-ui/core'; -import React, { ComponentType } from 'react'; +import React, { ComponentType, ReactNode } from 'react'; import { Navigate, Route, Routes, useParams } from 'react-router'; import { CatalogPage } from './CatalogPage'; -import { EntityLoaderProvider } from './EntityLoaderProvider'; import { EntityNotFound } from './EntityNotFound'; import { EntityPageLayout } from './EntityPageLayout'; import { Content } from '@backstage/core-components'; @@ -73,6 +74,13 @@ const OldEntityRouteRedirect = () => { ); }; +export const EntityLoader = (props: { children: ReactNode }) => ( + +); + +/** + * @deprecated Use plugin extensions instead + * */ export const Router = ({ EntityPage = DefaultEntityPage, }: { @@ -83,9 +91,9 @@ export const Router = ({ + - + } /> { let entity: Entity; const wrapper = ({ children }: PropsWithChildren<{}>) => { - return ( - - {children} - - ); + return {children}; }; const subject = () => renderHook(useConsumerGroupsForEntity, { wrapper }); diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx index 193d821ac9..a90d3d608e 100644 --- a/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx +++ b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderHook } from '@testing-library/react-hooks'; import { when } from 'jest-when'; import React, { PropsWithChildren } from 'react'; @@ -65,9 +65,7 @@ describe('useConsumerGroupOffsets', () => { mockKafkaApi, )} > - - {children} - + {children} ); }; diff --git a/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx b/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx index c4bd898f89..1433a186fb 100644 --- a/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx +++ b/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { lightTheme } from '@backstage/theme'; import { ThemeProvider } from '@material-ui/core'; import { render } from '@testing-library/react'; @@ -77,16 +77,14 @@ describe('', () => { }, }; - const subject = (value = {}) => + const subject = () => render( - + - + , diff --git a/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.test.tsx b/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.test.tsx index 9a9ac7781e..58a3033a1f 100644 --- a/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.test.tsx +++ b/plugins/lighthouse/src/components/Cards/LastLighthouseAuditCard.test.tsx @@ -15,7 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { lightTheme } from '@backstage/theme'; import { ThemeProvider } from '@material-ui/core'; import { render } from '@testing-library/react'; @@ -64,15 +64,13 @@ describe('', () => { }, }; - const subject = (value = {}) => + const subject = () => render( - + - + , ); diff --git a/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx b/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx index 85ed149b0e..64f7483081 100644 --- a/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx +++ b/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { renderHook } from '@testing-library/react-hooks'; import React, { PropsWithChildren } from 'react'; import { lighthouseApiRef, WebsiteListResponse } from '../api'; @@ -61,9 +61,7 @@ describe('useWebsiteForEntity', () => { mockLighthouseApi, )} > - - {children} - + {children} ); }; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx index a8fde99f1f..dc90a46fdf 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx @@ -15,7 +15,7 @@ */ import { GroupEntity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { Grid } from '@material-ui/core'; import React from 'react'; import { MemoryRouter } from 'react-router'; @@ -57,12 +57,12 @@ const defaultEntity: GroupEntity = { export const Default = () => ( - + - + ); diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx index f3dae18e77..0a5a0cfef1 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx @@ -16,7 +16,7 @@ import { Entity, GroupEntity } from '@backstage/catalog-model'; import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; -import { catalogApiRef, EntityContext } from '@backstage/plugin-catalog-react'; +import { catalogApiRef, EntityProvider } from '@backstage/plugin-catalog-react'; import { Grid } from '@material-ui/core'; import React from 'react'; import { MemoryRouter } from 'react-router'; @@ -105,13 +105,13 @@ const apiRegistry = (items: Entity[]) => export const Default = () => ( - + - + ); @@ -119,13 +119,13 @@ export const Default = () => ( export const Empty = () => ( - + - + ); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index 7ebdf31ae4..14fed985b6 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -20,7 +20,7 @@ import { CatalogApi, catalogApiRef, catalogRouteRef, - EntityContext, + EntityProvider, } from '@backstage/plugin-catalog-react'; import { wrapInTestApp } from '@backstage/test-utils'; import { @@ -91,13 +91,13 @@ const apiRegistry = ApiRegistry.from([[catalogApiRef, catalogApi]]); export const Default = () => wrapInTestApp( - + - + , { mountedRoutes: { '/catalog': catalogRouteRef }, @@ -124,15 +124,13 @@ export const Themed = () => wrapInTestApp( - + - + , { diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index c1019cf2dc..aecc401f52 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -15,7 +15,7 @@ */ import { UserEntity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { Grid } from '@material-ui/core'; import React from 'react'; import { MemoryRouter } from 'react-router'; @@ -55,13 +55,13 @@ const defaultEntity: UserEntity = { export const Default = () => ( - + - + ); @@ -83,12 +83,12 @@ const noImageEntity: UserEntity = { export const NoImage = () => ( - + - + ); diff --git a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx index 4ca86ee412..63d617aff2 100644 --- a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx +++ b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { Entity } from '@backstage/catalog-model'; -import { EntityContext } from '@backstage/plugin-catalog-react'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; import { wrapInTestApp } from '@backstage/test-utils'; import { splunkOnCallApiRef, @@ -71,35 +71,27 @@ const apis = ApiRegistry.from([ ], ]); -const mockEntityData = { - loading: false, - error: undefined, - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: 'splunkoncall-test', - annotations: { - 'splunk.com/on-call-team': 'test', - }, +const mockEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'splunkoncall-test', + annotations: { + 'splunk.com/on-call-team': 'test', }, - } as Entity, -}; + }, +} as Entity; -const mockEntityDataNoIncidents = { - loading: false, - error: undefined, - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: 'splunkoncall-test', - annotations: { - 'splunk.com/on-call-team': 'test-noincidents', - }, +const mockEntityNoIncidents = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'splunkoncall-test', + annotations: { + 'splunk.com/on-call-team': 'test-noincidents', }, - } as Entity, -}; + }, +} as Entity; describe('SplunkOnCallCard', () => { it('Render splunkoncall', async () => { @@ -113,9 +105,9 @@ describe('SplunkOnCallCard', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + - + , ), ); @@ -136,9 +128,9 @@ describe('SplunkOnCallCard', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + - + , ), ); @@ -155,9 +147,9 @@ describe('SplunkOnCallCard', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + - + , ), ); @@ -181,9 +173,9 @@ describe('SplunkOnCallCard', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + - + , ), ); @@ -204,9 +196,9 @@ describe('SplunkOnCallCard', () => { const { getByText, queryByTestId, getByRole } = render( wrapInTestApp( - + - + , ), );