Merge pull request #20121 from antoniobergas/feat/home-plugin-group-starred-entities-by-kind

feat(homePlugin): changed list view of starredEntities to facilitate…
This commit is contained in:
Djam
2023-11-27 09:42:07 +01:00
committed by GitHub
8 changed files with 174 additions and 47 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-home': minor
---
Added view of entities grouped by kind to make it easier to distinguish entities with different kind but same name
+7 -1
View File
@@ -148,7 +148,7 @@ export const HomePageRecentlyVisited: (
// @public
export const HomePageStarredEntities: (
props: CardExtensionProps_2<unknown>,
props: CardExtensionProps_2<Partial<StarredEntitiesProps>>,
) => JSX_2.Element;
// @public
@@ -192,6 +192,12 @@ export const SettingsModal: (props: {
children: JSX.Element;
}) => JSX_2.Element;
// @public
export type StarredEntitiesProps = {
noStarredEntitiesMessage?: React_2.ReactNode | undefined;
groupByKind?: boolean;
};
// @public (undocumented)
export const TemplateBackstageLogo: (props: {
classes: {
@@ -0,0 +1,60 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* 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 {
ListItem,
ListItemIcon,
Tooltip,
IconButton,
ListItemText,
} from '@material-ui/core';
import React from 'react';
import { Link } from 'react-router-dom';
import { entityRouteRef } from '@backstage/plugin-catalog-react';
import { useRouteRef } from '@backstage/core-plugin-api';
import StarIcon from '@material-ui/icons/Star';
type EntityListItemProps = {
entity: Entity;
onToggleStarredEntity: (entity: Entity) => void;
};
export const StarredEntityListItem = ({
entity,
onToggleStarredEntity,
}: EntityListItemProps) => {
const catalogEntityRoute = useRouteRef(entityRouteRef);
return (
<ListItem key={stringifyEntityRef(entity)}>
<ListItemIcon>
<Tooltip title="Remove from starred">
<IconButton
edge="end"
aria-label="unstar"
onClick={() => onToggleStarredEntity(entity)}
>
<StarIcon style={{ color: '#f3ba37' }} />
</IconButton>
</Tooltip>
</ListItemIcon>
<Link to={catalogEntityRoute(entityRouteParams(entity))}>
<ListItemText primary={entity.metadata.title ?? entity.metadata.name} />
</Link>
</ListItem>
);
};
@@ -0,0 +1,17 @@
/*
* Copyright 2023 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { StarredEntityListItem } from './StarredEntityListItem';
@@ -17,24 +17,18 @@
import {
catalogApiRef,
useStarredEntities,
entityRouteParams,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
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,
ListItemSecondaryAction,
IconButton,
ListItemText,
Tooltip,
Typography,
} from '@material-ui/core';
import StarIcon from '@material-ui/icons/Star';
Entity,
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { Progress, ResponseErrorPanel } from '@backstage/core-components';
import { List, Typography, Tabs, Tab } from '@material-ui/core';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
import { StarredEntityListItem } from '../../components/StarredEntityListItem/StarredEntityListItem';
/**
* A component to display a list of starred entities for the user.
@@ -42,12 +36,18 @@ import useAsync from 'react-use/lib/useAsync';
* @public
*/
export const Content = (props: {
export type StarredEntitiesProps = {
noStarredEntitiesMessage?: React.ReactNode | undefined;
}) => {
groupByKind?: boolean;
};
export const Content = ({
noStarredEntitiesMessage,
groupByKind,
}: StarredEntitiesProps) => {
const catalogApi = useApi(catalogApiRef);
const catalogEntityRoute = useRouteRef(entityRouteRef);
const { starredEntities, toggleStarredEntity } = useStarredEntities();
const [activeTab, setActiveTab] = React.useState(0);
// Grab starred entities from catalog to ensure they still exist and also retrieve display titles
const entities = useAsync(async () => {
@@ -79,7 +79,7 @@ export const Content = (props: {
if (starredEntities.size === 0)
return (
<Typography variant="body1">
{props.noStarredEntitiesMessage ||
{noStarredEntitiesMessage ||
'Click the star beside an entity name to add it to this list!'}
</Typography>
);
@@ -88,36 +88,73 @@ export const Content = (props: {
return <Progress />;
}
const groupedEntities: { [kind: string]: Entity[] } = {};
entities.value?.forEach(entity => {
const kind = entity.kind;
if (!groupedEntities[kind]) {
groupedEntities[kind] = [];
}
groupedEntities[kind].push(entity);
});
const groupByKindEntries = Object.entries(groupedEntities);
return entities.error ? (
<ResponseErrorPanel error={entities.error} />
) : (
<List>
{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}
<div>
{!groupByKind && (
<List>
{entities.value
?.sort((a, b) =>
(a.metadata.title ?? a.metadata.name).localeCompare(
b.metadata.title ?? b.metadata.name,
),
)
.map(entity => (
<StarredEntityListItem
key={stringifyEntityRef(entity)}
entity={entity}
onToggleStarredEntity={toggleStarredEntity}
/>
</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>
)}
{groupByKind && (
<Tabs
value={activeTab}
onChange={(_, newValue) => setActiveTab(newValue)}
variant="scrollable"
scrollButtons="auto"
aria-label="entity-tabs"
>
{groupByKindEntries.map(([kind]) => (
<Tab key={kind} label={kind} />
))}
</Tabs>
)}
{groupByKind &&
groupByKindEntries.map(([kind, entitiesByKind], index) => (
<div key={kind} hidden={groupByKind && activeTab !== index}>
<List>
{entitiesByKind
?.sort((a, b) =>
(a.metadata.title ?? a.metadata.name).localeCompare(
b.metadata.title ?? b.metadata.name,
),
)
.map(entity => (
<StarredEntityListItem
key={stringifyEntityRef(entity)}
entity={entity}
onToggleStarredEntity={toggleStarredEntity}
/>
))}
</List>
</div>
))}
</List>
</div>
);
};
@@ -13,5 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Content } from './Content';
export type { StarredEntitiesProps } from './Content';
@@ -19,3 +19,4 @@ export type { ClockConfig } from './HeaderWorldClock';
export type { WelcomeTitleLanguageProps } from './WelcomeTitle';
export type { VisitedByTypeProps, VisitedByTypeKind } from './VisitedByType';
export type { FeaturedDocsCardProps } from './FeaturedDocsCard';
export type { StarredEntitiesProps } from './StarredEntities';
+2 -1
View File
@@ -30,6 +30,7 @@ import {
} from './homePageComponents';
import { rootRouteRef } from './routes';
import { VisitsStorageApi, visitsApiRef } from './api';
import { StarredEntitiesProps } from './homePageComponents/StarredEntities/Content';
/** @public */
export const homePlugin = createPlugin({
@@ -168,7 +169,7 @@ export const HomePageToolkit = homePlugin.provide(
* @public
*/
export const HomePageStarredEntities = homePlugin.provide(
createCardExtension({
createCardExtension<Partial<StarredEntitiesProps>>({
name: 'HomePageStarredEntities',
title: 'Your Starred Entities',
components: () => import('./homePageComponents/StarredEntities'),