From a440d3b3893679c1c202e4893484b842427d9bbd Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Tue, 13 Jul 2021 17:16:51 -0400 Subject: [PATCH 1/3] refactor(techdocs): rework TechDocsHome to use EntityListProvider Signed-off-by: Phil Kuang --- .changeset/clean-jokes-think.md | 5 ++ .changeset/spicy-crews-applaud.md | 6 ++ plugins/catalog-react/api-report.md | 28 ++++++- .../FavoriteEntity/FavoriteEntity.tsx} | 14 ++-- .../src/components/FavoriteEntity/index.ts | 21 +++++ plugins/catalog-react/src/components/index.ts | 1 + plugins/catalog-react/src/types.ts | 5 +- plugins/catalog-react/src/utils/filters.ts | 4 +- .../components/CatalogTable/CatalogTable.tsx | 10 +-- .../components/EntityLayout/EntityLayout.tsx | 4 +- .../EntityPageLayout/EntityPageLayout.tsx | 4 +- plugins/techdocs/api-report.md | 38 ++++++++- plugins/techdocs/package.json | 3 + .../src/home/components/DocsTable.tsx | 70 ++++++++--------- .../home/components/EntityListDocsTable.tsx | 70 +++++++++++++++++ .../home/components/TechDocsCustomHome.tsx | 25 ++---- .../src/home/components/TechDocsHome.test.tsx | 14 ++-- .../src/home/components/TechDocsHome.tsx | 78 ++++++++++--------- .../home/components/TechDocsHomeLayout.tsx | 41 ++++++++++ .../src/home/components/TechDocsPicker.tsx | 47 +++++++++++ .../techdocs/src/home/components/actions.tsx | 32 ++++++++ plugins/techdocs/src/home/components/index.ts | 20 +++++ plugins/techdocs/src/home/components/types.ts | 24 ++++++ plugins/techdocs/src/index.ts | 7 +- 24 files changed, 451 insertions(+), 120 deletions(-) create mode 100644 .changeset/clean-jokes-think.md create mode 100644 .changeset/spicy-crews-applaud.md rename plugins/{catalog/src/components/FavouriteEntity/FavouriteEntity.tsx => catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx} (80%) create mode 100644 plugins/catalog-react/src/components/FavoriteEntity/index.ts create mode 100644 plugins/techdocs/src/home/components/EntityListDocsTable.tsx create mode 100644 plugins/techdocs/src/home/components/TechDocsHomeLayout.tsx create mode 100644 plugins/techdocs/src/home/components/TechDocsPicker.tsx create mode 100644 plugins/techdocs/src/home/components/actions.tsx create mode 100644 plugins/techdocs/src/home/components/index.ts create mode 100644 plugins/techdocs/src/home/components/types.ts diff --git a/.changeset/clean-jokes-think.md b/.changeset/clean-jokes-think.md new file mode 100644 index 0000000000..4a2c6a2c13 --- /dev/null +++ b/.changeset/clean-jokes-think.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Rework `TechDocsHome` to use `EntityListProvider` with support for starring docs and filtering on owned, starred, owner, and tags. diff --git a/.changeset/spicy-crews-applaud.md b/.changeset/spicy-crews-applaud.md new file mode 100644 index 0000000000..d2707516d2 --- /dev/null +++ b/.changeset/spicy-crews-applaud.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-react': patch +--- + +Move and rename `FavoriteEntity` component to `catalog-react` diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 8471a80fe5..57bb5360c2 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -10,9 +10,11 @@ import { AsyncState } from 'react-use/lib/useAsync'; import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { CatalogApi } from '@backstage/catalog-client'; import { ComponentEntity } from '@backstage/catalog-model'; +import { ComponentProps } from 'react'; import { Context } from 'react'; import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; +import { IconButton } from '@material-ui/core'; import { LinkProps } from '@backstage/core-components'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; @@ -116,7 +118,10 @@ export const EntityContext: Context; // // @public (undocumented) export type EntityFilter = { - getCatalogFilters?: () => Record; + getCatalogFilters?: () => Record< + string, + string | symbol | (string | symbol)[] + >; filterEntity?: (entity: Entity) => boolean; toQueryValue?: () => string | string[]; }; @@ -618,6 +623,25 @@ export class EntityTypeFilter implements EntityFilter { // @public (undocumented) export const EntityTypePicker: () => JSX.Element | null; +// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "FavoriteEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const FavoriteEntity: (props: Props_2) => JSX.Element; + +// Warning: (ae-missing-release-tag) "favoriteEntityIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const favoriteEntityIcon: (isStarred: boolean) => JSX.Element; + +// Warning: (ae-missing-release-tag) "favoriteEntityTooltip" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const favoriteEntityTooltip: ( + isStarred: boolean, +) => 'Remove from favorites' | 'Add to favorites'; + // Warning: (ae-missing-release-tag) "formatEntityRefTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -677,7 +701,7 @@ export const MockEntityListContextProvider: ({ // @public (undocumented) export function reduceCatalogFilters( filters: EntityFilter[], -): Record; +): Record; // Warning: (ae-missing-release-tag) "reduceEntityFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/catalog/src/components/FavouriteEntity/FavouriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx similarity index 80% rename from plugins/catalog/src/components/FavouriteEntity/FavouriteEntity.tsx rename to plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index 108697541b..95e617ab7f 100644 --- a/plugins/catalog/src/components/FavouriteEntity/FavouriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -15,7 +15,7 @@ */ import React, { ComponentProps } from 'react'; -import { useStarredEntities } from '@backstage/plugin-catalog-react'; +import { useStarredEntities } from '../../hooks/useStarredEntities'; import { IconButton, Tooltip, withStyles } from '@material-ui/core'; import StarBorder from '@material-ui/icons/StarBorder'; import Star from '@material-ui/icons/Star'; @@ -29,17 +29,17 @@ const YellowStar = withStyles({ }, })(Star); -export const favouriteEntityTooltip = (isStarred: boolean) => +export const favoriteEntityTooltip = (isStarred: boolean) => isStarred ? 'Remove from favorites' : 'Add to favorites'; -export const favouriteEntityIcon = (isStarred: boolean) => +export const favoriteEntityIcon = (isStarred: boolean) => isStarred ? : ; /** - * IconButton for showing if a current entity is starred and adding/removing it from the favourite entities + * IconButton for showing if a current entity is starred and adding/removing it from the favorite entities * @param props MaterialUI IconButton props extended by required `entity` prop */ -export const FavouriteEntity = (props: Props) => { +export const FavoriteEntity = (props: Props) => { const { toggleStarredEntity, isStarredEntity } = useStarredEntities(); const isStarred = isStarredEntity(props.entity); return ( @@ -48,8 +48,8 @@ export const FavouriteEntity = (props: Props) => { {...props} onClick={() => toggleStarredEntity(props.entity)} > - - {favouriteEntityIcon(isStarred)} + + {favoriteEntityIcon(isStarred)} ); diff --git a/plugins/catalog-react/src/components/FavoriteEntity/index.ts b/plugins/catalog-react/src/components/FavoriteEntity/index.ts new file mode 100644 index 0000000000..f731ca7483 --- /dev/null +++ b/plugins/catalog-react/src/components/FavoriteEntity/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2021 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 { + favoriteEntityTooltip, + favoriteEntityIcon, + FavoriteEntity, +} from './FavoriteEntity'; diff --git a/plugins/catalog-react/src/components/index.ts b/plugins/catalog-react/src/components/index.ts index dd7c4bebc2..2664af8ef9 100644 --- a/plugins/catalog-react/src/components/index.ts +++ b/plugins/catalog-react/src/components/index.ts @@ -22,4 +22,5 @@ export * from './EntitySearchBar'; export * from './EntityTable'; export * from './EntityTagPicker'; export * from './EntityTypePicker'; +export * from './FavoriteEntity'; export * from './UserListPicker'; diff --git a/plugins/catalog-react/src/types.ts b/plugins/catalog-react/src/types.ts index 4ac7644ee1..c5612fdf3f 100644 --- a/plugins/catalog-react/src/types.ts +++ b/plugins/catalog-react/src/types.ts @@ -23,7 +23,10 @@ export type EntityFilter = { * { field: 'kind', values: ['component'] } * { field: 'metadata.name', values: ['component-1', 'component-2'] } */ - getCatalogFilters?: () => Record; + getCatalogFilters?: () => Record< + string, + string | symbol | (string | symbol)[] + >; /** * Filter entities on the frontend after a catalog-backend request. This function will be called diff --git a/plugins/catalog-react/src/utils/filters.ts b/plugins/catalog-react/src/utils/filters.ts index b3683beea0..109e864c52 100644 --- a/plugins/catalog-react/src/utils/filters.ts +++ b/plugins/catalog-react/src/utils/filters.ts @@ -19,13 +19,13 @@ import { EntityFilter } from '../types'; export function reduceCatalogFilters( filters: EntityFilter[], -): Record { +): Record { return filters.reduce((compoundFilter, filter) => { return { ...compoundFilter, ...(filter.getCatalogFilters ? filter.getCatalogFilters() : {}), }; - }, {} as Record); + }, {} as Record); } export function reduceEntityFilters( diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 86e95b48e0..84c2afa0f6 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -15,6 +15,8 @@ */ import { RELATION_OWNED_BY, RELATION_PART_OF } from '@backstage/catalog-model'; import { + favoriteEntityIcon, + favoriteEntityTooltip, formatEntityRefTitle, getEntityMetadataEditUrl, getEntityMetadataViewUrl, @@ -26,10 +28,6 @@ import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; import { capitalize } from 'lodash'; import React from 'react'; -import { - favouriteEntityIcon, - favouriteEntityTooltip, -} from '../FavouriteEntity/FavouriteEntity'; import * as columnFactories from './columns'; import { EntityRow } from './types'; import { @@ -105,8 +103,8 @@ export const CatalogTable = ({ columns, actions }: CatalogTableProps) => { const isStarred = isStarredEntity(entity); return { cellStyle: { paddingLeft: '1em' }, - icon: () => favouriteEntityIcon(isStarred), - tooltip: favouriteEntityTooltip(isStarred), + icon: () => favoriteEntityIcon(isStarred), + tooltip: favoriteEntityTooltip(isStarred), onClick: () => toggleStarredEntity(entity), }; }, diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index f431027097..a139fbaa1e 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -35,6 +35,7 @@ import { import { EntityContext, EntityRefLinks, + FavoriteEntity, getEntityRelations, useEntityCompoundName, } from '@backstage/plugin-catalog-react'; @@ -43,7 +44,6 @@ import { Alert } from '@material-ui/lab'; import React, { useContext, useState } from 'react'; import { useNavigate } from 'react-router'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; -import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity'; import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog'; type SubRoute = { @@ -79,7 +79,7 @@ const EntityLayoutTitle = ({ > {title} - {entity && } + {entity && } ); }; diff --git a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx index 4abac4e01b..088a9f2c53 100644 --- a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx @@ -21,6 +21,7 @@ import { import { EntityContext, EntityRefLinks, + FavoriteEntity, getEntityRelations, useEntityCompoundName, } from '@backstage/plugin-catalog-react'; @@ -28,7 +29,6 @@ import { Box } from '@material-ui/core'; import React, { useContext, useState } from 'react'; import { useNavigate } from 'react-router'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; -import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity'; import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog'; import { Tabbed } from './Tabbed'; @@ -54,7 +54,7 @@ const EntityPageTitle = ({ }) => ( {title} - {entity && } + {entity && } ); diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 8364263f42..95d78475fb 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -5,6 +5,7 @@ ```ts /// +import { Action } from '@material-table/core'; import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Config } from '@backstage/config'; @@ -14,6 +15,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { IdentityApi } from '@backstage/core-plugin-api'; import { LocationSpec } from '@backstage/catalog-model'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; // Warning: (ae-missing-release-tag) "DocsCardGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -42,16 +44,34 @@ export const DocsResultListItem: ({ export const DocsTable: ({ entities, title, + loading, + actions, }: { entities: Entity[] | undefined; title?: string | undefined; + loading?: boolean | undefined; + actions?: + | ( + | Action + | { + action: (rowData: DocsTableRow) => Action; + position: string; + } + | ((rowData: DocsTableRow) => Action) + )[] + | undefined; }) => JSX.Element | null; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "EmbeddedDocsRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EmbeddedDocsRouter: (_props: Props) => JSX.Element; +export const EmbeddedDocsRouter: (_props: Props_2) => JSX.Element; + +// Warning: (ae-missing-release-tag) "EntityListDocsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityListDocsTable: () => JSX.Element; // Warning: (ae-missing-release-tag) "EntityTechdocsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -69,7 +89,7 @@ export type PanelType = 'DocsCardGrid' | 'DocsTable'; // Warning: (ae-missing-release-tag) "Reader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Reader: ({ entityId, onReady }: Props_2) => JSX.Element; +export const Reader: ({ entityId, onReady }: Props_3) => JSX.Element; // Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -143,11 +163,22 @@ export const TechDocsCustomHome: ({ tabsConfig: TabsConfig; }) => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "TechDocsHomeLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const TechDocsHomeLayout: ({ children }: Props) => JSX.Element; + // Warning: (ae-missing-release-tag) "TechdocsPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const TechdocsPage: () => JSX.Element; +// Warning: (ae-missing-release-tag) "TechDocsPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const TechDocsPicker: () => null; + // Warning: (ae-missing-release-tag) "techdocsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -255,7 +286,8 @@ export class TechDocsStorageClient implements TechDocsStorageApi { // Warnings were encountered during analysis: // -// src/plugin.d.ts:18:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts +// src/plugin.d.ts:17:5 - (ae-forgotten-export) The symbol "DocsTableRow" needs to be exported by the entry point index.d.ts +// src/plugin.d.ts:23:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 7051109c2e..9e15dd7aff 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -38,14 +38,17 @@ "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.9", "@backstage/integration-react": "^0.1.6", + "@backstage/plugin-catalog": "^0.6.10", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@material-ui/styles": "^4.10.0", + "@types/react": "^16.9", "dompurify": "^2.2.9", "event-source-polyfill": "^1.0.25", + "lodash": "^4.17.21", "react": "^16.13.1", "react-dom": "^16.13.1", "react-lazylog": "^4.5.2", diff --git a/plugins/techdocs/src/home/components/DocsTable.tsx b/plugins/techdocs/src/home/components/DocsTable.tsx index 031f09868f..68e6405265 100644 --- a/plugins/techdocs/src/home/components/DocsTable.tsx +++ b/plugins/techdocs/src/home/components/DocsTable.tsx @@ -18,24 +18,29 @@ import React from 'react'; import { useCopyToClipboard } from 'react-use'; import { generatePath } from 'react-router-dom'; -import { IconButton, Tooltip } from '@material-ui/core'; -import ShareIcon from '@material-ui/icons/Share'; import { Entity } from '@backstage/catalog-model'; import { rootDocsRouteRef } from '../../routes'; import { - Table, - EmptyState, Button, - SubvalueCell, + EmptyState, Link, + SubvalueCell, + Table, + TableProps, } from '@backstage/core-components'; +import * as actionFactories from './actions'; +import { DocsTableRow } from './types'; export const DocsTable = ({ entities, title, + loading, + actions, }: { entities: Entity[] | undefined; title?: string | undefined; + loading?: boolean | undefined; + actions?: TableProps['actions']; }) => { const [, copyToClipboard] = useCopyToClipboard(); @@ -43,15 +48,14 @@ export const DocsTable = ({ const documents = entities.map(entity => { return { - name: entity.metadata.name, - description: entity.metadata.description, - owner: entity?.spec?.owner, - type: entity?.spec?.type, - docsUrl: generatePath(rootDocsRouteRef.path, { - namespace: entity.metadata.namespace ?? 'default', - kind: entity.kind, - name: entity.metadata.name, - }), + entity, + resolved: { + docsUrl: generatePath(rootDocsRouteRef.path, { + namespace: entity.metadata.namespace ?? 'default', + kind: entity.kind, + name: entity.metadata.name, + }), + }, }; }); @@ -60,49 +64,43 @@ export const DocsTable = ({ title: 'Document', field: 'name', highlight: true, - render: (row: any): React.ReactNode => ( + render: (row: DocsTableRow): React.ReactNode => ( {row.name}} - subvalue={row.description} + value={ + {row.entity.metadata.name} + } + subvalue={row.entity.metadata.description} /> ), }, { title: 'Owner', - field: 'owner', + field: 'entity.spec.owner', }, { title: 'Type', - field: 'type', - }, - { - title: 'Actions', - width: '10%', - render: (row: any) => ( - - - copyToClipboard(`${window.location.href}/${row.docsUrl}`) - } - > - - - - ), + field: 'entity.spec.type', }, ]; + const defaultActions: TableProps['actions'] = [ + actionFactories.createCopyDocsUrlAction(copyToClipboard), + ]; + return ( <> - {documents && documents.length > 0 ? ( - 0) ? ( + + isLoading={loading} options={{ paging: true, pageSize: 20, search: true, + actionsColumnIndex: -1, }} data={documents} columns={columns} + actions={actions || defaultActions} title={ title ? `${title} (${documents.length})` diff --git a/plugins/techdocs/src/home/components/EntityListDocsTable.tsx b/plugins/techdocs/src/home/components/EntityListDocsTable.tsx new file mode 100644 index 0000000000..11e49fa2d4 --- /dev/null +++ b/plugins/techdocs/src/home/components/EntityListDocsTable.tsx @@ -0,0 +1,70 @@ +/* + * Copyright 2021 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 { useCopyToClipboard } from 'react-use'; +import { capitalize } from 'lodash'; +import { CodeSnippet, WarningPanel } from '@backstage/core-components'; +import { + favoriteEntityIcon, + favoriteEntityTooltip, + useEntityListProvider, + useStarredEntities, +} from '@backstage/plugin-catalog-react'; +import * as actionFactories from './actions'; +import { DocsTable } from './DocsTable'; +import { DocsTableRow } from './types'; + +export const EntityListDocsTable = () => { + const { loading, error, entities, filters } = useEntityListProvider(); + const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); + const [, copyToClipboard] = useCopyToClipboard(); + + const title = capitalize(filters.user?.value ?? 'all'); + + const actions = [ + actionFactories.createCopyDocsUrlAction(copyToClipboard), + ({ entity }: DocsTableRow) => { + const isStarred = isStarredEntity(entity); + return { + cellStyle: { paddingLeft: '1em' }, + icon: () => favoriteEntityIcon(isStarred), + tooltip: favoriteEntityTooltip(isStarred), + onClick: () => toggleStarredEntity(entity), + }; + }, + ]; + + if (error) { + return ( + + + + ); + } + + return ( + + ); +}; diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index e2623fe81f..7f93f6a306 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -28,20 +28,19 @@ import { import { Entity } from '@backstage/catalog-model'; import { DocsTable } from './DocsTable'; import { DocsCardGrid } from './DocsCardGrid'; +import { TechDocsHomeLayout } from './TechDocsHomeLayout'; import { CodeSnippet, Content, - Header, HeaderTabs, - Page, Progress, WarningPanel, SupportButton, ContentHeader, } from '@backstage/core-components'; -import { ConfigApi, configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useApi } from '@backstage/core-plugin-api'; const panels = { DocsTable: DocsTable, @@ -122,7 +121,6 @@ export const TechDocsCustomHome = ({ }) => { const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); - const configApi: ConfigApi = useApi(configApiRef); const { value: entities, @@ -147,27 +145,21 @@ export const TechDocsCustomHome = ({ }); }); - const generatedSubtitle = `Documentation available in ${ - configApi.getOptionalString('organization.name') ?? 'Backstage' - }`; - const currentTabConfig = tabsConfig[selectedTab]; if (loading) { return ( - -
+ - + ); } if (error) { return ( - -
+ - + ); } return ( - -
+ setSelectedTab(index)} @@ -201,6 +192,6 @@ export const TechDocsCustomHome = ({ /> ))} - + ); }; diff --git a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsHome.test.tsx index f90b35e181..9289884f1c 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.test.tsx @@ -15,7 +15,7 @@ */ import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; -import { renderInTestApp } from '@backstage/test-utils'; +import { MockStorageApi, renderInTestApp } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; import React from 'react'; import { TechDocsHome } from './TechDocsHome'; @@ -25,7 +25,11 @@ import { ApiRegistry, ConfigReader, } from '@backstage/core-app-api'; -import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; +import { + ConfigApi, + configApiRef, + storageApiRef, +} from '@backstage/core-plugin-api'; jest.mock('@backstage/plugin-catalog-react', () => { const actual = jest.requireActual('@backstage/plugin-catalog-react'); @@ -36,7 +40,7 @@ jest.mock('@backstage/plugin-catalog-react', () => { }); const mockCatalogApi = { - getEntityByName: jest.fn(), + getEntityByName: () => Promise.resolve(), getEntities: async () => ({ items: [ { @@ -61,6 +65,7 @@ describe('TechDocs Home', () => { const apiRegistry = ApiRegistry.from([ [catalogApiRef, mockCatalogApi], [configApiRef, configApi], + [storageApiRef, MockStorageApi.create()], ]); it('should render a TechDocs home page', async () => { @@ -75,8 +80,5 @@ describe('TechDocs Home', () => { expect( await screen.findByText(/Documentation available in My Company/i), ).toBeInTheDocument(); - - // Explore Content - expect(await screen.findByTestId('docs-explore')).toBeDefined(); }); }); diff --git a/plugins/techdocs/src/home/components/TechDocsHome.tsx b/plugins/techdocs/src/home/components/TechDocsHome.tsx index 6445713875..ef69bcdc44 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.tsx @@ -15,41 +15,49 @@ */ import React from 'react'; -import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome'; +import { + Content, + ContentHeader, + SupportButton, +} from '@backstage/core-components'; +import { + EntityListContainer, + FilterContainer, + FilteredEntityLayout, +} from '@backstage/plugin-catalog'; +import { + EntityListProvider, + EntityOwnerPicker, + EntityTagPicker, + UserListPicker, +} from '@backstage/plugin-catalog-react'; +import { EntityListDocsTable } from './EntityListDocsTable'; +import { TechDocsHomeLayout } from './TechDocsHomeLayout'; +import { TechDocsPicker } from './TechDocsPicker'; export const TechDocsHome = () => { - const tabsConfig = [ - { - label: 'Overview', - panels: [ - { - title: 'Overview', - description: - 'Explore your internal technical ecosystem through documentation.', - 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', - // }, - ], - }, - { - label: 'Owned Documents', - panels: [ - { - title: 'Owned documents', - description: 'Access your documentation.', - panelType: 'DocsTable' as PanelType, - // ownedByUser filters out entities owned by signed in user - filterPredicate: 'ownedByUser', - }, - ], - }, - ]; - return ; + return ( + + + + + Discover documentation in your ecosystem. + + + + + + + + + + + + + + + + + + ); }; diff --git a/plugins/techdocs/src/home/components/TechDocsHomeLayout.tsx b/plugins/techdocs/src/home/components/TechDocsHomeLayout.tsx new file mode 100644 index 0000000000..e6717fbecc --- /dev/null +++ b/plugins/techdocs/src/home/components/TechDocsHomeLayout.tsx @@ -0,0 +1,41 @@ +/* + * Copyright 2021 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 } from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; + +type Props = { + children?: React.ReactNode; +}; + +export const TechDocsHomeLayout = ({ children }: Props) => { + const configApi = useApi(configApiRef); + const generatedSubtitle = `Documentation available in ${ + configApi.getOptionalString('organization.name') ?? 'Backstage' + }`; + + return ( + + {children} + + ); +}; diff --git a/plugins/techdocs/src/home/components/TechDocsPicker.tsx b/plugins/techdocs/src/home/components/TechDocsPicker.tsx new file mode 100644 index 0000000000..9ed21da368 --- /dev/null +++ b/plugins/techdocs/src/home/components/TechDocsPicker.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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 { useEffect } from 'react'; +import { + CATALOG_FILTER_EXISTS, + DefaultEntityFilters, + EntityFilter, + useEntityListProvider, +} from '@backstage/plugin-catalog-react'; + +class TechDocsFilter implements EntityFilter { + getCatalogFilters(): Record { + return { + 'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS, + }; + } +} + +type CustomFilters = DefaultEntityFilters & { + techdocs?: TechDocsFilter; +}; + +export const TechDocsPicker = () => { + const { updateFilters } = useEntityListProvider(); + + useEffect(() => { + updateFilters({ + techdocs: new TechDocsFilter(), + }); + }, [updateFilters]); + + return null; +}; diff --git a/plugins/techdocs/src/home/components/actions.tsx b/plugins/techdocs/src/home/components/actions.tsx new file mode 100644 index 0000000000..e26415b049 --- /dev/null +++ b/plugins/techdocs/src/home/components/actions.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2021 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 ShareIcon from '@material-ui/icons/Share'; +import { DocsTableRow } from './types'; + +export function createCopyDocsUrlAction(copyToClipboard: Function) { + return (row: DocsTableRow) => { + return { + icon: () => , + tooltip: 'Click to copy documentation link to clipboard', + onClick: () => + copyToClipboard( + `${window.location.href.split('/?')[0]}/${row.resolved.docsUrl}`, + ), + }; + }; +} diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts new file mode 100644 index 0000000000..392aa2e82f --- /dev/null +++ b/plugins/techdocs/src/home/components/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 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 { EntityListDocsTable } from './EntityListDocsTable'; +export { TechDocsHomeLayout } from './TechDocsHomeLayout'; +export { TechDocsPicker } from './TechDocsPicker'; +export type { PanelType } from './TechDocsCustomHome'; diff --git a/plugins/techdocs/src/home/components/types.ts b/plugins/techdocs/src/home/components/types.ts new file mode 100644 index 0000000000..324cb59f96 --- /dev/null +++ b/plugins/techdocs/src/home/components/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2021 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 { Entity } from '@backstage/catalog-model'; + +export type DocsTableRow = { + entity: Entity; + resolved: { + docsUrl: string; + }; +}; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 66502a4585..9a93e2371c 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -18,7 +18,12 @@ export * from './api'; export { techdocsApiRef, techdocsStorageApiRef } from './api'; export type { TechDocsApi, TechDocsStorageApi } from './api'; export { TechDocsClient, TechDocsStorageClient } from './client'; -export type { PanelType } from './home/components/TechDocsCustomHome'; +export type { PanelType } from './home/components'; +export { + EntityListDocsTable, + TechDocsHomeLayout, + TechDocsPicker, +} from './home/components'; export * from './components/DocsResultListItem'; export { DocsCardGrid, From 10ef115554e5b2c29a4fe357cb9325cfd0295761 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 4 Aug 2021 10:01:00 -0400 Subject: [PATCH 2/3] refactor(EntityListDocsTable): support custom columns and actions Signed-off-by: Phil Kuang --- plugins/techdocs/api-report.md | 101 +++++++++++++++++- .../src/home/components/DocsTable.tsx | 46 ++++---- .../home/components/EntityListDocsTable.tsx | 41 ++++--- .../src/home/components/TechDocsHome.tsx | 18 +++- .../techdocs/src/home/components/actions.tsx | 24 ++++- .../techdocs/src/home/components/columns.tsx | 56 ++++++++++ plugins/techdocs/src/home/components/index.ts | 1 + plugins/techdocs/src/home/components/types.ts | 4 +- plugins/techdocs/src/index.ts | 3 +- plugins/techdocs/src/plugin.ts | 8 ++ 10 files changed, 251 insertions(+), 51 deletions(-) create mode 100644 plugins/techdocs/src/home/components/columns.tsx diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 95d78475fb..edae3d9585 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -17,6 +17,54 @@ import { IdentityApi } from '@backstage/core-plugin-api'; import { LocationSpec } from '@backstage/catalog-model'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; +import { TableColumn } from '@backstage/core-components'; +import { TableProps } from '@backstage/core-components'; +import { UserListFilterKind } from '@backstage/plugin-catalog-react'; + +// Warning: (ae-missing-release-tag) "createCopyDocsUrlAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createCopyDocsUrlAction( + copyToClipboard: Function, +): ( + row: DocsTableRow, +) => { + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; +}; + +// Warning: (ae-missing-release-tag) "createNameColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createNameColumn(): TableColumn; + +// Warning: (ae-missing-release-tag) "createOwnerColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createOwnerColumn(): TableColumn; + +// Warning: (ae-missing-release-tag) "createStarEntityAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createStarEntityAction( + isStarredEntity: Function, + toggleStarredEntity: Function, +): ({ + entity, +}: DocsTableRow) => { + cellStyle: { + paddingLeft: string; + }; + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; +}; + +// Warning: (ae-missing-release-tag) "createTypeColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +function createTypeColumn(): TableColumn; // Warning: (ae-missing-release-tag) "DocsCardGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -45,11 +93,13 @@ export const DocsTable: ({ entities, title, loading, + columns, actions, }: { entities: Entity[] | undefined; title?: string | undefined; loading?: boolean | undefined; + columns?: TableColumn[] | undefined; actions?: | ( | Action @@ -62,6 +112,18 @@ export const DocsTable: ({ | undefined; }) => JSX.Element | null; +// Warning: (ae-missing-release-tag) "DocsTableRow" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type DocsTableRow = { + entity: Entity; + resolved: { + docsUrl: string; + ownedByRelationsTitle: string; + ownedByRelations: EntityName[]; + }; +}; + // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "EmbeddedDocsRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -71,7 +133,17 @@ export const EmbeddedDocsRouter: (_props: Props_2) => JSX.Element; // Warning: (ae-missing-release-tag) "EntityListDocsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityListDocsTable: () => JSX.Element; +export const EntityListDocsTable: { + ({ + columns, + actions, + }: { + columns?: TableColumn[] | undefined; + actions?: TableProps['actions']; + }): JSX.Element; + columns: typeof columnFactories; + actions: typeof actionFactories; +}; // Warning: (ae-missing-release-tag) "EntityTechdocsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -163,6 +235,28 @@ export const TechDocsCustomHome: ({ tabsConfig: TabsConfig; }) => JSX.Element; +// Warning: (ae-missing-release-tag) "TechDocsHome" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const TechDocsHome: ({ + initialFilter, + columns, + actions, +}: { + initialFilter?: UserListFilterKind | undefined; + columns?: TableColumn[] | undefined; + actions?: + | ( + | Action + | { + action: (rowData: DocsTableRow) => Action; + position: string; + } + | ((rowData: DocsTableRow) => Action) + )[] + | undefined; +}) => JSX.Element; + // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "TechDocsHomeLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -286,8 +380,9 @@ export class TechDocsStorageClient implements TechDocsStorageApi { // Warnings were encountered during analysis: // -// src/plugin.d.ts:17:5 - (ae-forgotten-export) The symbol "DocsTableRow" needs to be exported by the entry point index.d.ts -// src/plugin.d.ts:23:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts +// src/home/components/EntityListDocsTable.d.ts:11:5 - (ae-forgotten-export) The symbol "columnFactories" needs to be exported by the entry point index.d.ts +// src/home/components/EntityListDocsTable.d.ts:12:5 - (ae-forgotten-export) The symbol "actionFactories" needs to be exported by the entry point index.d.ts +// src/plugin.d.ts:24:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/techdocs/src/home/components/DocsTable.tsx b/plugins/techdocs/src/home/components/DocsTable.tsx index 68e6405265..29b63f5441 100644 --- a/plugins/techdocs/src/home/components/DocsTable.tsx +++ b/plugins/techdocs/src/home/components/DocsTable.tsx @@ -18,28 +18,34 @@ import React from 'react'; import { useCopyToClipboard } from 'react-use'; import { generatePath } from 'react-router-dom'; -import { Entity } from '@backstage/catalog-model'; +import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { + formatEntityRefTitle, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; import { rootDocsRouteRef } from '../../routes'; import { Button, EmptyState, - Link, - SubvalueCell, Table, + TableColumn, TableProps, } from '@backstage/core-components'; import * as actionFactories from './actions'; +import * as columnFactories from './columns'; import { DocsTableRow } from './types'; export const DocsTable = ({ entities, title, loading, + columns, actions, }: { entities: Entity[] | undefined; title?: string | undefined; loading?: boolean | undefined; + columns?: TableColumn[]; actions?: TableProps['actions']; }) => { const [, copyToClipboard] = useCopyToClipboard(); @@ -47,6 +53,8 @@ export const DocsTable = ({ if (!entities) return null; const documents = entities.map(entity => { + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + return { entity, resolved: { @@ -55,32 +63,18 @@ export const DocsTable = ({ kind: entity.kind, name: entity.metadata.name, }), + ownedByRelations, + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), }, }; }); - const columns = [ - { - title: 'Document', - field: 'name', - highlight: true, - render: (row: DocsTableRow): React.ReactNode => ( - {row.entity.metadata.name} - } - subvalue={row.entity.metadata.description} - /> - ), - }, - { - title: 'Owner', - field: 'entity.spec.owner', - }, - { - title: 'Type', - field: 'entity.spec.type', - }, + const defaultColumns: TableColumn[] = [ + columnFactories.createNameColumn(), + columnFactories.createOwnerColumn(), + columnFactories.createTypeColumn(), ]; const defaultActions: TableProps['actions'] = [ @@ -99,7 +93,7 @@ export const DocsTable = ({ actionsColumnIndex: -1, }} data={documents} - columns={columns} + columns={columns || defaultColumns} actions={actions || defaultActions} title={ title diff --git a/plugins/techdocs/src/home/components/EntityListDocsTable.tsx b/plugins/techdocs/src/home/components/EntityListDocsTable.tsx index 11e49fa2d4..b94e7adace 100644 --- a/plugins/techdocs/src/home/components/EntityListDocsTable.tsx +++ b/plugins/techdocs/src/home/components/EntityListDocsTable.tsx @@ -17,35 +17,40 @@ import React from 'react'; import { useCopyToClipboard } from 'react-use'; import { capitalize } from 'lodash'; -import { CodeSnippet, WarningPanel } from '@backstage/core-components'; import { - favoriteEntityIcon, - favoriteEntityTooltip, + CodeSnippet, + TableColumn, + TableProps, + WarningPanel, +} from '@backstage/core-components'; +import { useEntityListProvider, useStarredEntities, } from '@backstage/plugin-catalog-react'; -import * as actionFactories from './actions'; import { DocsTable } from './DocsTable'; +import * as actionFactories from './actions'; +import * as columnFactories from './columns'; import { DocsTableRow } from './types'; -export const EntityListDocsTable = () => { +export const EntityListDocsTable = ({ + columns, + actions, +}: { + columns?: TableColumn[]; + actions?: TableProps['actions']; +}) => { const { loading, error, entities, filters } = useEntityListProvider(); const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const [, copyToClipboard] = useCopyToClipboard(); const title = capitalize(filters.user?.value ?? 'all'); - const actions = [ + const defaultActions = [ actionFactories.createCopyDocsUrlAction(copyToClipboard), - ({ entity }: DocsTableRow) => { - const isStarred = isStarredEntity(entity); - return { - cellStyle: { paddingLeft: '1em' }, - icon: () => favoriteEntityIcon(isStarred), - tooltip: favoriteEntityTooltip(isStarred), - onClick: () => toggleStarredEntity(entity), - }; - }, + actionFactories.createStarEntityAction( + isStarredEntity, + toggleStarredEntity, + ), ]; if (error) { @@ -64,7 +69,11 @@ export const EntityListDocsTable = () => { title={title} entities={entities} loading={loading} - actions={actions} + actions={actions || defaultActions} + columns={columns} /> ); }; + +EntityListDocsTable.columns = columnFactories; +EntityListDocsTable.actions = actionFactories; diff --git a/plugins/techdocs/src/home/components/TechDocsHome.tsx b/plugins/techdocs/src/home/components/TechDocsHome.tsx index ef69bcdc44..0aa1b87282 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.tsx @@ -19,6 +19,8 @@ import { Content, ContentHeader, SupportButton, + TableColumn, + TableProps, } from '@backstage/core-components'; import { EntityListContainer, @@ -29,13 +31,23 @@ import { EntityListProvider, EntityOwnerPicker, EntityTagPicker, + UserListFilterKind, UserListPicker, } from '@backstage/plugin-catalog-react'; import { EntityListDocsTable } from './EntityListDocsTable'; import { TechDocsHomeLayout } from './TechDocsHomeLayout'; import { TechDocsPicker } from './TechDocsPicker'; +import { DocsTableRow } from './types'; -export const TechDocsHome = () => { +export const TechDocsHome = ({ + initialFilter = 'all', + columns, + actions, +}: { + initialFilter?: UserListFilterKind; + columns?: TableColumn[]; + actions?: TableProps['actions']; +}) => { return ( @@ -48,12 +60,12 @@ export const TechDocsHome = () => { - + - + diff --git a/plugins/techdocs/src/home/components/actions.tsx b/plugins/techdocs/src/home/components/actions.tsx index e26415b049..b737e99d14 100644 --- a/plugins/techdocs/src/home/components/actions.tsx +++ b/plugins/techdocs/src/home/components/actions.tsx @@ -16,6 +16,10 @@ import React from 'react'; import ShareIcon from '@material-ui/icons/Share'; +import { + favoriteEntityIcon, + favoriteEntityTooltip, +} from '@backstage/plugin-catalog-react'; import { DocsTableRow } from './types'; export function createCopyDocsUrlAction(copyToClipboard: Function) { @@ -25,8 +29,26 @@ export function createCopyDocsUrlAction(copyToClipboard: Function) { tooltip: 'Click to copy documentation link to clipboard', onClick: () => copyToClipboard( - `${window.location.href.split('/?')[0]}/${row.resolved.docsUrl}`, + `${window.location.origin}${window.location.pathname.replace( + /\/?$/, + '/', + )}${row.resolved.docsUrl}`, ), }; }; } + +export function createStarEntityAction( + isStarredEntity: Function, + toggleStarredEntity: Function, +) { + return ({ entity }: DocsTableRow) => { + const isStarred = isStarredEntity(entity); + return { + cellStyle: { paddingLeft: '1em' }, + icon: () => favoriteEntityIcon(isStarred), + tooltip: favoriteEntityTooltip(isStarred), + onClick: () => toggleStarredEntity(entity), + }; + }; +} diff --git a/plugins/techdocs/src/home/components/columns.tsx b/plugins/techdocs/src/home/components/columns.tsx new file mode 100644 index 0000000000..5cdcc66c37 --- /dev/null +++ b/plugins/techdocs/src/home/components/columns.tsx @@ -0,0 +1,56 @@ +/* + * Copyright 2021 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 { Link, SubvalueCell, TableColumn } from '@backstage/core-components'; +import { EntityRefLinks } from '@backstage/plugin-catalog-react'; +import { DocsTableRow } from './types'; + +export function createNameColumn(): TableColumn { + return { + title: 'Document', + field: 'entity.metadata.name', + highlight: true, + render: (row: DocsTableRow) => ( + {row.entity.metadata.name} + } + subvalue={row.entity.metadata.description} + /> + ), + }; +} + +export function createOwnerColumn(): TableColumn { + return { + title: 'Owner', + field: 'resolved.ownedByRelationsTitle', + render: ({ resolved }) => ( + + ), + }; +} + +export function createTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'entity.spec.type', + }; +} diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts index 392aa2e82f..a56b80f9a1 100644 --- a/plugins/techdocs/src/home/components/index.ts +++ b/plugins/techdocs/src/home/components/index.ts @@ -18,3 +18,4 @@ export { EntityListDocsTable } from './EntityListDocsTable'; export { TechDocsHomeLayout } from './TechDocsHomeLayout'; export { TechDocsPicker } from './TechDocsPicker'; export type { PanelType } from './TechDocsCustomHome'; +export type { DocsTableRow } from './types'; diff --git a/plugins/techdocs/src/home/components/types.ts b/plugins/techdocs/src/home/components/types.ts index 324cb59f96..6fd29f1716 100644 --- a/plugins/techdocs/src/home/components/types.ts +++ b/plugins/techdocs/src/home/components/types.ts @@ -14,11 +14,13 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, EntityName } from '@backstage/catalog-model'; export type DocsTableRow = { entity: Entity; resolved: { docsUrl: string; + ownedByRelationsTitle: string; + ownedByRelations: EntityName[]; }; }; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 9a93e2371c..7c3bf607b4 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -18,7 +18,7 @@ export * from './api'; export { techdocsApiRef, techdocsStorageApiRef } from './api'; export type { TechDocsApi, TechDocsStorageApi } from './api'; export { TechDocsClient, TechDocsStorageClient } from './client'; -export type { PanelType } from './home/components'; +export type { DocsTableRow, PanelType } from './home/components'; export { EntityListDocsTable, TechDocsHomeLayout, @@ -30,6 +30,7 @@ export { DocsTable, EntityTechdocsContent, TechDocsCustomHome, + TechDocsHome, TechdocsPage, techdocsPlugin as plugin, techdocsPlugin, diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 39544500bd..2e031176e4 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -113,6 +113,14 @@ export const TechDocsCustomHome = techdocsPlugin.provide( }), ); +export const TechDocsHome = techdocsPlugin.provide( + createRoutableExtension({ + component: () => + import('./home/components/TechDocsHome').then(m => m.TechDocsHome), + mountPoint: rootRouteRef, + }), +); + export const TechDocsReaderPage = techdocsPlugin.provide( createRoutableExtension({ component: () => From 80582cbec49fc9d2613724799eb08bfaa2e348dc Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Thu, 5 Aug 2021 16:37:23 -0400 Subject: [PATCH 3/3] feat(techdocs): implement composable home page Signed-off-by: Phil Kuang --- .changeset/clean-jokes-think.md | 17 +++- .changeset/plenty-carpets-jog.md | 18 ++++ packages/app/src/App.tsx | 14 +++- .../default-app/packages/app/src/App.tsx | 14 +++- plugins/techdocs/api-report.md | 54 ++++++------ plugins/techdocs/package.json | 3 +- plugins/techdocs/src/Router.tsx | 11 ++- ....test.tsx => DefaultTechDocsHome.test.tsx} | 4 +- ...chDocsHome.tsx => DefaultTechDocsHome.tsx} | 8 +- .../components/LegacyTechDocsHome.test.tsx | 82 +++++++++++++++++++ .../home/components/LegacyTechDocsHome.tsx | 55 +++++++++++++ .../home/components/TechDocsCustomHome.tsx | 14 ++-- .../components/TechDocsIndexPage.test.tsx | 44 ++++++++++ .../src/home/components/TechDocsIndexPage.tsx | 25 ++++++ ...HomeLayout.tsx => TechDocsPageWrapper.tsx} | 2 +- plugins/techdocs/src/home/components/index.ts | 3 +- plugins/techdocs/src/index.ts | 5 +- plugins/techdocs/src/plugin.ts | 6 +- 18 files changed, 317 insertions(+), 62 deletions(-) create mode 100644 .changeset/plenty-carpets-jog.md rename plugins/techdocs/src/home/components/{TechDocsHome.test.tsx => DefaultTechDocsHome.test.tsx} (95%) rename plugins/techdocs/src/home/components/{TechDocsHome.tsx => DefaultTechDocsHome.tsx} (93%) create mode 100644 plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx create mode 100644 plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx create mode 100644 plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx create mode 100644 plugins/techdocs/src/home/components/TechDocsIndexPage.tsx rename plugins/techdocs/src/home/components/{TechDocsHomeLayout.tsx => TechDocsPageWrapper.tsx} (94%) diff --git a/.changeset/clean-jokes-think.md b/.changeset/clean-jokes-think.md index 4a2c6a2c13..a4fc65e6fc 100644 --- a/.changeset/clean-jokes-think.md +++ b/.changeset/clean-jokes-think.md @@ -1,5 +1,18 @@ --- -'@backstage/plugin-techdocs': minor +'@backstage/plugin-techdocs': patch --- -Rework `TechDocsHome` to use `EntityListProvider` with support for starring docs and filtering on owned, starred, owner, and tags. +Expose a new composable `TechDocsIndexPage` and a `DefaultTechDocsHome` with support for starring docs and filtering on owned, starred, owner, and tags. + +You can migrate to the new UI view by making the following changes in your `App.tsx`: + +```diff +- } /> ++ }> ++ ++ ++ } ++ /> +``` diff --git a/.changeset/plenty-carpets-jog.md b/.changeset/plenty-carpets-jog.md new file mode 100644 index 0000000000..f4586072a7 --- /dev/null +++ b/.changeset/plenty-carpets-jog.md @@ -0,0 +1,18 @@ +--- +'@backstage/create-app': patch +--- + +Use new composable `TechDocsIndexPage` and `DefaultTechDocsHome` + +Make the following changes to your `App.tsx` to migrate existing apps: + +```diff +- } /> ++ }> ++ ++ ++ } ++ /> +``` diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 10fb743304..1132368a91 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -52,7 +52,11 @@ import { } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; -import { TechdocsPage } from '@backstage/plugin-techdocs'; +import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; import { UserSettingsPage } from '@backstage/plugin-user-settings'; import AlarmIcon from '@material-ui/icons/Alarm'; import React from 'react'; @@ -116,7 +120,13 @@ const routes = ( {entityPage} } /> - } /> + }> + + + } + /> }> diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 9b77cf6a95..57491c9222 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -13,7 +13,11 @@ import { import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; -import { TechdocsPage } from '@backstage/plugin-techdocs'; +import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; import { UserSettingsPage } from '@backstage/plugin-user-settings'; import { apis } from './apis'; import { entityPage } from './components/catalog/EntityPage'; @@ -50,7 +54,13 @@ const routes = ( > {entityPage} - } /> + }> + + + } + /> } /> } /> { icon: () => JSX.Element; @@ -50,9 +48,7 @@ function createOwnerColumn(): TableColumn; function createStarEntityAction( isStarredEntity: Function, toggleStarredEntity: Function, -): ({ - entity, -}: DocsTableRow) => { +): ({ entity }: DocsTableRow) => { cellStyle: { paddingLeft: string; }; @@ -66,6 +62,19 @@ function createStarEntityAction( // @public (undocumented) function createTypeColumn(): TableColumn; +// Warning: (ae-missing-release-tag) "DefaultTechDocsHome" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const DefaultTechDocsHome: ({ + initialFilter, + columns, + actions, +}: { + initialFilter?: UserListFilterKind | undefined; + columns?: TableColumn[] | undefined; + actions?: TableProps['actions']; +}) => JSX.Element; + // Warning: (ae-missing-release-tag) "DocsCardGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -235,39 +244,22 @@ export const TechDocsCustomHome: ({ tabsConfig: TabsConfig; }) => JSX.Element; -// Warning: (ae-missing-release-tag) "TechDocsHome" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "TechDocsIndexPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const TechDocsHome: ({ - initialFilter, - columns, - actions, -}: { - initialFilter?: UserListFilterKind | undefined; - columns?: TableColumn[] | undefined; - actions?: - | ( - | Action - | { - action: (rowData: DocsTableRow) => Action; - position: string; - } - | ((rowData: DocsTableRow) => Action) - )[] - | undefined; -}) => JSX.Element; - -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "TechDocsHomeLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TechDocsHomeLayout: ({ children }: Props) => JSX.Element; +export const TechDocsIndexPage: () => JSX.Element; // Warning: (ae-missing-release-tag) "TechdocsPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const TechdocsPage: () => JSX.Element; +// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "TechDocsPageWrapper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const TechDocsPageWrapper: ({ children }: Props) => JSX.Element; + // Warning: (ae-missing-release-tag) "TechDocsPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 9e15dd7aff..bb9f8a7c95 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -45,7 +45,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@material-ui/styles": "^4.10.0", - "@types/react": "^16.9", + "@types/react": "*", "dompurify": "^2.2.9", "event-source-polyfill": "^1.0.25", "lodash": "^4.17.21", @@ -70,7 +70,6 @@ "@types/dompurify": "^2.2.2", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "@types/react": "*", "canvas": "^2.6.1", "cross-fetch": "^3.0.6", "msw": "^0.29.0" diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx index 6218f7520d..3fb109e790 100644 --- a/plugins/techdocs/src/Router.tsx +++ b/plugins/techdocs/src/Router.tsx @@ -23,8 +23,8 @@ import { rootDocsRouteRef, rootCatalogDocsRouteRef, } from './routes'; -import { TechDocsHome } from './home/components/TechDocsHome'; -import { TechDocsPage } from './reader/components/TechDocsPage'; +import { TechDocsIndexPage } from './home/components/TechDocsIndexPage'; +import { TechDocsPage as TechDocsReaderPage } from './reader/components/TechDocsPage'; import { EntityPageDocs } from './EntityPageDocs'; import { MissingAnnotationEmptyState } from '@backstage/core-components'; @@ -33,8 +33,11 @@ const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; export const Router = () => { return ( - } /> - } /> + } /> + } + /> ); }; diff --git a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx similarity index 95% rename from plugins/techdocs/src/home/components/TechDocsHome.test.tsx rename to plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx index 9289884f1c..0b1d6e8db2 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx @@ -18,7 +18,7 @@ import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { MockStorageApi, renderInTestApp } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; import React from 'react'; -import { TechDocsHome } from './TechDocsHome'; +import { DefaultTechDocsHome } from './DefaultTechDocsHome'; import { ApiProvider, @@ -71,7 +71,7 @@ describe('TechDocs Home', () => { 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/DefaultTechDocsHome.tsx similarity index 93% rename from plugins/techdocs/src/home/components/TechDocsHome.tsx rename to plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index 0aa1b87282..ff4bf63724 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -35,11 +35,11 @@ import { UserListPicker, } from '@backstage/plugin-catalog-react'; import { EntityListDocsTable } from './EntityListDocsTable'; -import { TechDocsHomeLayout } from './TechDocsHomeLayout'; +import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { TechDocsPicker } from './TechDocsPicker'; import { DocsTableRow } from './types'; -export const TechDocsHome = ({ +export const DefaultTechDocsHome = ({ initialFilter = 'all', columns, actions, @@ -49,7 +49,7 @@ export const TechDocsHome = ({ actions?: TableProps['actions']; }) => { return ( - + @@ -70,6 +70,6 @@ export const TechDocsHome = ({ - + ); }; diff --git a/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx b/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx new file mode 100644 index 0000000000..c9a6871906 --- /dev/null +++ b/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2021 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 { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { LegacyTechDocsHome } from './LegacyTechDocsHome'; + +import { + ApiProvider, + ApiRegistry, + ConfigReader, +} from '@backstage/core-app-api'; +import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; + +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', + }, + }, + ], + }), +} as Partial; + +describe('Legacy TechDocs Home', () => { + const configApi: ConfigApi = new ConfigReader({ + organization: { + name: 'My Company', + }, + }); + + const apiRegistry = ApiRegistry.from([ + [catalogApiRef, mockCatalogApi], + [configApiRef, configApi], + ]); + + it('should render a TechDocs home page', async () => { + await renderInTestApp( + + + , + ); + + // Header + expect(await screen.findByText('Documentation')).toBeInTheDocument(); + expect( + await screen.findByText(/Documentation available in My Company/i), + ).toBeInTheDocument(); + + // Explore Content + expect(await screen.findByTestId('docs-explore')).toBeDefined(); + }); +}); diff --git a/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx b/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx new file mode 100644 index 0000000000..34ea416026 --- /dev/null +++ b/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx @@ -0,0 +1,55 @@ +/* + * Copyright 2021 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 { PanelType, TechDocsCustomHome } from './TechDocsCustomHome'; + +export const LegacyTechDocsHome = () => { + const tabsConfig = [ + { + label: 'Overview', + panels: [ + { + title: 'Overview', + description: + 'Explore your internal technical ecosystem through documentation.', + 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', + // }, + ], + }, + { + label: 'Owned Documents', + panels: [ + { + title: 'Owned documents', + description: 'Access your documentation.', + panelType: 'DocsTable' as PanelType, + // ownedByUser filters out entities owned by signed in user + filterPredicate: 'ownedByUser', + }, + ], + }, + ]; + return ; +}; diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 7f93f6a306..9975db6435 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -28,7 +28,7 @@ import { import { Entity } from '@backstage/catalog-model'; import { DocsTable } from './DocsTable'; import { DocsCardGrid } from './DocsCardGrid'; -import { TechDocsHomeLayout } from './TechDocsHomeLayout'; +import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { CodeSnippet, @@ -149,17 +149,17 @@ export const TechDocsCustomHome = ({ if (loading) { return ( - + - + ); } if (error) { return ( - + - + ); } return ( - + setSelectedTab(index)} @@ -192,6 +192,6 @@ export const TechDocsCustomHome = ({ /> ))} - + ); }; diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx new file mode 100644 index 0000000000..fbeca7cfd4 --- /dev/null +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2021 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 { renderInTestApp } from '@backstage/test-utils'; +import { useOutlet } from 'react-router'; +import { TechDocsIndexPage } from './TechDocsIndexPage'; + +jest.mock('react-router', () => ({ + ...jest.requireActual('react-router'), + useOutlet: jest.fn().mockReturnValue('Route Children'), +})); + +jest.mock('./LegacyTechDocsHome', () => ({ + LegacyTechDocsHome: jest.fn().mockReturnValue('LegacyTechDocsHomeMock'), +})); + +describe('TechDocsIndexPage', () => { + it('renders provided router element', async () => { + const { getByText } = await renderInTestApp(); + + expect(getByText('Route Children')).toBeInTheDocument(); + }); + + it('renders legacy TechDocs home when no router children are provided', async () => { + (useOutlet as jest.Mock).mockReturnValueOnce(null); + const { getByText } = await renderInTestApp(); + + expect(getByText('LegacyTechDocsHomeMock')).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx new file mode 100644 index 0000000000..ff694d3570 --- /dev/null +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx @@ -0,0 +1,25 @@ +/* + * Copyright 2021 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 { useOutlet } from 'react-router'; +import { LegacyTechDocsHome } from './LegacyTechDocsHome'; + +export const TechDocsIndexPage = () => { + const outlet = useOutlet(); + + return outlet || ; +}; diff --git a/plugins/techdocs/src/home/components/TechDocsHomeLayout.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx similarity index 94% rename from plugins/techdocs/src/home/components/TechDocsHomeLayout.tsx rename to plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index e6717fbecc..453cfc22cc 100644 --- a/plugins/techdocs/src/home/components/TechDocsHomeLayout.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -23,7 +23,7 @@ type Props = { children?: React.ReactNode; }; -export const TechDocsHomeLayout = ({ children }: Props) => { +export const TechDocsPageWrapper = ({ children }: Props) => { const configApi = useApi(configApiRef); const generatedSubtitle = `Documentation available in ${ configApi.getOptionalString('organization.name') ?? 'Backstage' diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts index a56b80f9a1..53dd0fd909 100644 --- a/plugins/techdocs/src/home/components/index.ts +++ b/plugins/techdocs/src/home/components/index.ts @@ -15,7 +15,8 @@ */ export { EntityListDocsTable } from './EntityListDocsTable'; -export { TechDocsHomeLayout } from './TechDocsHomeLayout'; +export { DefaultTechDocsHome } from './DefaultTechDocsHome'; +export { TechDocsPageWrapper } from './TechDocsPageWrapper'; export { TechDocsPicker } from './TechDocsPicker'; export type { PanelType } from './TechDocsCustomHome'; export type { DocsTableRow } from './types'; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 7c3bf607b4..4ebf29180c 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -21,7 +21,8 @@ export { TechDocsClient, TechDocsStorageClient } from './client'; export type { DocsTableRow, PanelType } from './home/components'; export { EntityListDocsTable, - TechDocsHomeLayout, + DefaultTechDocsHome, + TechDocsPageWrapper, TechDocsPicker, } from './home/components'; export * from './components/DocsResultListItem'; @@ -30,7 +31,7 @@ export { DocsTable, EntityTechdocsContent, TechDocsCustomHome, - TechDocsHome, + TechDocsIndexPage, TechdocsPage, techdocsPlugin as plugin, techdocsPlugin, diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 2e031176e4..18b386f88c 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -113,10 +113,12 @@ export const TechDocsCustomHome = techdocsPlugin.provide( }), ); -export const TechDocsHome = techdocsPlugin.provide( +export const TechDocsIndexPage = techdocsPlugin.provide( createRoutableExtension({ component: () => - import('./home/components/TechDocsHome').then(m => m.TechDocsHome), + import('./home/components/TechDocsIndexPage').then( + m => m.TechDocsIndexPage, + ), mountPoint: rootRouteRef, }), );