feat(starredEntitiesCard): show title if defined & hide unregistered entities
Signed-off-by: Phil Kuang <pkuang@factset.com>
This commit is contained in:
@@ -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>,
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user