reload the filtered entities context instead of direct fetch

This commit is contained in:
Fredrik Adelöw
2020-08-26 11:42:09 +02:00
parent 26159c01e9
commit ddad61d824
7 changed files with 79 additions and 87 deletions
@@ -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,
+1
View File
@@ -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,
};
}