diff --git a/.changeset/clean-jokes-think.md b/.changeset/clean-jokes-think.md new file mode 100644 index 0000000000..a4fc65e6fc --- /dev/null +++ b/.changeset/clean-jokes-think.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +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/.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/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} - } /> + }> + + + } + /> } /> } /> ; // // @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..af12b42ad2 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,7 +15,65 @@ 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'; +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) "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) // @@ -42,16 +101,58 @@ export const DocsResultListItem: ({ export const DocsTable: ({ entities, title, + loading, + columns, + actions, }: { entities: Entity[] | undefined; title?: string | undefined; + loading?: boolean | undefined; + columns?: TableColumn[] | undefined; + actions?: + | ( + | Action + | { + action: (rowData: DocsTableRow) => Action; + position: string; + } + | ((rowData: DocsTableRow) => Action) + )[] + | 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) // // @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: { + ({ + 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) // @@ -69,7 +170,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 +244,27 @@ export const TechDocsCustomHome: ({ tabsConfig: TabsConfig; }) => JSX.Element; +// 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 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) +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 +372,9 @@ 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/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/package.json b/plugins/techdocs/package.json index 7051109c2e..bb9f8a7c95 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": "*", "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", @@ -67,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/DefaultTechDocsHome.test.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx new file mode 100644 index 0000000000..0b1d6e8db2 --- /dev/null +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx @@ -0,0 +1,84 @@ +/* + * 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 { MockStorageApi, renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { DefaultTechDocsHome } from './DefaultTechDocsHome'; + +import { + ApiProvider, + ApiRegistry, + ConfigReader, +} from '@backstage/core-app-api'; +import { + ConfigApi, + configApiRef, + storageApiRef, +} 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: () => Promise.resolve(), + getEntities: async () => ({ + items: [ + { + apiVersion: 'version', + kind: 'User', + metadata: { + name: 'owned', + namespace: 'default', + }, + }, + ], + }), +} as Partial; + +describe('TechDocs Home', () => { + const configApi: ConfigApi = new ConfigReader({ + organization: { + name: 'My Company', + }, + }); + + const apiRegistry = ApiRegistry.from([ + [catalogApiRef, mockCatalogApi], + [configApiRef, configApi], + [storageApiRef, MockStorageApi.create()], + ]); + + 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(); + }); +}); diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx new file mode 100644 index 0000000000..ff4bf63724 --- /dev/null +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -0,0 +1,75 @@ +/* + * 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 { + Content, + ContentHeader, + SupportButton, + TableColumn, + TableProps, +} from '@backstage/core-components'; +import { + EntityListContainer, + FilterContainer, + FilteredEntityLayout, +} from '@backstage/plugin-catalog'; +import { + EntityListProvider, + EntityOwnerPicker, + EntityTagPicker, + UserListFilterKind, + UserListPicker, +} from '@backstage/plugin-catalog-react'; +import { EntityListDocsTable } from './EntityListDocsTable'; +import { TechDocsPageWrapper } from './TechDocsPageWrapper'; +import { TechDocsPicker } from './TechDocsPicker'; +import { DocsTableRow } from './types'; + +export const DefaultTechDocsHome = ({ + initialFilter = 'all', + columns, + actions, +}: { + initialFilter?: UserListFilterKind; + columns?: TableColumn[]; + actions?: TableProps['actions']; +}) => { + return ( + + + + + Discover documentation in your ecosystem. + + + + + + + + + + + + + + + + + + ); +}; diff --git a/plugins/techdocs/src/home/components/DocsTable.tsx b/plugins/techdocs/src/home/components/DocsTable.tsx index 031f09868f..29b63f5441 100644 --- a/plugins/techdocs/src/home/components/DocsTable.tsx +++ b/plugins/techdocs/src/home/components/DocsTable.tsx @@ -18,91 +18,83 @@ 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 { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { + formatEntityRefTitle, + getEntityRelations, +} from '@backstage/plugin-catalog-react'; import { rootDocsRouteRef } from '../../routes'; import { - Table, - EmptyState, Button, - SubvalueCell, - Link, + EmptyState, + 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(); if (!entities) return null; const documents = entities.map(entity => { + const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + 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, + }), + ownedByRelations, + ownedByRelationsTitle: ownedByRelations + .map(r => formatEntityRefTitle(r, { defaultKind: 'group' })) + .join(', '), + }, }; }); - const columns = [ - { - title: 'Document', - field: 'name', - highlight: true, - render: (row: any): React.ReactNode => ( - {row.name}} - subvalue={row.description} - /> - ), - }, - { - title: 'Owner', - field: 'owner', - }, - { - title: 'Type', - field: 'type', - }, - { - title: 'Actions', - width: '10%', - render: (row: any) => ( - - - copyToClipboard(`${window.location.href}/${row.docsUrl}`) - } - > - - - - ), - }, + const defaultColumns: TableColumn[] = [ + columnFactories.createNameColumn(), + columnFactories.createOwnerColumn(), + columnFactories.createTypeColumn(), + ]; + + 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} + columns={columns || defaultColumns} + 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..b94e7adace --- /dev/null +++ b/plugins/techdocs/src/home/components/EntityListDocsTable.tsx @@ -0,0 +1,79 @@ +/* + * 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, + TableColumn, + TableProps, + WarningPanel, +} from '@backstage/core-components'; +import { + useEntityListProvider, + useStarredEntities, +} from '@backstage/plugin-catalog-react'; +import { DocsTable } from './DocsTable'; +import * as actionFactories from './actions'; +import * as columnFactories from './columns'; +import { DocsTableRow } from './types'; + +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 defaultActions = [ + actionFactories.createCopyDocsUrlAction(copyToClipboard), + actionFactories.createStarEntityAction( + isStarredEntity, + toggleStarredEntity, + ), + ]; + + if (error) { + return ( + + + + ); + } + + return ( + + ); +}; + +EntityListDocsTable.columns = columnFactories; +EntityListDocsTable.actions = actionFactories; diff --git a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx b/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx similarity index 94% rename from plugins/techdocs/src/home/components/TechDocsHome.test.tsx rename to plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx index f90b35e181..c9a6871906 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx +++ b/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx @@ -18,7 +18,7 @@ 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 { TechDocsHome } from './TechDocsHome'; +import { LegacyTechDocsHome } from './LegacyTechDocsHome'; import { ApiProvider, @@ -51,7 +51,7 @@ const mockCatalogApi = { }), } as Partial; -describe('TechDocs Home', () => { +describe('Legacy TechDocs Home', () => { const configApi: ConfigApi = new ConfigReader({ organization: { name: 'My Company', @@ -66,7 +66,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/LegacyTechDocsHome.tsx similarity index 97% rename from plugins/techdocs/src/home/components/TechDocsHome.tsx rename to plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx index 6445713875..34ea416026 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome'; -export const TechDocsHome = () => { +export const LegacyTechDocsHome = () => { const tabsConfig = [ { label: 'Overview', diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index e2623fe81f..9975db6435 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 { TechDocsPageWrapper } from './TechDocsPageWrapper'; 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/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/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx new file mode 100644 index 0000000000..453cfc22cc --- /dev/null +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.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 TechDocsPageWrapper = ({ 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..b737e99d14 --- /dev/null +++ b/plugins/techdocs/src/home/components/actions.tsx @@ -0,0 +1,54 @@ +/* + * 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 { + favoriteEntityIcon, + favoriteEntityTooltip, +} from '@backstage/plugin-catalog-react'; +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.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 new file mode 100644 index 0000000000..53dd0fd909 --- /dev/null +++ b/plugins/techdocs/src/home/components/index.ts @@ -0,0 +1,22 @@ +/* + * 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 { 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/home/components/types.ts b/plugins/techdocs/src/home/components/types.ts new file mode 100644 index 0000000000..6fd29f1716 --- /dev/null +++ b/plugins/techdocs/src/home/components/types.ts @@ -0,0 +1,26 @@ +/* + * 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, 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 66502a4585..4ebf29180c 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -18,13 +18,20 @@ 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 { DocsTableRow, PanelType } from './home/components'; +export { + EntityListDocsTable, + DefaultTechDocsHome, + TechDocsPageWrapper, + TechDocsPicker, +} from './home/components'; export * from './components/DocsResultListItem'; export { DocsCardGrid, DocsTable, EntityTechdocsContent, TechDocsCustomHome, + TechDocsIndexPage, TechdocsPage, techdocsPlugin as plugin, techdocsPlugin, diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 39544500bd..18b386f88c 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -113,6 +113,16 @@ export const TechDocsCustomHome = techdocsPlugin.provide( }), ); +export const TechDocsIndexPage = techdocsPlugin.provide( + createRoutableExtension({ + component: () => + import('./home/components/TechDocsIndexPage').then( + m => m.TechDocsIndexPage, + ), + mountPoint: rootRouteRef, + }), +); + export const TechDocsReaderPage = techdocsPlugin.provide( createRoutableExtension({ component: () =>