Merge pull request #2125 from spotify/catalog/add-mock-data
add button to register dummy entities (fixed)
This commit is contained in:
+14
-2
@@ -50,6 +50,18 @@ newrelic:
|
||||
baseUrl: 'https://api.newrelic.com/v2'
|
||||
key: NEW_RELIC_REST_API_KEY
|
||||
|
||||
catalog:
|
||||
exampleEntityLocations:
|
||||
github:
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/artist-lookup-component.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-order-component.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/podcast-api-component.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/queue-proxy-component.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/searcher-component.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/playback-lib-component.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/www-artist-component.yaml
|
||||
- https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/shuffle-api-component.yaml
|
||||
|
||||
auth:
|
||||
providers:
|
||||
google:
|
||||
@@ -124,7 +136,7 @@ auth:
|
||||
env: AUTH_AUTH0_DOMAIN
|
||||
microsoft:
|
||||
development:
|
||||
appOrigin: "http://localhost:3000/"
|
||||
appOrigin: 'http://localhost:3000/'
|
||||
secure: false
|
||||
clientId:
|
||||
$secret:
|
||||
@@ -134,4 +146,4 @@ auth:
|
||||
env: AUTH_MICROSOFT_CLIENT_SECRET
|
||||
tenantId:
|
||||
$secret:
|
||||
env: AUTH_MICROSOFT_TENANT_ID
|
||||
env: AUTH_MICROSOFT_TENANT_ID
|
||||
|
||||
@@ -15,27 +15,29 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
configApiRef,
|
||||
Content,
|
||||
ContentHeader,
|
||||
errorApiRef,
|
||||
identityApiRef,
|
||||
SupportButton,
|
||||
configApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core';
|
||||
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 { CatalogFilter, ButtonGroup } from '../CatalogFilter/CatalogFilter';
|
||||
import { ButtonGroup, CatalogFilter } from '../CatalogFilter/CatalogFilter';
|
||||
import { CatalogTable } from '../CatalogTable/CatalogTable';
|
||||
import { ResultsFilter } from '../ResultsFilter/ResultsFilter';
|
||||
import CatalogLayout from './CatalogLayout';
|
||||
import { CatalogTabs, LabeledComponentType } from './CatalogTabs';
|
||||
import { WelcomeBanner } from './WelcomeBanner';
|
||||
import { ResultsFilter } from '../ResultsFilter/ResultsFilter';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
@@ -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,12 +15,11 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
|
||||
import * as React from 'react';
|
||||
import { CatalogTable } from './CatalogTable';
|
||||
|
||||
const entites: Entity[] = [
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
@@ -40,13 +39,14 @@ const entites: Entity[] = [
|
||||
|
||||
describe('CatalogTable component', () => {
|
||||
it('should render error message when error is passed in props', async () => {
|
||||
const rendered = render(
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<CatalogTable
|
||||
titlePreamble="Owned"
|
||||
entities={[]}
|
||||
loading={false}
|
||||
error={{ code: 'error' }}
|
||||
onAddMockData={() => {}}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
@@ -57,12 +57,13 @@ describe('CatalogTable component', () => {
|
||||
});
|
||||
|
||||
it('should display entity names when loading has finished and no error occurred', async () => {
|
||||
const rendered = render(
|
||||
const rendered = await renderWithEffects(
|
||||
wrapInTestApp(
|
||||
<CatalogTable
|
||||
titlePreamble="Owned"
|
||||
entities={entites}
|
||||
entities={entities}
|
||||
loading={false}
|
||||
onAddMockData={() => {}}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
import { Entity, LocationSpec } from '@backstage/catalog-model';
|
||||
import { Table, TableColumn, TableProps } from '@backstage/core';
|
||||
import { Link, Chip } from '@material-ui/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 from 'react';
|
||||
import React, { Dispatch } from 'react';
|
||||
import { generatePath, Link as RouterLink } from 'react-router-dom';
|
||||
import { findLocationForEntityMeta } from '../../data/utils';
|
||||
import { useStarredEntities } from '../../hooks/useStarredEntites';
|
||||
@@ -74,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' }}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
@@ -86,6 +92,7 @@ type CatalogTableProps = {
|
||||
titlePreamble: string;
|
||||
loading: boolean;
|
||||
error?: any;
|
||||
onAddMockData: Dispatch<void>;
|
||||
};
|
||||
|
||||
export const CatalogTable = ({
|
||||
@@ -93,6 +100,7 @@ export const CatalogTable = ({
|
||||
loading,
|
||||
error,
|
||||
titlePreamble,
|
||||
onAddMockData,
|
||||
}: CatalogTableProps) => {
|
||||
const { isStarredEntity, toggleStarredEntity } = useStarredEntities();
|
||||
|
||||
@@ -148,6 +156,13 @@ export const CatalogTable = ({
|
||||
onClick: () => toggleStarredEntity(rowData),
|
||||
};
|
||||
},
|
||||
{
|
||||
icon: () => <Add />,
|
||||
tooltip: 'Add example components',
|
||||
isFreeAction: true,
|
||||
onClick: onAddMockData,
|
||||
hidden: !(entities && entities.length === 0),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -51,12 +51,12 @@ describe('useEntityFilterGroup', () => {
|
||||
it('works for an empty set of filters', async () => {
|
||||
catalogApi.getEntities.mockResolvedValue([]);
|
||||
const group: FilterGroup = { filters: {} };
|
||||
const { result, wait } = renderHook(
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useEntityFilterGroup('g1', group),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await wait(() => expect(result.current.state.type).toBe('ready'));
|
||||
await waitFor(() => expect(result.current.state.type).toBe('ready'));
|
||||
});
|
||||
|
||||
it('works for a single group', async () => {
|
||||
@@ -73,12 +73,12 @@ describe('useEntityFilterGroup', () => {
|
||||
f2: e => e.metadata.name !== 'n',
|
||||
},
|
||||
};
|
||||
const { result, wait } = renderHook(
|
||||
const { result, waitFor } = renderHook(
|
||||
() => useEntityFilterGroup('g1', group),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await wait(() => expect(result.current.state.type).toEqual('ready'));
|
||||
await waitFor(() => expect(result.current.state.type).toEqual('ready'));
|
||||
let state = result.current.state as FilterGroupStatesReady;
|
||||
expect(state.state.filters.f1).toEqual({
|
||||
isSelected: false,
|
||||
@@ -91,7 +91,7 @@ describe('useEntityFilterGroup', () => {
|
||||
|
||||
act(() => result.current.setSelectedFilters(['f1']));
|
||||
|
||||
await wait(() => expect(result.current.state.type).toEqual('ready'));
|
||||
await waitFor(() => expect(result.current.state.type).toEqual('ready'));
|
||||
state = result.current.state as FilterGroupStatesReady;
|
||||
expect(state.state.filters.f1).toEqual({
|
||||
isSelected: true,
|
||||
@@ -104,7 +104,7 @@ describe('useEntityFilterGroup', () => {
|
||||
|
||||
act(() => result.current.setSelectedFilters(['f2']));
|
||||
|
||||
await wait(() => expect(result.current.state.type).toEqual('ready'));
|
||||
await waitFor(() => expect(result.current.state.type).toEqual('ready'));
|
||||
state = result.current.state as FilterGroupStatesReady;
|
||||
expect(state.state.filters.f1).toEqual({
|
||||
isSelected: false,
|
||||
|
||||
@@ -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