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;