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,