diff --git a/.changeset/green-lizards-boil.md b/.changeset/green-lizards-boil.md new file mode 100644 index 0000000000..640699e001 --- /dev/null +++ b/.changeset/green-lizards-boil.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Use a versioned context for `useEntityList`, to better work with mixed `@backstage/plugin-catalog-react` versions. diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index 8916fc98f9..14c832d545 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -322,7 +322,7 @@ export const EntityLifecyclePicker: (props: { initialFilter?: string[]; }) => JSX_2.Element; -// @public +// @public @deprecated export const EntityListContext: Context< EntityListContextProps | undefined >; diff --git a/plugins/catalog-react/src/deprecated.tsx b/plugins/catalog-react/src/deprecated.tsx index 74186d9c29..7d347dda6b 100644 --- a/plugins/catalog-react/src/deprecated.tsx +++ b/plugins/catalog-react/src/deprecated.tsx @@ -15,10 +15,12 @@ */ import { PropsWithChildren, useCallback, useMemo, useState } from 'react'; +import { createVersionedValueMap } from '@backstage/version-bridge'; import { DefaultEntityFilters, - EntityListContext, EntityListContextProps, + NewEntityListContext, + OldEntityListContext, } from './hooks/useEntityListProvider'; /** @@ -81,8 +83,18 @@ export function MockEntityListContextProvider< ); return ( - + {children} - + ); } + +/** + * Creates new context for entity listing and filtering. + * + * @public + * @deprecated Please use `EntityListProvider` and `EntityListProvider` instead. + */ +export const EntityListContext = OldEntityListContext; diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index befcce520d..2bce5a4673 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -24,11 +24,7 @@ export type { EntityProviderProps, AsyncEntityProviderProps, } from './useEntity'; -export { - EntityListContext, - EntityListProvider, - useEntityList, -} from './useEntityListProvider'; +export { EntityListProvider, useEntityList } from './useEntityListProvider'; export type { DefaultEntityFilters, EntityListContextProps, diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index f7090f330b..7b9d17d82e 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -42,7 +42,13 @@ import { } from '../filters'; import { createDeferred } from '@backstage/types'; import { EntityListPagination } from '../types'; -import { EntityListProvider, useEntityList } from './useEntityListProvider'; +import { + EntityListContextProps, + EntityListProvider, + NewEntityListContext, + useEntityList, +} from './useEntityListProvider'; +import { createVersionedValueMap } from '@backstage/version-bridge'; const entities: Entity[] = [ { @@ -1048,3 +1054,59 @@ describe(``, () => { ); }); }); + +describe('versioned context', () => { + it('should work explicitly with new versioned contexts', () => { + const value: EntityListContextProps = { + filters: {}, + entities: [], + backendEntities: [], + updateFilters: jest.fn(), + queryParameters: {}, + loading: true, + limit: 277, + setLimit: jest.fn(), + setOffset: jest.fn(), + paginationMode: 'none', + }; + + const { result } = renderHook(() => useEntityList(), { + wrapper: ({ children }) => { + const InitialFiltersWrapper = (f: PropsWithChildren<{}>) => { + const { updateFilters } = useEntityList(); + useMountEffect(() => { + updateFilters({ + kind: new EntityKindFilter('component', 'Component'), + }); + }); + return <>{f.children}; + }; + + return ( + + + + {children} + + + + ); + }, + }); + + expect(result.current.limit).toBe(277); + }); +}); diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 8c4927ad0a..a2ab4ea377 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -17,6 +17,11 @@ import { QueryEntitiesResponse } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; +import { + createVersionedContext, + createVersionedValueMap, + useVersionedContext, +} from '@backstage/version-bridge'; import { compact, isEqual } from 'lodash'; import qs from 'qs'; import { @@ -122,11 +127,17 @@ export type EntityListContextProps< paginationMode: PaginationMode; }; +// 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. +export const NewEntityListContext = createVersionedContext<{ + 1: EntityListContextProps; +}>('entity-list-context'); + /** * Creates new context for entity listing and filtering. - * @public */ -export const EntityListContext = createContext< +export const OldEntityListContext = createContext< EntityListContextProps | undefined >(undefined); @@ -487,9 +498,13 @@ export const EntityListProvider = ( ); return ( - - {props.children} - + + + {props.children} + + ); }; @@ -500,8 +515,22 @@ export const EntityListProvider = ( export function useEntityList< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, >(): EntityListContextProps { - const context = useContext(EntityListContext); - if (!context) - throw new Error('useEntityList must be used within EntityListProvider'); - return context; + const versionedHolder = useVersionedContext<{ + 1: EntityListContextProps; + }>('entity-list-context'); + const oldContext = useContext(OldEntityListContext); + + if (versionedHolder) { + const value = versionedHolder.atVersion(1); + if (!value) { + throw new Error('EntityListContext v1 not available'); + } + return value; + } + + if (oldContext) { + return oldContext; + } + + throw new Error('useEntityList must be used within EntityListProvider'); } diff --git a/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx index 0dcbbed8e8..e75e7e20e3 100644 --- a/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx +++ b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx @@ -17,9 +17,10 @@ import { PropsWithChildren, useCallback, useMemo, useState } from 'react'; import { DefaultEntityFilters, - EntityListContext, EntityListContextProps, } from '@backstage/plugin-catalog-react'; +import { createVersionedValueMap } from '@backstage/version-bridge'; +import { NewEntityListContext } from '../hooks/useEntityListProvider'; /** * Simplifies testing of code that uses the entity list hooks. @@ -82,8 +83,10 @@ export function MockEntityListContextProvider< ); return ( - + {children} - + ); }