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: () =>