diff --git a/.changeset/techdocs-metal-clouds-work.md b/.changeset/techdocs-metal-clouds-work.md new file mode 100644 index 0000000000..64d18dcc02 --- /dev/null +++ b/.changeset/techdocs-metal-clouds-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Adding support for user owned document filter for TechDocs custom Homepage diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index 9ee32fef13..1b9085349f 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -21,12 +21,32 @@ import { screen } from '@testing-library/react'; import React from 'react'; import { TechDocsCustomHome, PanelType } from './TechDocsCustomHome'; -describe('TechDocsCustomHome', () => { - const catalogApi: Partial = { - getEntities: async () => ({ items: [] }), +jest.mock('@backstage/plugin-catalog-react', () => { + const actual = jest.requireActual('@backstage/plugin-catalog-react'); + return { + ...actual, + useOwnUser: () => 'test-user', }; +}); - const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi); +const mockCatalogApi = { + getEntityByName: jest.fn(), + getEntities: async () => ({ + items: [ + { + apiVersion: 'version', + kind: 'User', + metadata: { + name: 'owned', + namespace: 'default', + }, + }, + ], + }), +} as Partial; + +describe('TechDocsCustomHome', () => { + const apiRegistry = ApiRegistry.with(catalogApiRef, mockCatalogApi); it('should render a TechDocs home page', async () => { const tabsConfig = [ diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index ff9f1bf101..afe209f70c 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -18,7 +18,12 @@ import React, { useState } from 'react'; import { useAsync } from 'react-use'; import { makeStyles } from '@material-ui/core'; import { CSSProperties } from '@material-ui/styles'; -import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + CatalogApi, + isOwnerOf, + useOwnUser, +} from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { CodeSnippet, @@ -49,7 +54,7 @@ export interface PanelConfig { description: string; panelType: PanelType; panelCSS?: CSSProperties; - filterPredicate: (entity: Entity) => boolean; + filterPredicate: ((entity: Entity) => boolean) | string; } export interface TabConfig { @@ -75,8 +80,24 @@ const CustomPanel = ({ }, }); const classes = useStyles(); + const { value: user } = useOwnUser(); + const Panel = panels[config.panelType]; - const shownEntities = entities.filter(config.filterPredicate); + + const shownEntities = entities.filter(entity => { + if (config.filterPredicate === 'ownedByUser') { + if (!user) { + return false; + } + return isOwnerOf(user, entity); + } + + return ( + typeof config.filterPredicate === 'function' && + config.filterPredicate(entity) + ); + }); + return ( <> diff --git a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsHome.test.tsx index 566ed3bd0b..8135c4d894 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.test.tsx @@ -27,45 +27,41 @@ import { screen } from '@testing-library/react'; import React from 'react'; import { TechDocsHome } from './TechDocsHome'; -jest.mock('../hooks', () => ({ - useOwnUser: () => { - return { - value: { +jest.mock('@backstage/plugin-catalog-react', () => { + const actual = jest.requireActual('@backstage/plugin-catalog-react'); + return { + ...actual, + useOwnUser: () => 'test-user', + }; +}); + +const mockCatalogApi = { + getEntityByName: jest.fn(), + getEntities: async () => ({ + items: [ + { apiVersion: 'version', kind: 'User', metadata: { name: 'owned', namespace: 'default', }, - relations: [ - { - target: { - kind: 'TestKind', - name: 'testName', - }, - type: 'ownerOf', - }, - ], }, - }; - }, -})); + ], + }), +} as Partial; describe('TechDocs Home', () => { - const catalogApi: Partial = { - getEntities: async () => ({ items: [] }), - }; - const configApi: ConfigApi = new ConfigReader({ organization: { name: 'My Company', }, }); - const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi).with( - configApiRef, - configApi, - ); + const apiRegistry = ApiRegistry.from([ + [catalogApiRef, mockCatalogApi], + [configApiRef, configApi], + ]); it('should render a TechDocs home page', async () => { await renderInTestApp( diff --git a/plugins/techdocs/src/home/components/TechDocsHome.tsx b/plugins/techdocs/src/home/components/TechDocsHome.tsx index 72f59d5446..3ebe394cf4 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.tsx @@ -15,14 +15,9 @@ */ import React from 'react'; -import { Entity } from '@backstage/catalog-model'; -import { useOwnUser } from '../hooks'; -import { isOwnerOf } from '@backstage/plugin-catalog-react'; import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome'; export const TechDocsHome = () => { - const { value: user } = useOwnUser(); - const tabsConfig = [ { label: 'Overview', @@ -34,6 +29,13 @@ export const TechDocsHome = () => { panelType: 'DocsCardGrid' as PanelType, filterPredicate: () => true, }, + // uncomment this if you would like to have a secondary panel with owned documents + // { + // title: 'Owned', + // description: 'Explore your owned internal documentation.', + // panelType: 'DocsCardGrid' as PanelType, + // filterPredicate: 'ownedByUser', + // }, ], }, { @@ -43,12 +45,8 @@ export const TechDocsHome = () => { title: 'Owned documents', description: 'Access your documentation.', panelType: 'DocsTable' as PanelType, - filterPredicate: (entity: Entity) => { - if (!user) { - return false; - } - return isOwnerOf(user, entity); - }, + // ownedByUser filters out entities owned by signed in user + filterPredicate: 'ownedByUser', }, ], }, diff --git a/plugins/techdocs/src/home/hooks/index.ts b/plugins/techdocs/src/home/hooks/index.ts deleted file mode 100644 index 96419f5e7b..0000000000 --- a/plugins/techdocs/src/home/hooks/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { useOwnUser } from './useOwnUser'; diff --git a/plugins/techdocs/src/home/hooks/useOwnUser.ts b/plugins/techdocs/src/home/hooks/useOwnUser.ts deleted file mode 100644 index 29d8a0d11f..0000000000 --- a/plugins/techdocs/src/home/hooks/useOwnUser.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020 Spotify AB - * - * 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 { UserEntity } from '@backstage/catalog-model'; -import { identityApiRef, useApi } from '@backstage/core'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { useAsync } from 'react-use'; -import { AsyncState } from 'react-use/lib/useAsync'; - -/** - * Get the catalog User entity (if any) that matches the logged-in user. - */ -export function useOwnUser(): AsyncState { - const catalogApi = useApi(catalogApiRef); - const identityApi = useApi(identityApiRef); - - // TODO: get the full entity (or at least the full entity name) from the - // identityApi - return useAsync( - () => - catalogApi.getEntityByName({ - kind: 'User', - namespace: 'default', - name: identityApi.getUserId(), - }) as Promise, - [catalogApi, identityApi], - ); -}