catalog-react: deprecate EntityContext and add AsyncEntityProvider
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user