From f0e2ef3b814cc94818c6096a33679d90ad3ca4aa Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Mon, 30 Oct 2023 17:23:29 -0400 Subject: [PATCH] feat(playlists): support custom index page Signed-off-by: Phil Kuang --- .changeset/thick-tigers-call.md | 17 +++ packages/app/src/App.tsx | 3 +- plugins/playlist/README.md | 19 +++- plugins/playlist/api-report.md | 102 ++++++++++++++++++ plugins/playlist/dev/index.tsx | 7 +- .../CreatePlaylistButton.test.tsx | 9 +- .../CreatePlaylistButton.tsx | 3 + .../EntityPlaylistDialog.test.tsx | 9 +- .../PersonalListPicker/PersonalListPicker.tsx | 9 ++ .../PlaylistCard/PlaylistCard.test.tsx | 3 +- .../DefaultPlaylistIndexPage.test.tsx | 57 ++++++++++ .../DefaultPlaylistIndexPage.tsx | 67 ++++++++++++ .../PlaylistIndexPage.test.tsx | 52 ++++----- .../PlaylistIndexPage/PlaylistIndexPage.tsx | 49 +-------- .../src/components/PlaylistIndexPage/index.ts | 1 + .../components/PlaylistList/PlaylistList.tsx | 3 + .../PlaylistOwnerPicker.tsx | 6 ++ .../PlaylistSearchBar/PlaylistSearchBar.tsx | 6 ++ .../PlaylistSortPicker/PlaylistSortPicker.tsx | 3 + .../src/components/Router/Router.test.tsx | 51 --------- .../playlist/src/components/Router/Router.tsx | 29 ----- .../playlist/src/components/Router/index.ts | 17 --- .../src/hooks/PlaylistListProvider.tsx | 3 + .../playlist/src/hooks/usePlaylistList.tsx | 6 ++ plugins/playlist/src/index.ts | 20 +++- plugins/playlist/src/plugin.ts | 17 ++- plugins/playlist/src/routes.ts | 7 +- plugins/playlist/src/types.ts | 3 + 28 files changed, 391 insertions(+), 187 deletions(-) create mode 100644 .changeset/thick-tigers-call.md create mode 100644 plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.test.tsx create mode 100644 plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.tsx delete mode 100644 plugins/playlist/src/components/Router/Router.test.tsx delete mode 100644 plugins/playlist/src/components/Router/Router.tsx delete mode 100644 plugins/playlist/src/components/Router/index.ts diff --git a/.changeset/thick-tigers-call.md b/.changeset/thick-tigers-call.md new file mode 100644 index 0000000000..4a9c66a354 --- /dev/null +++ b/.changeset/thick-tigers-call.md @@ -0,0 +1,17 @@ +--- +'@backstage/plugin-playlist': minor +--- + +Support being able to define custom composable Playlist index pages + +**BREAKING** The individual `PlaylistPage` route must now be manually hooked up by making the following change to your setup: + +```diff +-import { PlaylistIndexPage } from '@backstage/plugin-playlist'; ++import { PlaylistIndexPage, PlaylistPage } from '@backstage/plugin-playlist'; + +// ... + + } /> ++} /> +``` diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index cc3787a8bc..c2296d01e7 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -109,7 +109,7 @@ import { techDocsPage } from './components/techdocs/TechDocsPage'; import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow'; import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; -import { PlaylistIndexPage } from '@backstage/plugin-playlist'; +import { PlaylistIndexPage, PlaylistPage } from '@backstage/plugin-playlist'; import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts'; import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card'; import { StackstormPage } from '@backstage/plugin-stackstorm'; @@ -316,6 +316,7 @@ const routes = ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/plugins/playlist/README.md b/plugins/playlist/README.md index 1e81d07e56..d326bcdedb 100644 --- a/plugins/playlist/README.md +++ b/plugins/playlist/README.md @@ -23,12 +23,12 @@ yarn --cwd packages/app add @backstage/plugin-playlist ### Add the plugin to your `packages/app` -Add the root page that the playlist plugin provides to your app. You can -choose any path for the route, but we recommend the following: +Add the pages that the playlist plugin provides to your app. You can +choose any base path for the route, but we recommend the following: ```diff // packages/app/src/App.tsx -+import { PlaylistIndexPage } from '@backstage/plugin-playlist'; ++import { PlaylistIndexPage, PlaylistPage } from '@backstage/plugin-playlist'; @@ -37,6 +37,7 @@ choose any path for the route, but we recommend the following: {entityPage} + } /> ++ } /> ... ``` @@ -140,6 +141,18 @@ playlist: title: Collection ``` +## Custom Index Page + +You can customize your playlist index page by composing your own implementation. See the [`DefaultPlaylistIndexPage`](./src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.tsx) for a reference of what components are available from the default setup. + +```ts +- } /> ++ }> ++ ++ + } /> +``` + ## Features ### View All Playlists diff --git a/plugins/playlist/api-report.md b/plugins/playlist/api-report.md index be7605d1e6..316b7746bd 100644 --- a/plugins/playlist/api-report.md +++ b/plugins/playlist/api-report.md @@ -13,8 +13,24 @@ import { FetchApi } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { Playlist } from '@backstage/plugin-playlist-common'; import { PlaylistMetadata } from '@backstage/plugin-playlist-common'; +import { PropsWithChildren } from 'react'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; +// @public (undocumented) +export const CreatePlaylistButton: () => React_2.JSX.Element; + +// @public (undocumented) +export type DefaultPlaylistFilters = { + noop?: NoopFilter; + owners?: PlaylistOwnerFilter; + personal?: PersonalListFilter; + text?: PlaylistTextFilter; +}; + +// @public (undocumented) +export const DefaultPlaylistIndexPage: () => React_2.JSX.Element; + // @public (undocumented) export const EntityPlaylistDialog: ( props: EntityPlaylistDialogProps, @@ -35,6 +51,43 @@ export interface GetAllPlaylistsRequest { | Record; } +// @public (undocumented) +export class NoopFilter implements PlaylistFilter { + // (undocumented) + getBackendFilters(): { + '': null; + }; +} + +// @public (undocumented) +export class PersonalListFilter implements PlaylistFilter { + constructor( + value: PersonalListFilterValue, + isOwnedPlaylist: (playlist: Playlist) => boolean, + ); + // (undocumented) + filterPlaylist(playlist: Playlist): boolean; + // (undocumented) + readonly isOwnedPlaylist: (playlist: Playlist) => boolean; + // (undocumented) + toQueryValue(): string; + // (undocumented) + readonly value: PersonalListFilterValue; +} + +// @public (undocumented) +export const enum PersonalListFilterValue { + // (undocumented) + all = 'all', + // (undocumented) + following = 'following', + // (undocumented) + owned = 'owned', +} + +// @public (undocumented) +export const PersonalListPicker: () => React_2.JSX.Element; + // @public (undocumented) export interface PlaylistApi { // (undocumented) @@ -93,9 +146,43 @@ export class PlaylistClient implements PlaylistApi { updatePlaylist(playlist: PlaylistMetadata): Promise; } +// @public (undocumented) +export type PlaylistFilter = { + getBackendFilters?: () => Record; + filterPlaylist?: (playlist: Playlist) => boolean; + toQueryValue?: () => string | string[]; +}; + // @public (undocumented) export const PlaylistIndexPage: () => JSX_2.Element; +// @public (undocumented) +export const PlaylistList: () => React_2.JSX.Element; + +// @public (undocumented) +export const PlaylistListProvider: < + PlaylistFilters extends DefaultPlaylistFilters, +>({ + children, +}: PropsWithChildren<{}>) => React_2.JSX.Element; + +// @public (undocumented) +export class PlaylistOwnerFilter implements PlaylistFilter { + constructor(values: string[]); + // (undocumented) + filterPlaylist(playlist: Playlist): boolean; + // (undocumented) + toQueryValue(): string[]; + // (undocumented) + readonly values: string[]; +} + +// @public (undocumented) +export const PlaylistOwnerPicker: () => React_2.JSX.Element | null; + +// @public (undocumented) +export const PlaylistPage: () => JSX_2.Element; + // @public (undocumented) export const playlistPlugin: BackstagePlugin< { @@ -103,4 +190,19 @@ export const playlistPlugin: BackstagePlugin< }, {} >; + +// @public (undocumented) +export const PlaylistSearchBar: () => React_2.JSX.Element; + +// @public (undocumented) +export const PlaylistSortPicker: () => React_2.JSX.Element; + +// @public (undocumented) +export class PlaylistTextFilter implements PlaylistFilter { + constructor(value: string); + // (undocumented) + filterPlaylist(playlist: Playlist): boolean; + // (undocumented) + readonly value: string; +} ``` diff --git a/plugins/playlist/dev/index.tsx b/plugins/playlist/dev/index.tsx index c9c626ab38..38cda2291f 100644 --- a/plugins/playlist/dev/index.tsx +++ b/plugins/playlist/dev/index.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { playlistPlugin, PlaylistIndexPage } from '../src/plugin'; +import { PlaylistIndexPage, PlaylistPage, playlistPlugin } from '../src/plugin'; createDevApp() .registerPlugin(playlistPlugin) @@ -24,4 +24,9 @@ createDevApp() title: 'Root Page', path: '/playlist', }) + .addPage({ + element: , + title: 'Root Page', + path: '/playlist/:playlistId', + }) .render(); diff --git a/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.test.tsx b/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.test.tsx index c21283bcce..ab839c9a85 100644 --- a/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.test.tsx +++ b/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.test.tsx @@ -26,7 +26,7 @@ import { fireEvent, waitFor, act } from '@testing-library/react'; import React from 'react'; import { SWRConfig } from 'swr'; import { PlaylistApi, playlistApiRef } from '../../api'; -import { rootRouteRef } from '../../routes'; +import { playlistRouteRef, rootRouteRef } from '../../routes'; import { CreatePlaylistButton } from './CreatePlaylistButton'; jest.mock('../PlaylistEditDialog', () => ({ @@ -73,7 +73,12 @@ describe('', () => { , - { mountedRoutes: { '/playlists': rootRouteRef } }, + { + mountedRoutes: { + '/playlists': rootRouteRef, + '/playlists/:playlistId': playlistRouteRef, + }, + }, ); }; diff --git a/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.tsx b/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.tsx index d88ffbcda4..ac32097680 100644 --- a/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.tsx +++ b/plugins/playlist/src/components/CreatePlaylistButton/CreatePlaylistButton.tsx @@ -36,6 +36,9 @@ import { playlistRouteRef } from '../../routes'; import { PlaylistEditDialog } from '../PlaylistEditDialog'; import { useTitle } from '../../hooks'; +/** + * @public + */ export const CreatePlaylistButton = () => { const navigate = useNavigate(); const errorApi = useApi(errorApiRef); diff --git a/plugins/playlist/src/components/EntityPlaylistDialog/EntityPlaylistDialog.test.tsx b/plugins/playlist/src/components/EntityPlaylistDialog/EntityPlaylistDialog.test.tsx index 68e00b6b96..3595f42d00 100644 --- a/plugins/playlist/src/components/EntityPlaylistDialog/EntityPlaylistDialog.test.tsx +++ b/plugins/playlist/src/components/EntityPlaylistDialog/EntityPlaylistDialog.test.tsx @@ -28,7 +28,7 @@ import { fireEvent, getByRole, waitFor, act } from '@testing-library/react'; import React from 'react'; import { SWRConfig } from 'swr'; import { PlaylistApi, playlistApiRef } from '../../api'; -import { rootRouteRef } from '../../routes'; +import { playlistRouteRef, rootRouteRef } from '../../routes'; import { EntityPlaylistDialog } from './EntityPlaylistDialog'; const navigateMock = jest.fn(); @@ -103,7 +103,12 @@ describe('EntityPlaylistDialog', () => { , , - { mountedRoutes: { '/playlists': rootRouteRef } }, + { + mountedRoutes: { + '/playlists': rootRouteRef, + '/playlists/:playlistId': playlistRouteRef, + }, + }, ); beforeEach(() => { diff --git a/plugins/playlist/src/components/PersonalListPicker/PersonalListPicker.tsx b/plugins/playlist/src/components/PersonalListPicker/PersonalListPicker.tsx index 1172f2f6fa..20bb5ba68a 100644 --- a/plugins/playlist/src/components/PersonalListPicker/PersonalListPicker.tsx +++ b/plugins/playlist/src/components/PersonalListPicker/PersonalListPicker.tsx @@ -41,12 +41,18 @@ import useAsync from 'react-use/lib/useAsync'; import { usePlaylistList } from '../../hooks'; import { PlaylistFilter } from '../../types'; +/** + * @public + */ export const enum PersonalListFilterValue { owned = 'owned', following = 'following', all = 'all', } +/** + * @public + */ export class PersonalListFilter implements PlaylistFilter { constructor( readonly value: PersonalListFilterValue, @@ -131,6 +137,9 @@ function getFilterGroups(orgName: string | undefined): ButtonGroup[] { ]; } +/** + * @public + */ export const PersonalListPicker = () => { const classes = useStyles(); const configApi = useApi(configApiRef); diff --git a/plugins/playlist/src/components/PlaylistCard/PlaylistCard.test.tsx b/plugins/playlist/src/components/PlaylistCard/PlaylistCard.test.tsx index 5a34aa93e2..16b1b87203 100644 --- a/plugins/playlist/src/components/PlaylistCard/PlaylistCard.test.tsx +++ b/plugins/playlist/src/components/PlaylistCard/PlaylistCard.test.tsx @@ -18,7 +18,7 @@ import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; import React from 'react'; -import { rootRouteRef } from '../../routes'; +import { playlistRouteRef, rootRouteRef } from '../../routes'; import { PlaylistCard } from './PlaylistCard'; describe('', () => { @@ -39,6 +39,7 @@ describe('', () => { { mountedRoutes: { '/playlists': rootRouteRef, + '/playlists/:playlistId': playlistRouteRef, '/catalog/:namespace/:kind/:name': entityRouteRef, }, }, diff --git a/plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.test.tsx b/plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.test.tsx new file mode 100644 index 0000000000..35feb9cdae --- /dev/null +++ b/plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.test.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2023 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { + PermissionApi, + permissionApiRef, +} from '@backstage/plugin-permission-react'; +import React from 'react'; + +import { PlaylistApi, playlistApiRef } from '../../api'; +import { playlistRouteRef, rootRouteRef } from '../../routes'; +import { DefaultPlaylistIndexPage } from './DefaultPlaylistIndexPage'; + +const playlistApi: Partial = { + getAllPlaylists: async () => [], +}; + +const permissionApi: Partial = { + authorize: async () => ({ result: AuthorizeResult.ALLOW }), +}; + +describe('DefaultPlaylistIndexPage', () => { + it('should render', async () => { + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/playlists': rootRouteRef, + '/playlists/:playlistId': playlistRouteRef, + }, + }, + ); + expect(rendered.getByText('Playlists')).toBeInTheDocument(); + }); +}); diff --git a/plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.tsx b/plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.tsx new file mode 100644 index 0000000000..a2df92e532 --- /dev/null +++ b/plugins/playlist/src/components/PlaylistIndexPage/DefaultPlaylistIndexPage.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2023 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 React from 'react'; +import { + PageWithHeader, + Content, + ContentHeader, + SupportButton, +} from '@backstage/core-components'; +import { CatalogFilterLayout } from '@backstage/plugin-catalog-react'; + +import { CreatePlaylistButton } from '../CreatePlaylistButton'; +import { PersonalListPicker } from '../PersonalListPicker'; +import { PlaylistList } from '../PlaylistList'; +import { PlaylistOwnerPicker } from '../PlaylistOwnerPicker'; +import { PlaylistSearchBar } from '../PlaylistSearchBar'; +import { PlaylistSortPicker } from '../PlaylistSortPicker'; +import { PlaylistListProvider } from '../../hooks/PlaylistListProvider'; +import { useTitle } from '../../hooks/useTitle'; + +/** + * @public + */ +export const DefaultPlaylistIndexPage = () => { + const pluralTitle = useTitle({ + pluralize: true, + lowerCase: false, + }); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.test.tsx b/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.test.tsx index 6dae17d3bc..5dbe621787 100644 --- a/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.test.tsx +++ b/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.test.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Backstage Authors + * Copyright 2023 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. @@ -14,39 +14,33 @@ * limitations under the License. */ -import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { AuthorizeResult } from '@backstage/plugin-permission-common'; -import { - PermissionApi, - permissionApiRef, -} from '@backstage/plugin-permission-react'; import React from 'react'; - -import { PlaylistApi, playlistApiRef } from '../../api'; -import { rootRouteRef } from '../../routes'; +import { renderInTestApp } from '@backstage/test-utils'; +import { useOutlet } from 'react-router-dom'; import { PlaylistIndexPage } from './PlaylistIndexPage'; -const playlistApi: Partial = { - getAllPlaylists: async () => [], -}; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useOutlet: jest.fn().mockReturnValue('Route Children'), +})); -const permissionApi: Partial = { - authorize: async () => ({ result: AuthorizeResult.ALLOW }), -}; +jest.mock('./DefaultPlaylistIndexPage', () => ({ + DefaultPlaylistIndexPage: jest + .fn() + .mockReturnValue('DefaultPlaylistIndexPage'), +})); describe('PlaylistIndexPage', () => { - it('should render', async () => { - const rendered = await renderInTestApp( - - - , - { mountedRoutes: { '/playlists': rootRouteRef } }, - ); - expect(rendered.getByText('Playlists')).toBeInTheDocument(); + it('renders provided router element', async () => { + const { getByText } = await renderInTestApp(); + + expect(getByText('Route Children')).toBeInTheDocument(); + }); + + it('renders DefaultPlaylistIndexPage when no router children are provided', async () => { + (useOutlet as jest.Mock).mockReturnValueOnce(null); + const { getByText } = await renderInTestApp(); + + expect(getByText('DefaultPlaylistIndexPage')).toBeInTheDocument(); }); }); diff --git a/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.tsx b/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.tsx index 9f8b4e1576..283d33c96d 100644 --- a/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.tsx +++ b/plugins/playlist/src/components/PlaylistIndexPage/PlaylistIndexPage.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2022 The Backstage Authors + * Copyright 2023 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. @@ -15,50 +15,11 @@ */ import React from 'react'; -import { - PageWithHeader, - Content, - ContentHeader, - SupportButton, -} from '@backstage/core-components'; -import { CatalogFilterLayout } from '@backstage/plugin-catalog-react'; - -import { CreatePlaylistButton } from '../CreatePlaylistButton'; -import { PersonalListPicker } from '../PersonalListPicker'; -import { PlaylistList } from '../PlaylistList'; -import { PlaylistOwnerPicker } from '../PlaylistOwnerPicker'; -import { PlaylistSearchBar } from '../PlaylistSearchBar'; -import { PlaylistSortPicker } from '../PlaylistSortPicker'; -import { PlaylistListProvider } from '../../hooks/PlaylistListProvider'; -import { useTitle } from '../../hooks/useTitle'; +import { useOutlet } from 'react-router-dom'; +import { DefaultPlaylistIndexPage } from './DefaultPlaylistIndexPage'; export const PlaylistIndexPage = () => { - const pluralTitle = useTitle({ - pluralize: true, - lowerCase: false, - }); + const outlet = useOutlet(); - return ( - - - - - - - - - - - - - - - - - - - - - - ); + return outlet || ; }; diff --git a/plugins/playlist/src/components/PlaylistIndexPage/index.ts b/plugins/playlist/src/components/PlaylistIndexPage/index.ts index 06858a8262..5c39468d74 100644 --- a/plugins/playlist/src/components/PlaylistIndexPage/index.ts +++ b/plugins/playlist/src/components/PlaylistIndexPage/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +export { DefaultPlaylistIndexPage } from './DefaultPlaylistIndexPage'; export { PlaylistIndexPage } from './PlaylistIndexPage'; diff --git a/plugins/playlist/src/components/PlaylistList/PlaylistList.tsx b/plugins/playlist/src/components/PlaylistList/PlaylistList.tsx index 2f815f07c6..6ec06f3e05 100644 --- a/plugins/playlist/src/components/PlaylistList/PlaylistList.tsx +++ b/plugins/playlist/src/components/PlaylistList/PlaylistList.tsx @@ -26,6 +26,9 @@ import { Typography } from '@material-ui/core'; import { useTitle, usePlaylistList } from '../../hooks'; import { PlaylistCard } from '../PlaylistCard'; +/** + * @public + */ export const PlaylistList = () => { const { loading, error, playlists } = usePlaylistList(); const pluralTitleLowerCase = useTitle({ diff --git a/plugins/playlist/src/components/PlaylistOwnerPicker/PlaylistOwnerPicker.tsx b/plugins/playlist/src/components/PlaylistOwnerPicker/PlaylistOwnerPicker.tsx index 7def794c6b..d7fd97ff19 100644 --- a/plugins/playlist/src/components/PlaylistOwnerPicker/PlaylistOwnerPicker.tsx +++ b/plugins/playlist/src/components/PlaylistOwnerPicker/PlaylistOwnerPicker.tsx @@ -34,6 +34,9 @@ import React, { useEffect, useMemo, useState } from 'react'; import { usePlaylistList } from '../../hooks'; import { PlaylistFilter } from '../../types'; +/** + * @public + */ export class PlaylistOwnerFilter implements PlaylistFilter { constructor(readonly values: string[]) {} @@ -49,6 +52,9 @@ export class PlaylistOwnerFilter implements PlaylistFilter { const icon = ; const checkedIcon = ; +/** + * @public + */ export const PlaylistOwnerPicker = () => { const { updateFilters, diff --git a/plugins/playlist/src/components/PlaylistSearchBar/PlaylistSearchBar.tsx b/plugins/playlist/src/components/PlaylistSearchBar/PlaylistSearchBar.tsx index 94a22a23f5..44e0b28c13 100644 --- a/plugins/playlist/src/components/PlaylistSearchBar/PlaylistSearchBar.tsx +++ b/plugins/playlist/src/components/PlaylistSearchBar/PlaylistSearchBar.tsx @@ -31,6 +31,9 @@ import useDebounce from 'react-use/lib/useDebounce'; import { usePlaylistList } from '../../hooks'; import { PlaylistFilter } from '../../types'; +/** + * @public + */ export class PlaylistTextFilter implements PlaylistFilter { constructor(readonly value: string) {} @@ -52,6 +55,9 @@ const useStyles = makeStyles(_theme => ({ }, })); +/** + * @public + */ export const PlaylistSearchBar = () => { const classes = useStyles(); diff --git a/plugins/playlist/src/components/PlaylistSortPicker/PlaylistSortPicker.tsx b/plugins/playlist/src/components/PlaylistSortPicker/PlaylistSortPicker.tsx index 01e3bd08ff..219792e2dc 100644 --- a/plugins/playlist/src/components/PlaylistSortPicker/PlaylistSortPicker.tsx +++ b/plugins/playlist/src/components/PlaylistSortPicker/PlaylistSortPicker.tsx @@ -74,6 +74,9 @@ const useStyles = makeStyles({ }, }); +/** + * @public + */ export const PlaylistSortPicker = () => { const classes = useStyles(); const { updateSort } = usePlaylistList(); diff --git a/plugins/playlist/src/components/Router/Router.test.tsx b/plugins/playlist/src/components/Router/Router.test.tsx deleted file mode 100644 index ea8620ec42..0000000000 --- a/plugins/playlist/src/components/Router/Router.test.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 React from 'react'; -import { PlaylistIndexPage } from '../PlaylistIndexPage'; -import { PlaylistPage } from '../PlaylistPage'; -import { Router } from './Router'; -import { renderInTestApp } from '@backstage/test-utils'; - -jest.mock('../PlaylistIndexPage', () => ({ - PlaylistIndexPage: jest.fn(() => null), -})); - -jest.mock('../PlaylistPage', () => ({ - PlaylistPage: jest.fn(() => null), -})); - -describe('Router', () => { - beforeEach(() => { - (PlaylistPage as jest.Mock).mockClear(); - (PlaylistIndexPage as jest.Mock).mockClear(); - }); - describe('/', () => { - it('should render the PlaylistIndexPage', async () => { - await renderInTestApp(); - expect(PlaylistIndexPage).toHaveBeenCalled(); - }); - }); - - describe('/:playlistId', () => { - it('should render the PlaylistPage page', async () => { - await renderInTestApp(, { - routeEntries: ['/my-playlist'], - }); - expect(PlaylistPage).toHaveBeenCalled(); - }); - }); -}); diff --git a/plugins/playlist/src/components/Router/Router.tsx b/plugins/playlist/src/components/Router/Router.tsx deleted file mode 100644 index e153c83416..0000000000 --- a/plugins/playlist/src/components/Router/Router.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 React from 'react'; -import { Routes, Route } from 'react-router-dom'; - -import { playlistRouteRef } from '../../routes'; -import { PlaylistIndexPage } from '../PlaylistIndexPage'; -import { PlaylistPage } from '../PlaylistPage'; - -export const Router = () => ( - - } /> - } /> - -); diff --git a/plugins/playlist/src/components/Router/index.ts b/plugins/playlist/src/components/Router/index.ts deleted file mode 100644 index 6606629fee..0000000000 --- a/plugins/playlist/src/components/Router/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export * from './Router'; diff --git a/plugins/playlist/src/hooks/PlaylistListProvider.tsx b/plugins/playlist/src/hooks/PlaylistListProvider.tsx index b931e469e7..5b8e1ee2b6 100644 --- a/plugins/playlist/src/hooks/PlaylistListProvider.tsx +++ b/plugins/playlist/src/hooks/PlaylistListProvider.tsx @@ -58,6 +58,9 @@ type OutputState = { backendPlaylists: Playlist[]; }; +/** + * @public + */ export const PlaylistListProvider = < PlaylistFilters extends DefaultPlaylistFilters, >({ diff --git a/plugins/playlist/src/hooks/usePlaylistList.tsx b/plugins/playlist/src/hooks/usePlaylistList.tsx index 0c71a66e65..cf099b5be2 100644 --- a/plugins/playlist/src/hooks/usePlaylistList.tsx +++ b/plugins/playlist/src/hooks/usePlaylistList.tsx @@ -21,12 +21,18 @@ import { PlaylistOwnerFilter } from '../components/PlaylistOwnerPicker'; import { PlaylistTextFilter } from '../components/PlaylistSearchBar'; import { PlaylistFilter, PlaylistSortCompareFunction } from '../types'; +/** + * @public + */ export class NoopFilter implements PlaylistFilter { getBackendFilters() { return { '': null }; } } +/** + * @public + */ export type DefaultPlaylistFilters = { noop?: NoopFilter; owners?: PlaylistOwnerFilter; diff --git a/plugins/playlist/src/index.ts b/plugins/playlist/src/index.ts index 3b7004ce22..0281af45f7 100644 --- a/plugins/playlist/src/index.ts +++ b/plugins/playlist/src/index.ts @@ -19,10 +19,28 @@ * * @packageDocumentation */ +export * from './api'; +export { + CreatePlaylistButton, + DefaultPlaylistIndexPage, + PersonalListFilter, + PersonalListFilterValue, + PersonalListPicker, + PlaylistList, + PlaylistOwnerFilter, + PlaylistOwnerPicker, + PlaylistSearchBar, + PlaylistSortPicker, + PlaylistTextFilter, +} from './components'; export type { EntityPlaylistDialogProps } from './components'; +export { NoopFilter } from './hooks'; +export type { DefaultPlaylistFilters } from './hooks'; +export { PlaylistListProvider } from './hooks/PlaylistListProvider'; export { EntityPlaylistDialog, + PlaylistPage, playlistPlugin, PlaylistIndexPage, } from './plugin'; -export * from './api'; +export type { PlaylistFilter } from './types'; diff --git a/plugins/playlist/src/plugin.ts b/plugins/playlist/src/plugin.ts index 8e73fffe37..7a8a38c673 100644 --- a/plugins/playlist/src/plugin.ts +++ b/plugins/playlist/src/plugin.ts @@ -24,7 +24,7 @@ import { } from '@backstage/core-plugin-api'; import { playlistApiRef, PlaylistClient } from './api'; -import { rootRouteRef } from './routes'; +import { playlistRouteRef, rootRouteRef } from './routes'; /** * @public @@ -53,11 +53,24 @@ export const playlistPlugin = createPlugin({ export const PlaylistIndexPage = playlistPlugin.provide( createRoutableExtension({ name: 'PlaylistIndexPage', - component: () => import('./components/Router').then(m => m.Router), + component: () => + import('./components/PlaylistIndexPage').then(m => m.PlaylistIndexPage), mountPoint: rootRouteRef, }), ); +/** + * @public + */ +export const PlaylistPage = playlistPlugin.provide( + createRoutableExtension({ + name: 'PlaylistPage', + component: () => + import('./components/PlaylistPage').then(m => m.PlaylistPage), + mountPoint: playlistRouteRef, + }), +); + /** * @public */ diff --git a/plugins/playlist/src/routes.ts b/plugins/playlist/src/routes.ts index 632dc06439..2ca833efff 100644 --- a/plugins/playlist/src/routes.ts +++ b/plugins/playlist/src/routes.ts @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createRouteRef, createSubRouteRef } from '@backstage/core-plugin-api'; +import { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ id: 'playlist:index-page', }); -export const playlistRouteRef = createSubRouteRef({ +export const playlistRouteRef = createRouteRef({ id: 'playlist:playlist-page', - parent: rootRouteRef, - path: '/:playlistId', + params: ['playlistId'], }); diff --git a/plugins/playlist/src/types.ts b/plugins/playlist/src/types.ts index fbd348f98a..b31b282031 100644 --- a/plugins/playlist/src/types.ts +++ b/plugins/playlist/src/types.ts @@ -16,6 +16,9 @@ import { Playlist } from '@backstage/plugin-playlist-common'; +/** + * @public + */ export type PlaylistFilter = { getBackendFilters?: () => Record;