[Playlist] feat: added dynamic group noun through the config
Signed-off-by: antonio.bergas <anthonyprincedom@hotmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Vendored
+31
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export * from './usePlaylistList';
|
||||
export * from './useConfig';
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user