Merge pull request #6458 from kuangp/refactor/techdocsHome
refactor(techdocs): rework TechDocsHome to use EntityListProvider
This commit is contained in:
@@ -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
|
||||
- <Route path="/docs" element={<TechdocsPage />} />
|
||||
+ <Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
+ <DefaultTechDocsHome />
|
||||
+ </Route>
|
||||
+ <Route
|
||||
+ path="/docs/:namespace/:kind/:name/*"
|
||||
+ element={<TechDocsReaderPage />}
|
||||
+ />
|
||||
```
|
||||
@@ -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
|
||||
- <Route path="/docs" element={<TechdocsPage />} />
|
||||
+ <Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
+ <DefaultTechDocsHome />
|
||||
+ </Route>
|
||||
+ <Route
|
||||
+ path="/docs/:namespace/:kind/:name/*"
|
||||
+ element={<TechDocsReaderPage />}
|
||||
+ />
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
Move and rename `FavoriteEntity` component to `catalog-react`
|
||||
@@ -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}
|
||||
</Route>
|
||||
<Route path="/catalog-import" element={<CatalogImportPage />} />
|
||||
<Route path="/docs" element={<TechdocsPage />} />
|
||||
<Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
<DefaultTechDocsHome />
|
||||
</Route>
|
||||
<Route
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
/>
|
||||
<Route path="/create" element={<ScaffolderPage />}>
|
||||
<ScaffolderFieldExtensions>
|
||||
<EntityPickerFieldExtension />
|
||||
|
||||
@@ -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}
|
||||
</Route>
|
||||
<Route path="/docs" element={<TechdocsPage />} />
|
||||
<Route path="/docs" element={<TechDocsIndexPage />}>
|
||||
<DefaultTechDocsHome />
|
||||
</Route>
|
||||
<Route
|
||||
path="/docs/:namespace/:kind/:name/*"
|
||||
element={<TechDocsReaderPage />}
|
||||
/>
|
||||
<Route path="/create" element={<ScaffolderPage />} />
|
||||
<Route path="/api-docs" element={<ApiExplorerPage />} />
|
||||
<Route
|
||||
|
||||
@@ -10,9 +10,11 @@ import { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { ComponentEntity } from '@backstage/catalog-model';
|
||||
import { ComponentProps } from 'react';
|
||||
import { Context } from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { IconButton } from '@material-ui/core';
|
||||
import { LinkProps } from '@backstage/core-components';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
@@ -116,7 +118,10 @@ export const EntityContext: Context<EntityLoadingStatus>;
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type EntityFilter = {
|
||||
getCatalogFilters?: () => Record<string, string | string[]>;
|
||||
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<string, string | string[]>;
|
||||
): Record<string, string | symbol | (string | symbol)[]>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "reduceEntityFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
+7
-7
@@ -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 ? <YellowStar /> : <StarBorder />;
|
||||
|
||||
/**
|
||||
* 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)}
|
||||
>
|
||||
<Tooltip title={favouriteEntityTooltip(isStarred)}>
|
||||
{favouriteEntityIcon(isStarred)}
|
||||
<Tooltip title={favoriteEntityTooltip(isStarred)}>
|
||||
{favoriteEntityIcon(isStarred)}
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
);
|
||||
@@ -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';
|
||||
@@ -22,4 +22,5 @@ export * from './EntitySearchBar';
|
||||
export * from './EntityTable';
|
||||
export * from './EntityTagPicker';
|
||||
export * from './EntityTypePicker';
|
||||
export * from './FavoriteEntity';
|
||||
export * from './UserListPicker';
|
||||
|
||||
@@ -23,7 +23,10 @@ export type EntityFilter = {
|
||||
* { field: 'kind', values: ['component'] }
|
||||
* { field: 'metadata.name', values: ['component-1', 'component-2'] }
|
||||
*/
|
||||
getCatalogFilters?: () => Record<string, string | string[]>;
|
||||
getCatalogFilters?: () => Record<
|
||||
string,
|
||||
string | symbol | (string | symbol)[]
|
||||
>;
|
||||
|
||||
/**
|
||||
* Filter entities on the frontend after a catalog-backend request. This function will be called
|
||||
|
||||
@@ -19,13 +19,13 @@ import { EntityFilter } from '../types';
|
||||
|
||||
export function reduceCatalogFilters(
|
||||
filters: EntityFilter[],
|
||||
): Record<string, string | string[]> {
|
||||
): Record<string, string | symbol | (string | symbol)[]> {
|
||||
return filters.reduce((compoundFilter, filter) => {
|
||||
return {
|
||||
...compoundFilter,
|
||||
...(filter.getCatalogFilters ? filter.getCatalogFilters() : {}),
|
||||
};
|
||||
}, {} as Record<string, string | string[]>);
|
||||
}, {} as Record<string, string | symbol | (string | symbol)[]>);
|
||||
}
|
||||
|
||||
export function reduceEntityFilters(
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -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}
|
||||
</Box>
|
||||
{entity && <FavouriteEntity entity={entity} />}
|
||||
{entity && <FavoriteEntity entity={entity} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 = ({
|
||||
}) => (
|
||||
<Box display="inline-flex" alignItems="center" height="1em">
|
||||
{title}
|
||||
{entity && <FavouriteEntity entity={entity} />}
|
||||
{entity && <FavoriteEntity entity={entity} />}
|
||||
</Box>
|
||||
);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
```ts
|
||||
/// <reference types="react" />
|
||||
|
||||
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<DocsTableRow>;
|
||||
|
||||
// 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<DocsTableRow>;
|
||||
|
||||
// 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<DocsTableRow>;
|
||||
|
||||
// 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<DocsTableRow>[] | undefined;
|
||||
actions?: TableProps<DocsTableRow>['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<DocsTableRow>[] | undefined;
|
||||
actions?:
|
||||
| (
|
||||
| Action<DocsTableRow>
|
||||
| {
|
||||
action: (rowData: DocsTableRow) => Action<DocsTableRow>;
|
||||
position: string;
|
||||
}
|
||||
| ((rowData: DocsTableRow) => Action<DocsTableRow>)
|
||||
)[]
|
||||
| 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<DocsTableRow>[] | undefined;
|
||||
actions?: TableProps<DocsTableRow>['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)
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
<Routes>
|
||||
<Route path={`/${rootRouteRef.path}`} element={<TechDocsHome />} />
|
||||
<Route path={`/${rootDocsRouteRef.path}`} element={<TechDocsPage />} />
|
||||
<Route path={`/${rootRouteRef.path}`} element={<TechDocsIndexPage />} />
|
||||
<Route
|
||||
path={`/${rootDocsRouteRef.path}`}
|
||||
element={<TechDocsReaderPage />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<CatalogApi>;
|
||||
|
||||
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(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<DefaultTechDocsHome />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
// Header
|
||||
expect(await screen.findByText('Documentation')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText(/Documentation available in My Company/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -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<DocsTableRow>[];
|
||||
actions?: TableProps<DocsTableRow>['actions'];
|
||||
}) => {
|
||||
return (
|
||||
<TechDocsPageWrapper>
|
||||
<Content>
|
||||
<ContentHeader title="">
|
||||
<SupportButton>
|
||||
Discover documentation in your ecosystem.
|
||||
</SupportButton>
|
||||
</ContentHeader>
|
||||
<EntityListProvider>
|
||||
<FilteredEntityLayout>
|
||||
<FilterContainer>
|
||||
<TechDocsPicker />
|
||||
<UserListPicker initialFilter={initialFilter} />
|
||||
<EntityOwnerPicker />
|
||||
<EntityTagPicker />
|
||||
</FilterContainer>
|
||||
<EntityListContainer>
|
||||
<EntityListDocsTable actions={actions} columns={columns} />
|
||||
</EntityListContainer>
|
||||
</FilteredEntityLayout>
|
||||
</EntityListProvider>
|
||||
</Content>
|
||||
</TechDocsPageWrapper>
|
||||
);
|
||||
};
|
||||
@@ -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<DocsTableRow>[];
|
||||
actions?: TableProps<DocsTableRow>['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 => (
|
||||
<SubvalueCell
|
||||
value={<Link to={row.docsUrl}>{row.name}</Link>}
|
||||
subvalue={row.description}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Owner',
|
||||
field: 'owner',
|
||||
},
|
||||
{
|
||||
title: 'Type',
|
||||
field: 'type',
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
width: '10%',
|
||||
render: (row: any) => (
|
||||
<Tooltip title="Click to copy documentation link to clipboard">
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
copyToClipboard(`${window.location.href}/${row.docsUrl}`)
|
||||
}
|
||||
>
|
||||
<ShareIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
),
|
||||
},
|
||||
const defaultColumns: TableColumn<DocsTableRow>[] = [
|
||||
columnFactories.createNameColumn(),
|
||||
columnFactories.createOwnerColumn(),
|
||||
columnFactories.createTypeColumn(),
|
||||
];
|
||||
|
||||
const defaultActions: TableProps<DocsTableRow>['actions'] = [
|
||||
actionFactories.createCopyDocsUrlAction(copyToClipboard),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{documents && documents.length > 0 ? (
|
||||
<Table
|
||||
{loading || (documents && documents.length > 0) ? (
|
||||
<Table<DocsTableRow>
|
||||
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})`
|
||||
|
||||
@@ -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<DocsTableRow>[];
|
||||
actions?: TableProps<DocsTableRow>['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 (
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
title="Could not load available documentation."
|
||||
>
|
||||
<CodeSnippet language="text" text={error.toString()} />
|
||||
</WarningPanel>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DocsTable
|
||||
title={title}
|
||||
entities={entities}
|
||||
loading={loading}
|
||||
actions={actions || defaultActions}
|
||||
columns={columns}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
EntityListDocsTable.columns = columnFactories;
|
||||
EntityListDocsTable.actions = actionFactories;
|
||||
+3
-3
@@ -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<CatalogApi>;
|
||||
|
||||
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(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<TechDocsHome />
|
||||
<LegacyTechDocsHome />
|
||||
</ApiProvider>,
|
||||
);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
import React from 'react';
|
||||
import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome';
|
||||
|
||||
export const TechDocsHome = () => {
|
||||
export const LegacyTechDocsHome = () => {
|
||||
const tabsConfig = [
|
||||
{
|
||||
label: 'Overview',
|
||||
@@ -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<number>(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 (
|
||||
<Page themeId="documentation">
|
||||
<Header title="Documentation" subtitle={generatedSubtitle} />
|
||||
<TechDocsPageWrapper>
|
||||
<Content>
|
||||
<Progress />
|
||||
</Content>
|
||||
</Page>
|
||||
</TechDocsPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Page themeId="documentation">
|
||||
<Header title="Documentation" subtitle={generatedSubtitle} />
|
||||
<TechDocsPageWrapper>
|
||||
<Content>
|
||||
<WarningPanel
|
||||
severity="error"
|
||||
@@ -176,13 +168,12 @@ export const TechDocsCustomHome = ({
|
||||
<CodeSnippet language="text" text={error.toString()} />
|
||||
</WarningPanel>
|
||||
</Content>
|
||||
</Page>
|
||||
</TechDocsPageWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page themeId="documentation">
|
||||
<Header title="Documentation" subtitle={generatedSubtitle} />
|
||||
<TechDocsPageWrapper>
|
||||
<HeaderTabs
|
||||
selectedIndex={selectedTab}
|
||||
onChange={index => setSelectedTab(index)}
|
||||
@@ -201,6 +192,6 @@ export const TechDocsCustomHome = ({
|
||||
/>
|
||||
))}
|
||||
</Content>
|
||||
</Page>
|
||||
</TechDocsPageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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(<TechDocsIndexPage />);
|
||||
|
||||
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(<TechDocsIndexPage />);
|
||||
|
||||
expect(getByText('LegacyTechDocsHomeMock')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -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 || <LegacyTechDocsHome />;
|
||||
};
|
||||
@@ -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 (
|
||||
<PageWithHeader
|
||||
title="Documentation"
|
||||
subtitle={generatedSubtitle}
|
||||
themeId="documentation"
|
||||
>
|
||||
{children}
|
||||
</PageWithHeader>
|
||||
);
|
||||
};
|
||||
@@ -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<string, string | symbol | (string | symbol)[]> {
|
||||
return {
|
||||
'metadata.annotations.backstage.io/techdocs-ref': CATALOG_FILTER_EXISTS,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
type CustomFilters = DefaultEntityFilters & {
|
||||
techdocs?: TechDocsFilter;
|
||||
};
|
||||
|
||||
export const TechDocsPicker = () => {
|
||||
const { updateFilters } = useEntityListProvider<CustomFilters>();
|
||||
|
||||
useEffect(() => {
|
||||
updateFilters({
|
||||
techdocs: new TechDocsFilter(),
|
||||
});
|
||||
}, [updateFilters]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -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: () => <ShareIcon fontSize="small" />,
|
||||
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),
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -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<DocsTableRow> {
|
||||
return {
|
||||
title: 'Document',
|
||||
field: 'entity.metadata.name',
|
||||
highlight: true,
|
||||
render: (row: DocsTableRow) => (
|
||||
<SubvalueCell
|
||||
value={
|
||||
<Link to={row.resolved.docsUrl}>{row.entity.metadata.name}</Link>
|
||||
}
|
||||
subvalue={row.entity.metadata.description}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createOwnerColumn(): TableColumn<DocsTableRow> {
|
||||
return {
|
||||
title: 'Owner',
|
||||
field: 'resolved.ownedByRelationsTitle',
|
||||
render: ({ resolved }) => (
|
||||
<EntityRefLinks
|
||||
entityRefs={resolved.ownedByRelations}
|
||||
defaultKind="group"
|
||||
/>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function createTypeColumn(): TableColumn<DocsTableRow> {
|
||||
return {
|
||||
title: 'Type',
|
||||
field: 'entity.spec.type',
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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[];
|
||||
};
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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: () =>
|
||||
|
||||
Reference in New Issue
Block a user