feat(starredEntitiesCard): show title if defined & hide unregistered entities

Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
Phil Kuang
2022-05-05 12:47:59 -04:00
parent 8386a32644
commit 69093c5f91
4 changed files with 155 additions and 28 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-home': patch
---
Display entity titles in `StarredEntities` home page card (if defined) and don't show entities which no longer exist
@@ -15,6 +15,7 @@
*/
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import {
catalogApiRef,
starredEntitiesApiRef,
MockStarredEntitiesApi,
entityRouteRef,
@@ -22,14 +23,44 @@ import {
import React from 'react';
import { Content } from './Content';
const entities = [
{
apiVersion: '1',
kind: 'Component',
metadata: {
name: 'mock-starred-entity',
},
},
{
apiVersion: '1',
kind: 'Component',
metadata: {
name: 'mock-starred-entity-2',
title: 'Mock Starred Entity 2!',
},
},
];
describe('StarredEntitiesContent', () => {
it('should render list of tools', async () => {
it('should render list of starred entities', async () => {
const mockedApi = new MockStarredEntitiesApi();
mockedApi.toggleStarred('component:default/mock-starred-entity');
mockedApi.toggleStarred('component:default/mock-starred-entity-2');
mockedApi.toggleStarred('component:default/mock-starred-entity-3');
const { getByText } = await renderInTestApp(
<TestApiProvider apis={[[starredEntitiesApiRef, mockedApi]]}>
const mockCatalogApi = {
getEntities: jest
.fn()
.mockImplementation(async () => ({ items: entities })),
};
const { getByText, queryByText } = await renderInTestApp(
<TestApiProvider
apis={[
[catalogApiRef, mockCatalogApi],
[starredEntitiesApiRef, mockedApi],
]}
>
<Content />
</TestApiProvider>,
{
@@ -40,12 +71,13 @@ describe('StarredEntitiesContent', () => {
);
expect(getByText('mock-starred-entity')).toBeInTheDocument();
expect(getByText('mock-starred-entity-2')).toBeInTheDocument();
expect(getByText('Mock Starred Entity 2!')).toBeInTheDocument();
expect(queryByText('mock-starred-entity-3')).not.toBeInTheDocument();
expect(getByText('mock-starred-entity').closest('a')).toHaveAttribute(
'href',
'/catalog/default/component/mock-starred-entity',
);
expect(getByText('mock-starred-entity-2').closest('a')).toHaveAttribute(
expect(getByText('Mock Starred Entity 2!').closest('a')).toHaveAttribute(
'href',
'/catalog/default/component/mock-starred-entity-2',
);
@@ -15,12 +15,14 @@
*/
import {
catalogApiRef,
useStarredEntities,
entityRouteParams,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { parseEntityRef } from '@backstage/catalog-model';
import { useRouteRef } from '@backstage/core-plugin-api';
import { Link } from '@backstage/core-components';
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
import { useApi, useRouteRef } from '@backstage/core-plugin-api';
import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components';
import {
List,
ListItem,
@@ -32,6 +34,7 @@ import {
} from '@material-ui/core';
import StarIcon from '@material-ui/icons/Star';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
/**
* A component to display a list of starred entities for the user.
@@ -39,9 +42,37 @@ import React from 'react';
* @public
*/
export const Content = () => {
const catalogApi = useApi(catalogApiRef);
const catalogEntityRoute = useRouteRef(entityRouteRef);
const { starredEntities, toggleStarredEntity } = useStarredEntities();
// Grab starred entities from catalog to ensure they still exist and also retrieve display titles
const entities = useAsync(async () => {
if (!starredEntities.size) {
return [];
}
const filter = [...starredEntities]
.map(ent => parseEntityRef(ent))
.map(ref => ({
kind: ref.kind,
'metadata.namespace': ref.namespace,
'metadata.name': ref.name,
}));
return (
await catalogApi.getEntities({
filter,
fields: [
'kind',
'metadata.namespace',
'metadata.name',
'metadata.title',
],
})
).items;
}, [catalogApi, starredEntities]);
if (starredEntities.size === 0)
return (
<Typography variant="body1">
@@ -49,26 +80,40 @@ export const Content = () => {
</Typography>
);
return (
if (entities.loading) {
return <Progress />;
}
return entities.error ? (
<ResponseErrorPanel error={entities.error} />
) : (
<List>
{Array.from(starredEntities).map(entity => (
<ListItem key={entity}>
<Link to={catalogEntityRoute(parseEntityRef(entity))}>
<ListItemText primary={parseEntityRef(entity).name} />
</Link>
<ListItemSecondaryAction>
<Tooltip title="Remove from starred">
<IconButton
edge="end"
aria-label="unstar"
onClick={() => toggleStarredEntity(entity)}
>
<StarIcon style={{ color: '#f3ba37' }} />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
))}
{entities.value
?.sort((a, b) =>
(a.metadata.title ?? a.metadata.name).localeCompare(
b.metadata.title ?? b.metadata.name,
),
)
.map(entity => (
<ListItem key={stringifyEntityRef(entity)}>
<Link to={catalogEntityRoute(entityRouteParams(entity))}>
<ListItemText
primary={entity.metadata.title ?? entity.metadata.name}
/>
</Link>
<ListItemSecondaryAction>
<Tooltip title="Remove from starred">
<IconButton
edge="end"
aria-label="unstar"
onClick={() => toggleStarredEntity(entity)}
>
<StarIcon style={{ color: '#f3ba37' }} />
</IconButton>
</Tooltip>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
);
};
@@ -17,6 +17,7 @@
import { HomePageStarredEntities } from '../../plugin';
import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils';
import {
catalogApiRef,
starredEntitiesApiRef,
MockStarredEntitiesApi,
entityRouteRef,
@@ -30,12 +31,56 @@ starredEntitiesApi.toggleStarred('component:default/example-starred-entity-2');
starredEntitiesApi.toggleStarred('component:default/example-starred-entity-3');
starredEntitiesApi.toggleStarred('component:default/example-starred-entity-4');
const entities = [
{
apiVersion: '1',
kind: 'Component',
metadata: {
name: 'mock-starred-entity',
title: 'Mock Starred Entity!',
},
},
{
apiVersion: '1',
kind: 'Component',
metadata: {
name: 'mock-starred-entity-2',
title: 'Mock Starred Entity 2!',
},
},
{
apiVersion: '1',
kind: 'Component',
metadata: {
name: 'mock-starred-entity-3',
title: 'Mock Starred Entity 3!',
},
},
{
apiVersion: '1',
kind: 'Component',
metadata: {
name: 'mock-starred-entity-4',
title: 'Mock Starred Entity 4!',
},
},
];
const mockCatalogApi = {
getEntities: async () => ({ items: entities }),
};
export default {
title: 'Plugins/Home/Components/StarredEntities',
decorators: [
(Story: ComponentType<{}>) =>
wrapInTestApp(
<TestApiProvider apis={[[starredEntitiesApiRef, starredEntitiesApi]]}>
<TestApiProvider
apis={[
[catalogApiRef, mockCatalogApi],
[starredEntitiesApiRef, starredEntitiesApi],
]}
>
<Story />
</TestApiProvider>,
{