Limit the use of the same playlist name when adding a playlist

Signed-off-by: AmbrishRamachandiran <ambrish.r@infosys.com>
This commit is contained in:
AmbrishRamachandiran
2023-08-17 16:33:56 +05:30
parent f9faeca0a6
commit b5ba33a927
3 changed files with 116 additions and 3 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-playlist': patch
---
Limit the use of the same playlist name when adding a playlist
@@ -18,11 +18,32 @@ import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { fireEvent, getByRole, waitFor } from '@testing-library/react';
import { act } from '@testing-library/react-hooks';
import { PlaylistApi, playlistApiRef } from '../../api';
import React from 'react';
import { PlaylistEditDialog } from './PlaylistEditDialog';
describe('<PlaylistEditDialog/>', () => {
const samplePlaylists = [
{
id: 'id1',
name: 'playlist-1',
owner: 'group:default/some-owner',
public: true,
entities: 1,
followers: 2,
isFollowing: false,
},
{
id: 'id2',
name: 'playlist-2',
owner: 'group:default/another-owner',
public: true,
entities: 2,
followers: 1,
isFollowing: true,
},
];
it('handle saving with an edited playlist', async () => {
const identityApi: Partial<IdentityApi> = {
getBackstageIdentity: async () => ({
@@ -33,8 +54,18 @@ describe('<PlaylistEditDialog/>', () => {
};
const mockOnSave = jest.fn().mockImplementation(async () => {});
const playlistApi: Partial<PlaylistApi> = {
getAllPlaylists: jest
.fn()
.mockImplementation(async () => samplePlaylists),
};
const rendered = await renderInTestApp(
<TestApiProvider apis={[[identityApiRef, identityApi]]}>
<TestApiProvider
apis={[
[identityApiRef, identityApi],
[playlistApiRef, playlistApi],
]}
>
<PlaylistEditDialog open onClose={jest.fn()} onSave={mockOnSave} />
</TestApiProvider>,
);
@@ -83,4 +114,53 @@ describe('<PlaylistEditDialog/>', () => {
});
});
});
it('displays duplicate validation message for playlist name', async () => {
const identityApi: Partial<IdentityApi> = {
getBackstageIdentity: async () => ({
type: 'user',
userEntityRef: 'user:default/me',
ownershipEntityRefs: ['group:default/test-owner', 'user:default/me'],
}),
};
const mockOnSave = jest.fn().mockImplementation(async () => {});
const playlistApi: Partial<PlaylistApi> = {
getAllPlaylists: jest
.fn()
.mockImplementation(async () => [...samplePlaylists]),
};
const rendered = await renderInTestApp(
<TestApiProvider
apis={[
[identityApiRef, identityApi],
[playlistApiRef, playlistApi],
]}
>
<PlaylistEditDialog open onClose={jest.fn()} onSave={mockOnSave} />
</TestApiProvider>,
);
act(() => {
fireEvent.input(
getByRole(rendered.getByTestId('edit-dialog-name-input'), 'textbox'),
{
target: {
value: 'playlist-1',
},
},
);
fireEvent.click(rendered.getByTestId('edit-dialog-save-button'));
});
await waitFor(() => {
expect(
rendered.getByText('A playlist with this name already exists'),
).toBeInTheDocument();
});
expect(mockOnSave).not.toHaveBeenCalled();
});
});
@@ -18,6 +18,7 @@ import { parseEntityRef } from '@backstage/catalog-model';
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
import { humanizeEntityRef } from '@backstage/plugin-catalog-react';
import { PlaylistMetadata } from '@backstage/plugin-playlist-common';
import { playlistApiRef } from '../../api';
import {
Button,
CircularProgress,
@@ -35,7 +36,7 @@ import {
Select,
TextField,
} from '@material-ui/core';
import React from 'react';
import React, { useState } from 'react'; // Import useState
import { useForm, Controller } from 'react-hook-form';
import useAsync from 'react-use/lib/useAsync';
import useAsyncFn from 'react-use/lib/useAsyncFn';
@@ -74,6 +75,15 @@ export const PlaylistEditDialog = ({
}: PlaylistEditDialogProps) => {
const classes = useStyles();
const identityApi = useApi(identityApiRef);
const playlistApi = useApi(playlistApiRef);
const playListApiData = playlistApi.getAllPlaylists();
const [editingOtherFields, setEditingOtherFields] = useState(false);
const fetchAndProcessData = async () => {
const playlistArray = await playListApiData;
return playlistArray;
};
const { loading: loadingOwnership, value: ownershipRefs } =
useAsync(async () => {
@@ -81,6 +91,18 @@ export const PlaylistEditDialog = ({
return ownershipEntityRefs;
}, []);
const nameIsUnique = async (name: string) => {
const playlistArray = await fetchAndProcessData();
if (
editingOtherFields ||
(await playlistArray.some(
(playlistData: { name: string }) => playlistData.name === name,
))
)
return 'A playlist with this name already exists';
return true;
};
const defaultValues = {
...playlist,
public: playlist.public.toString(),
@@ -103,6 +125,7 @@ export const PlaylistEditDialog = ({
if (!saving.loading) {
onClose();
reset(defaultValues);
setEditingOtherFields(false);
}
};
@@ -117,13 +140,17 @@ export const PlaylistEditDialog = ({
<Controller
name="name"
control={control}
rules={{ required: true }}
rules={{
required: true,
validate: editingOtherFields ? undefined : nameIsUnique,
}}
render={({ field }) => (
<TextField
{...field}
disabled={saving.loading}
data-testid="edit-dialog-name-input"
error={!!errors.name}
helperText={errors.name?.message}
fullWidth
label="Name"
margin="dense"
@@ -147,6 +174,7 @@ export const PlaylistEditDialog = ({
multiline
placeholder={`Describe your ${titleSingularLowerCase}`}
type="text"
onBlur={() => setEditingOtherFields(true)}
/>
)}
/>