feat(home): improve StarredEntities UI (#26843)

* feat(home): improve StarredEntities UI

Signed-off-by: Thomas Cardonne <thomas.cardonne@adevinta.com>
---------

Signed-off-by: Thomas Cardonne <thomas.cardonne@adevinta.com>
Signed-off-by: Thomas Cardonne <t.cardonne@gmail.com>
Co-authored-by: Sydney Achinger <78113809+squid-ney@users.noreply.github.com>
This commit is contained in:
Thomas Cardonne
2024-10-16 15:26:33 +02:00
committed by GitHub
parent 40ec720214
commit 8b1b2cfe4b
3 changed files with 86 additions and 21 deletions
+9
View File
@@ -0,0 +1,9 @@
---
'@backstage/plugin-home': patch
---
Improve Starred Entities UI to reduce whitespace and provide more context on the entities:
- Use the Entity Presentation API (via `<EntityDisplayName>`) to display the entity's name
- Component's `kind` and `spec.type` are displayed as a secondary text
- List items are condensed to reduce unnecessary spacing
@@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { entityRouteParams } from '@backstage/plugin-catalog-react';
import { Entity } from '@backstage/catalog-model';
import {
EntityDisplayName,
entityRouteParams,
} from '@backstage/plugin-catalog-react';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
@@ -23,21 +26,59 @@ import { Link } from 'react-router-dom';
import { entityRouteRef } from '@backstage/plugin-catalog-react';
import { useRouteRef } from '@backstage/core-plugin-api';
import { FavoriteToggle } from '@backstage/core-components';
import { makeStyles } from '@material-ui/core/styles';
type EntityListItemProps = {
entity: Entity;
onToggleStarredEntity: (entity: Entity) => void;
showKind?: boolean;
};
const useStyles = makeStyles(theme => ({
listItem: {
paddingBottom: theme.spacing(0),
paddingTop: theme.spacing(0),
},
secondary: {
textTransform: 'uppercase',
},
}));
export const StarredEntityListItem = ({
entity,
onToggleStarredEntity,
showKind,
}: EntityListItemProps) => {
const classes = useStyles();
const catalogEntityRoute = useRouteRef(entityRouteRef);
let secondaryText = '';
if (showKind) {
secondaryText += entity.kind.toLocaleLowerCase('en-US');
}
if (entity.spec && 'type' in entity.spec) {
if (showKind) {
secondaryText += ' — ';
}
secondaryText += (entity.spec as { type: string }).type.toLocaleLowerCase(
'en-US',
);
}
return (
<ListItem key={stringifyEntityRef(entity)}>
<ListItemIcon>
<ListItem
dense
className={classes.listItem}
component={Link}
button
to={catalogEntityRoute(entityRouteParams(entity))}
>
<ListItemIcon
// Prevent following the link when clicking on the icon
onClick={e => {
e.preventDefault();
}}
>
<FavoriteToggle
id={`remove-favorite-${entity.metadata.uid}`}
title="Remove entity from favorites"
@@ -45,9 +86,10 @@ export const StarredEntityListItem = ({
onToggle={() => onToggleStarredEntity(entity)}
/>
</ListItemIcon>
<Link to={catalogEntityRoute(entityRouteParams(entity))}>
<ListItemText primary={entity.metadata.title ?? entity.metadata.name} />
</Link>
<ListItemText
primary={<EntityDisplayName hideIcon entityRef={entity} />}
secondary={secondaryText}
/>
</ListItem>
);
};
@@ -28,22 +28,38 @@ import Tab from '@material-ui/core/Tab';
import React from 'react';
import useAsync from 'react-use/esm/useAsync';
import { StarredEntityListItem } from '../../components/StarredEntityListItem/StarredEntityListItem';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
tabs: {
marginBottom: theme.spacing(1),
},
list: {
paddingTop: 0,
paddingBottom: 0,
},
}));
/**
* Props for the StarredEntities component
*
* @public
*/
export type StarredEntitiesProps = {
noStarredEntitiesMessage?: React.ReactNode | undefined;
groupByKind?: boolean;
};
/**
* A component to display a list of starred entities for the user.
*
* @public
*/
export type StarredEntitiesProps = {
noStarredEntitiesMessage?: React.ReactNode | undefined;
groupByKind?: boolean;
};
export const Content = ({
noStarredEntitiesMessage,
groupByKind,
}: StarredEntitiesProps) => {
const classes = useStyles();
const catalogApi = useApi(catalogApiRef);
const { starredEntities, toggleStarredEntity } = useStarredEntities();
const [activeTab, setActiveTab] = React.useState(0);
@@ -57,12 +73,7 @@ export const Content = ({
return (
await catalogApi.getEntitiesByRefs({
entityRefs: [...starredEntities],
fields: [
'kind',
'metadata.namespace',
'metadata.name',
'metadata.title',
],
fields: ['kind', 'metadata.namespace', 'metadata.name', 'spec.type'],
})
).items.filter((e): e is Entity => !!e);
}, [catalogApi, starredEntities]);
@@ -95,7 +106,7 @@ export const Content = ({
) : (
<div>
{!groupByKind && (
<List>
<List className={classes.list}>
{entities.value
?.sort((a, b) =>
(a.metadata.title ?? a.metadata.name).localeCompare(
@@ -107,6 +118,7 @@ export const Content = ({
key={stringifyEntityRef(entity)}
entity={entity}
onToggleStarredEntity={toggleStarredEntity}
showKind
/>
))}
</List>
@@ -114,6 +126,7 @@ export const Content = ({
{groupByKind && (
<Tabs
className={classes.tabs}
value={activeTab}
onChange={(_, newValue) => setActiveTab(newValue)}
variant="scrollable"
@@ -129,7 +142,7 @@ export const Content = ({
{groupByKind &&
groupByKindEntries.map(([kind, entitiesByKind], index) => (
<div key={kind} hidden={groupByKind && activeTab !== index}>
<List>
<List className={classes.list}>
{entitiesByKind
?.sort((a, b) =>
(a.metadata.title ?? a.metadata.name).localeCompare(
@@ -141,6 +154,7 @@ export const Content = ({
key={stringifyEntityRef(entity)}
entity={entity}
onToggleStarredEntity={toggleStarredEntity}
showKind={false}
/>
))}
</List>