[Playlist] feat: added dynamic group noun through the config

Signed-off-by: antonio.bergas <anthonyprincedom@hotmail.com>
This commit is contained in:
antonio.bergas
2023-03-18 23:01:03 +01:00
parent 0182d61b98
commit f4f50d8eef
13 changed files with 181 additions and 46 deletions
+4
View File
@@ -125,6 +125,10 @@ proxy:
organization:
name: My Company
playlist:
groupPluralNoun: Collections
groupSingularNoun: Collection
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
# Note: After experimenting with basic setup, use CI/CD to generate docs
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright 2020 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 interface Config {
playlist: {
/**
* (Optional) The plural noun for the entities grouping that will shown in the UI; leave empty for `PLaylists`.
* @visibility frontend
*/
groupPluralNoun: string;
/**
* (Optional) The singular noun for the entities grouping that will shown in the UI; leave empty for `PLaylists`.
* @visibility frontend
*/
groupSingularNoun: string;
};
}
+22 -1
View File
@@ -66,5 +66,26 @@
},
"files": [
"dist"
]
],
"configSchema": {
"$schema": "https://backstage.io/schema/config-v1",
"type": "object",
"properties": {
"playlist": {
"type": "object",
"properties": {
"groupPluralNoun": {
"type": "string",
"description": "Frontend plural entities grouping name",
"visibility": "frontend"
},
"groupSingularNoun": {
"type": "string",
"description": "Frontend singular entities grouping name",
"visibility": "frontend"
}
}
}
}
}
}
@@ -14,7 +14,12 @@
* limitations under the License.
*/
import { errorApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import {
configApiRef,
errorApiRef,
useApi,
useRouteRef,
} from '@backstage/core-plugin-api';
import { BackstageTheme } from '@backstage/theme';
import { usePermission } from '@backstage/plugin-permission-react';
import {
@@ -29,6 +34,7 @@ import { useNavigate } from 'react-router-dom';
import { playlistApiRef } from '../../api';
import { playlistRouteRef } from '../../routes';
import { PlaylistEditDialog } from '../PlaylistEditDialog';
import { useGroupNoun } from '../../hooks/useConfig';
export const CreatePlaylistButton = () => {
const navigate = useNavigate();
@@ -55,13 +61,15 @@ export const CreatePlaylistButton = () => {
[errorApi, navigate, playlistApi, playlistRoute],
);
const groupSingularNounLowerCase = useGroupNoun(false, false);
return (
<>
{isXSScreen ? (
<IconButton
disabled={!allowed}
color="primary"
title="Create Playlist"
title={`Create ${groupSingularNounLowerCase}`}
size="small"
onClick={() => setOpenDialog(true)}
>
@@ -74,7 +82,7 @@ export const CreatePlaylistButton = () => {
color="primary"
onClick={() => setOpenDialog(true)}
>
Create Playlist
Create {groupSingularNounLowerCase}
</Button>
)}
<PlaylistEditDialog
@@ -16,7 +16,12 @@
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
import { ResponseErrorPanel } from '@backstage/core-components';
import { alertApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api';
import {
alertApiRef,
configApiRef,
useApi,
useRouteRef,
} from '@backstage/core-plugin-api';
import {
humanizeEntityRef,
useAsyncEntity,
@@ -51,6 +56,7 @@ import { useNavigate } from 'react-router-dom';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { playlistApiRef } from '../../api';
import { useGroupNoun } from '../../hooks/useConfig';
import { playlistRouteRef } from '../../routes';
import { PlaylistEditDialog } from '../PlaylistEditDialog';
@@ -104,6 +110,10 @@ export const EntityPlaylistDialog = (props: EntityPlaylistDialogProps) => {
[playlistApi],
);
const groupSingularNoun = useGroupNoun(true, false);
const groupSingularNounLowerCase = useGroupNoun(false, true);
const groupPluralNounLowerCase = useGroupNoun(true, true);
useEffect(() => {
if (open) {
loadPlaylists();
@@ -120,12 +130,19 @@ export const EntityPlaylistDialog = (props: EntityPlaylistDialogProps) => {
navigate(playlistRoute({ playlistId }));
} catch (e) {
alertApi.post({
message: `Failed to add entity to playlist: ${e}`,
message: `Failed to add entity to ${groupSingularNounLowerCase}: ${e}`,
severity: 'error',
});
}
},
[alertApi, entity, navigate, playlistApi, playlistRoute],
[
alertApi,
entity,
navigate,
playlistApi,
playlistRoute,
groupSingularNounLowerCase,
],
);
const [{ loading: addEntityLoading }, addToPlaylist] = useAsyncFn(
@@ -141,12 +158,12 @@ export const EntityPlaylistDialog = (props: EntityPlaylistDialogProps) => {
});
} catch (e) {
alertApi.post({
message: `Failed to add entity to playlist: ${e}`,
message: `Failed to add entity to ${groupSingularNounLowerCase}: ${e}`,
severity: 'error',
});
}
},
[alertApi, closeDialog, entity, playlistApi],
[alertApi, closeDialog, entity, playlistApi, groupSingularNounLowerCase],
);
return (
@@ -160,7 +177,7 @@ export const EntityPlaylistDialog = (props: EntityPlaylistDialogProps) => {
>
{(loading || addEntityLoading) && <LinearProgress />}
<DialogTitle className={classes.dialogTitle}>
Add to Playlist
Add to {groupSingularNoun}
<TextField
fullWidth
data-testid="entity-playlist-dialog-search"
@@ -191,7 +208,10 @@ export const EntityPlaylistDialog = (props: EntityPlaylistDialogProps) => {
</DialogTitle>
<DialogContent className={classes.dialogContent}>
{error && (
<ResponseErrorPanel title="Error loading playlists" error={error} />
<ResponseErrorPanel
title={`Error loading ${groupPluralNounLowerCase}`}
error={error}
/>
)}
{playlists && entity && (
<List>
@@ -205,7 +225,9 @@ export const EntityPlaylistDialog = (props: EntityPlaylistDialogProps) => {
<ListItemIcon>
<PlaylistAddIcon />
</ListItemIcon>
<ListItemText primary="Create new playlist" />
<ListItemText
primary={`Create new ${groupSingularNounLowerCase}`}
/>
</ListItem>
)}
{playlists
@@ -15,7 +15,11 @@
*/
import { parseEntityRef } from '@backstage/catalog-model';
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
import {
configApiRef,
identityApiRef,
useApi,
} from '@backstage/core-plugin-api';
import { humanizeEntityRef } from '@backstage/plugin-catalog-react';
import { PlaylistMetadata } from '@backstage/plugin-playlist-common';
import {
@@ -39,6 +43,7 @@ import React from 'react';
import { useForm, Controller } from 'react-hook-form';
import useAsync from 'react-use/lib/useAsync';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { useGroupNoun } from '../../hooks/useConfig';
const useStyles = makeStyles({
buttonWrapper: {
@@ -105,6 +110,8 @@ export const PlaylistEditDialog = ({
}
};
const groupSingularNounLowercase = useGroupNoun(false, false);
return (
<Dialog fullWidth maxWidth="xs" onClose={closeDialog} open={open}>
<DialogContent>
@@ -121,7 +128,7 @@ export const PlaylistEditDialog = ({
fullWidth
label="Name"
margin="dense"
placeholder="Give your playlist name"
placeholder={`Give your ${groupSingularNounLowercase} name`}
required
type="text"
/>
@@ -139,7 +146,7 @@ export const PlaylistEditDialog = ({
label="Description"
margin="dense"
multiline
placeholder="Describe your playlist"
placeholder={`Describe your ${groupSingularNounLowercase}`}
type="text"
/>
)}
@@ -23,7 +23,7 @@ import {
} from '@backstage/core-components';
import { CatalogFilterLayout } from '@backstage/plugin-catalog-react';
import { PlaylistListProvider } from '../../hooks';
import { PlaylistListProvider, useGroupNoun } from '../../hooks';
import { CreatePlaylistButton } from '../CreatePlaylistButton';
import { PersonalListPicker } from '../PersonalListPicker';
import { PlaylistList } from '../PlaylistList';
@@ -31,26 +31,30 @@ import { PlaylistOwnerPicker } from '../PlaylistOwnerPicker';
import { PlaylistSearchBar } from '../PlaylistSearchBar';
import { PlaylistSortPicker } from '../PlaylistSortPicker';
export const PlaylistIndexPage = () => (
<PageWithHeader themeId="home" title="Playlists">
<PlaylistListProvider>
<Content>
<ContentHeader title="">
<PlaylistSortPicker />
<CreatePlaylistButton />
<SupportButton />
</ContentHeader>
<CatalogFilterLayout>
<CatalogFilterLayout.Filters>
<PlaylistSearchBar />
<PersonalListPicker />
<PlaylistOwnerPicker />
</CatalogFilterLayout.Filters>
<CatalogFilterLayout.Content>
<PlaylistList />
</CatalogFilterLayout.Content>
</CatalogFilterLayout>
</Content>
</PlaylistListProvider>
</PageWithHeader>
);
export const PlaylistIndexPage = () => {
const groupPluralNoun = useGroupNoun(true, false);
return (
<PageWithHeader themeId="home" title={groupPluralNoun}>
<PlaylistListProvider>
<Content>
<ContentHeader title="">
<PlaylistSortPicker />
<CreatePlaylistButton />
<SupportButton />
</ContentHeader>
<CatalogFilterLayout>
<CatalogFilterLayout.Filters>
<PlaylistSearchBar />
<PersonalListPicker />
<PlaylistOwnerPicker />
</CatalogFilterLayout.Filters>
<CatalogFilterLayout.Content>
<PlaylistList />
</CatalogFilterLayout.Content>
</CatalogFilterLayout>
</Content>
</PlaylistListProvider>
</PageWithHeader>
);
};
@@ -23,18 +23,21 @@ import {
} from '@backstage/core-components';
import { Typography } from '@material-ui/core';
import { usePlaylistList } from '../../hooks';
import { useGroupNoun, usePlaylistList } from '../../hooks';
import { PlaylistCard } from '../PlaylistCard';
export const PlaylistList = () => {
const { loading, error, playlists } = usePlaylistList();
const groupPluralNounLowercase = useGroupNoun(true, true);
return (
<>
{loading && <Progress />}
{error && (
<WarningPanel title="Oops! Something went wrong loading playlists">
<WarningPanel
title={`Oops! Something went wrong loading ${groupPluralNounLowercase}`}
>
{error.message}
</WarningPanel>
)}
@@ -21,7 +21,7 @@ import {
Table,
TableFilter,
} from '@backstage/core-components';
import { errorApiRef, useApi } from '@backstage/core-plugin-api';
import { configApiRef, errorApiRef, useApi } from '@backstage/core-plugin-api';
import { EntityRefLink } from '@backstage/plugin-catalog-react';
import { usePermission } from '@backstage/plugin-permission-react';
import { permissions } from '@backstage/plugin-playlist-common';
@@ -32,6 +32,7 @@ import React, { forwardRef, useCallback, useEffect, useState } from 'react';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { playlistApiRef } from '../../api';
import { useGroupNoun } from '../../hooks/useConfig';
import { AddEntitiesDrawer } from './AddEntitiesDrawer';
export const PlaylistEntitiesTable = ({
@@ -41,6 +42,7 @@ export const PlaylistEntitiesTable = ({
}) => {
const errorApi = useApi(errorApiRef);
const playlistApi = useApi(playlistApiRef);
const configApi = useApi(configApiRef);
const [openAddEntitiesDrawer, setOpenAddEntitiesDrawer] = useState(false);
const { allowed: editAllowed } = usePermission({
@@ -84,16 +86,18 @@ export const PlaylistEntitiesTable = ({
[errorApi, loadEntities, playlistApi, playlistId],
);
const groupSingularNounLowerCase = useGroupNoun(false, true);
const actions = editAllowed
? [
{
icon: DeleteIcon,
tooltip: 'Remove from playlist',
tooltip: `Remove from ${groupSingularNounLowerCase}`,
onClick: removeEntity,
},
{
icon: AddBoxIcon,
tooltip: 'Add entities to playlist',
tooltip: `Add entities to ${groupSingularNounLowerCase}`,
isFreeAction: true,
onClick: () => setOpenAddEntitiesDrawer(true),
},
@@ -45,6 +45,7 @@ import useAsyncFn from 'react-use/lib/useAsyncFn';
import { playlistApiRef } from '../../api';
import { rootRouteRef } from '../../routes';
import { PlaylistEditDialog } from '../PlaylistEditDialog';
import { useGroupNoun } from '../../hooks/useConfig';
const useStyles = makeStyles({
buttonWrapper: {
@@ -109,6 +110,8 @@ export const PlaylistHeader = ({ playlist, onUpdate }: PlaylistHeaderProps) => {
}
}, [playlistApi]);
const groupSingularNoun = useGroupNoun(false, false);
return (
<Header
type={!playlist.public ? 'private' : undefined}
@@ -143,7 +146,7 @@ export const PlaylistHeader = ({ playlist, onUpdate }: PlaylistHeaderProps) => {
onClick: () => setOpenEditDialog(true),
},
{
label: 'Delete Playlist',
label: `Delete ${groupSingularNoun}`,
icon: <DeleteIcon />,
disabled: !deleteAllowed,
onClick: () => setOpenDeleteDialog(true),
+1
View File
@@ -15,3 +15,4 @@
*/
export * from './usePlaylistList';
export * from './useConfig';
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright 2022 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 { configApiRef, useApi } from '@backstage/core-plugin-api';
export function useGroupNoun(inPlural: boolean, inLowerCase: boolean) {
const configApi = useApi(configApiRef);
const defaultNoun = inPlural ? 'Playlists' : 'Playlist';
const configProp = inPlural ? 'groupPluralNoun' : 'groupSingularNoun';
const groupNoun =
configApi.getOptionalString(`playlist.${configProp}`) ?? `${defaultNoun}`;
return inLowerCase ? groupNoun.toLocaleLowerCase('en-US') : groupNoun;
}
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { useApi } from '@backstage/core-plugin-api';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { Playlist } from '@backstage/plugin-playlist-common';
import { compact, isEqual } from 'lodash';
import qs from 'qs';