catalog-react: deprecate EntityContext and add AsyncEntityProvider

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-09-11 13:12:56 +02:00
parent 10e3c97d91
commit ea81a1f19c
9 changed files with 230 additions and 131 deletions
+5
View File
@@ -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.
+33 -8
View File
@@ -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<EntityLoadingStatus>;
// 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<T extends Entity = Entity>(): {
entity: T;
+1
View File
@@ -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",
@@ -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) => (
<EntityContext.Provider
value={{
entity,
loading: !Boolean(entity),
error: undefined,
}}
>
{children}
</EntityContext.Provider>
);
@@ -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';
@@ -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';
+11 -1
View File
@@ -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,
@@ -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<EntityLoadingStatus>({
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<T extends Entity = Entity>() {
const { entity, loading, error, refresh } = useContext(EntityContext);
return { entity: entity as T, loading, error, refresh };
}
@@ -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<EntityLoadingStatus> =
createContext<EntityLoadingStatus>({
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 (
<OldEntityProvider value={value}>
<NewEntityContext.Provider value={createVersionedValueMap({ 1: value })}>
{children}
</NewEntityContext.Provider>
</OldEntityProvider>
);
};
/**
* 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) => (
<AsyncEntityProvider
entity={entity}
loading={!Boolean(entity)}
error={undefined}
refresh={undefined}
children={children}
/>
);
// This is used for forwards compatibility with the new entity context
EntityContext.Provider =
EntityProvider as unknown as Provider<EntityLoadingStatus>;
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<T extends Entity = Entity>() {
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 };
}