From b5ba33a9275076483fdfa6a39d42e86e022ce673 Mon Sep 17 00:00:00 2001 From: AmbrishRamachandiran Date: Thu, 17 Aug 2023 16:33:56 +0530 Subject: [PATCH] Limit the use of the same playlist name when adding a playlist Signed-off-by: AmbrishRamachandiran --- .changeset/swift-frogs-drop.md | 5 ++ .../PlaylistEditDialog.test.tsx | 82 ++++++++++++++++++- .../PlaylistEditDialog/PlaylistEditDialog.tsx | 32 +++++++- 3 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 .changeset/swift-frogs-drop.md diff --git a/.changeset/swift-frogs-drop.md b/.changeset/swift-frogs-drop.md new file mode 100644 index 0000000000..3edff21ca2 --- /dev/null +++ b/.changeset/swift-frogs-drop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-playlist': patch +--- + +Limit the use of the same playlist name when adding a playlist diff --git a/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.test.tsx b/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.test.tsx index 9f30d7608c..b01b5acbf2 100644 --- a/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.test.tsx +++ b/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.test.tsx @@ -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('', () => { + 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 = { getBackstageIdentity: async () => ({ @@ -33,8 +54,18 @@ describe('', () => { }; const mockOnSave = jest.fn().mockImplementation(async () => {}); + const playlistApi: Partial = { + getAllPlaylists: jest + .fn() + .mockImplementation(async () => samplePlaylists), + }; const rendered = await renderInTestApp( - + , ); @@ -83,4 +114,53 @@ describe('', () => { }); }); }); + + it('displays duplicate validation message for playlist name', async () => { + const identityApi: Partial = { + 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 = { + getAllPlaylists: jest + .fn() + .mockImplementation(async () => [...samplePlaylists]), + }; + + const rendered = await renderInTestApp( + + + , + ); + + 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(); + }); }); diff --git a/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.tsx b/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.tsx index a213c7c51c..67749042cb 100644 --- a/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.tsx +++ b/plugins/playlist/src/components/PlaylistEditDialog/PlaylistEditDialog.tsx @@ -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 = ({ ( setEditingOtherFields(true)} /> )} />