reload the filtered entities context instead of direct fetch
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
configApiRef,
|
||||
Content,
|
||||
ContentHeader,
|
||||
errorApiRef,
|
||||
identityApiRef,
|
||||
SupportButton,
|
||||
useApi,
|
||||
@@ -26,8 +27,9 @@ import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder';
|
||||
import { Button, makeStyles } from '@material-ui/core';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import StarIcon from '@material-ui/icons/Star';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { catalogApiRef } from '../../api/types';
|
||||
import { EntityFilterGroupsProvider, useFilteredEntities } from '../../filter';
|
||||
import { useStarredEntities } from '../../hooks/useStarredEntites';
|
||||
import { ButtonGroup, CatalogFilter } from '../CatalogFilter/CatalogFilter';
|
||||
@@ -51,15 +53,34 @@ const CatalogPageContents = () => {
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
reload,
|
||||
matchingEntities,
|
||||
availableTags,
|
||||
} = useFilteredEntities();
|
||||
const configApi = useApi(configApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const { isStarredEntity } = useStarredEntities();
|
||||
const userId = useApi(identityApiRef).getUserId();
|
||||
const [selectedTab, setSelectedTab] = useState<string>();
|
||||
const [selectedSidebarItem, setSelectedSidebarItem] = useState<string>();
|
||||
const orgName =
|
||||
useApi(configApiRef).getOptionalString('organization.name') ?? 'Company';
|
||||
const orgName = configApi.getOptionalString('organization.name') ?? 'Company';
|
||||
|
||||
const addMockData = useCallback(async () => {
|
||||
try {
|
||||
const promises: Promise<unknown>[] = [];
|
||||
const root = configApi.getConfig('catalog.exampleEntityLocations');
|
||||
for (const type of root.keys()) {
|
||||
for (const target of root.getStringArray(type)) {
|
||||
promises.push(catalogApi.addLocation(type, target));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
await reload();
|
||||
} catch (err) {
|
||||
errorApi.post(err);
|
||||
}
|
||||
}, [catalogApi, configApi, errorApi, reload]);
|
||||
|
||||
const tabs = useMemo<LabeledComponentType[]>(
|
||||
() => [
|
||||
@@ -153,6 +174,7 @@ const CatalogPageContents = () => {
|
||||
entities={matchingEntities}
|
||||
loading={loading}
|
||||
error={error}
|
||||
onAddMockData={addMockData}
|
||||
/>
|
||||
</div>
|
||||
</Content>
|
||||
|
||||
@@ -15,15 +15,8 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
} from '@backstage/core';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import * as React from 'react';
|
||||
import { CatalogApi, catalogApiRef } from '../../api/types';
|
||||
import { CatalogTable } from './CatalogTable';
|
||||
|
||||
const entities: Entity[] = [
|
||||
@@ -45,22 +38,16 @@ const entities: Entity[] = [
|
||||
];
|
||||
|
||||
describe('CatalogTable component', () => {
|
||||
const apis = ApiRegistry.from([
|
||||
[configApiRef, ({} as Partial<ConfigApi>) as ConfigApi],
|
||||
[catalogApiRef, ({} as Partial<CatalogApi>) as CatalogApi],
|
||||
]);
|
||||
|
||||
it('should render error message when error is passed in props', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<CatalogTable
|
||||
titlePreamble="Owned"
|
||||
entities={[]}
|
||||
loading={false}
|
||||
error={{ code: 'error' }}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
<CatalogTable
|
||||
titlePreamble="Owned"
|
||||
entities={[]}
|
||||
loading={false}
|
||||
error={{ code: 'error' }}
|
||||
onAddMockData={() => {}}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
const errorMessage = await rendered.findByText(
|
||||
@@ -72,13 +59,12 @@ describe('CatalogTable component', () => {
|
||||
it('should display entity names when loading has finished and no error occurred', async () => {
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apis}>
|
||||
<CatalogTable
|
||||
titlePreamble="Owned"
|
||||
entities={entities}
|
||||
loading={false}
|
||||
/>
|
||||
</ApiProvider>,
|
||||
<CatalogTable
|
||||
titlePreamble="Owned"
|
||||
entities={entities}
|
||||
loading={false}
|
||||
onAddMockData={() => {}}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
expect(rendered.getByText(/Owned \(3\)/)).toBeInTheDocument();
|
||||
|
||||
@@ -14,21 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Entity, LocationSpec } from '@backstage/catalog-model';
|
||||
import {
|
||||
configApiRef,
|
||||
Table,
|
||||
TableColumn,
|
||||
TableProps,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
import { Table, TableColumn, TableProps } from '@backstage/core';
|
||||
import { Chip, Link } from '@material-ui/core';
|
||||
import Add from '@material-ui/icons/Add';
|
||||
import Edit from '@material-ui/icons/Edit';
|
||||
import GitHub from '@material-ui/icons/GitHub';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { Dispatch } from 'react';
|
||||
import { generatePath, Link as RouterLink } from 'react-router-dom';
|
||||
import { catalogApiRef } from '../../api/types';
|
||||
import { findLocationForEntityMeta } from '../../data/utils';
|
||||
import { useStarredEntities } from '../../hooks/useStarredEntites';
|
||||
import { entityRoute } from '../../routes';
|
||||
@@ -82,7 +75,12 @@ const columns: TableColumn<Entity>[] = [
|
||||
<>
|
||||
{entity.metadata.tags &&
|
||||
entity.metadata.tags.map(t => (
|
||||
<Chip label={t} color="secondary" style={{ marginBottom: '0px' }} />
|
||||
<Chip
|
||||
key={t}
|
||||
label={t}
|
||||
color="secondary"
|
||||
style={{ marginBottom: '0px' }}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
@@ -94,6 +92,7 @@ type CatalogTableProps = {
|
||||
titlePreamble: string;
|
||||
loading: boolean;
|
||||
error?: any;
|
||||
onAddMockData: Dispatch<void>;
|
||||
};
|
||||
|
||||
export const CatalogTable = ({
|
||||
@@ -101,46 +100,20 @@ export const CatalogTable = ({
|
||||
loading,
|
||||
error,
|
||||
titlePreamble,
|
||||
onAddMockData,
|
||||
}: CatalogTableProps) => {
|
||||
const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
|
||||
const configApi = useApi(configApiRef);
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
|
||||
const [entitiesState, setEntitiesState] = useState<Entity[]>([]);
|
||||
const [errorState, setError] = useState<Error | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
setError(error);
|
||||
setEntitiesState(entities);
|
||||
}, [error, entities]);
|
||||
if (errorState) {
|
||||
if (error) {
|
||||
return (
|
||||
<div>
|
||||
<Alert severity="error">
|
||||
Error encountered while fetching catalog entities.{' '}
|
||||
{errorState.toString()}
|
||||
Error encountered while fetching catalog entities. {error.toString()}
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const addMockData = async () => {
|
||||
try {
|
||||
const _promises = configApi
|
||||
.getStringArray('catalog.entities')
|
||||
.map(file =>
|
||||
catalogApi.addLocation(
|
||||
'github',
|
||||
`${configApi.getString('catalog.baseUrl')}/${file}`,
|
||||
),
|
||||
);
|
||||
await Promise.all(_promises);
|
||||
const data: Entity[] = await catalogApi.getEntities();
|
||||
setEntitiesState(data);
|
||||
} catch (err) {
|
||||
setError(err);
|
||||
}
|
||||
};
|
||||
const actions: TableProps<Entity>['actions'] = [
|
||||
(rowData: Entity) => {
|
||||
const location = findLocationForEntityMeta(rowData.metadata);
|
||||
@@ -187,8 +160,8 @@ export const CatalogTable = ({
|
||||
icon: () => <Add />,
|
||||
tooltip: 'Add example components',
|
||||
isFreeAction: true,
|
||||
onClick: () => addMockData(),
|
||||
hidden: entitiesState && entitiesState.length > 0,
|
||||
onClick: onAddMockData,
|
||||
hidden: !(entities && entities.length === 0),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -203,7 +176,7 @@ export const CatalogTable = ({
|
||||
showEmptyDataSourceMessage: !loading,
|
||||
}}
|
||||
title={`${titlePreamble} (${(entities && entities.length) || 0})`}
|
||||
data={entitiesState}
|
||||
data={entities}
|
||||
actions={actions}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useApi } from '@backstage/core';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import { catalogApiRef } from '../api/types';
|
||||
import { filterGroupsContext, FilterGroupsContext } from './context';
|
||||
import {
|
||||
@@ -46,7 +46,9 @@ export const EntityFilterGroupsProvider = ({
|
||||
// The hook that implements the actual context building
|
||||
function useProvideEntityFilters(): FilterGroupsContext {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const { value: entities, error } = useAsync(() => catalogApi.getEntities());
|
||||
const [{ value: entities, error }, doReload] = useAsyncFn(() =>
|
||||
catalogApi.getEntities(),
|
||||
);
|
||||
|
||||
const filterGroups = useRef<{
|
||||
[filterGroupId: string]: FilterGroup;
|
||||
@@ -61,6 +63,10 @@ function useProvideEntityFilters(): FilterGroupsContext {
|
||||
const [matchingEntities, setMatchingEntities] = useState<Entity[]>([]);
|
||||
const [availableTags, setAvailableTags] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
doReload();
|
||||
}, [doReload]);
|
||||
|
||||
const rebuild = useCallback(() => {
|
||||
setFilterGroupStates(
|
||||
buildStates(
|
||||
@@ -122,11 +128,16 @@ function useProvideEntityFilters(): FilterGroupsContext {
|
||||
[rebuild],
|
||||
);
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
await doReload();
|
||||
}, [doReload]);
|
||||
|
||||
return {
|
||||
register,
|
||||
unregister,
|
||||
setGroupSelectedFilters,
|
||||
setSelectedTags,
|
||||
reload,
|
||||
loading: !error && !entities,
|
||||
error,
|
||||
filterGroupStates,
|
||||
|
||||
@@ -27,6 +27,7 @@ export type FilterGroupsContext = {
|
||||
unregister: (filterGroupId: string) => void;
|
||||
setGroupSelectedFilters: (filterGroupId: string, filterIds: string[]) => void;
|
||||
setSelectedTags: (tags: string[]) => void;
|
||||
reload: () => Promise<void>;
|
||||
loading: boolean;
|
||||
error?: Error;
|
||||
filterGroupStates: { [filterGroupId: string]: FilterGroupStates };
|
||||
|
||||
@@ -31,5 +31,6 @@ export function useFilteredEntities() {
|
||||
error: context.error,
|
||||
matchingEntities: context.matchingEntities,
|
||||
availableTags: context.availableTags,
|
||||
reload: context.reload,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user