Merge pull request #2125 from spotify/catalog/add-mock-data

add button to register dummy entities (fixed)
This commit is contained in:
Fredrik Adelöw
2020-08-26 13:12:24 +02:00
committed by GitHub
8 changed files with 89 additions and 26 deletions
+14 -2
View File
@@ -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,
+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 };
@@ -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,
};
}