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:
@@ -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)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user