From cffde24a15c4f9b7e5936288603af09671eb5773 Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 26 Nov 2024 17:15:40 -0800 Subject: [PATCH 01/18] add props to customize CustomHome Signed-off-by: nikolar --- plugins/techdocs/package.json | 1 + .../home/components/DefaultTechDocsHome.tsx | 35 +++++-- .../home/components/Grids/InfoCardGrid.tsx | 94 +++++++++++++++++++ .../src/home/components/Grids/index.ts | 1 + .../home/components/TechDocsCustomHome.tsx | 65 +++++++++---- .../src/home/components/TechDocsIndexPage.tsx | 11 ++- .../home/components/TechDocsPageWrapper.tsx | 17 ++-- plugins/techdocs/src/overridableComponents.ts | 36 +++++++ yarn.lock | 1 + 9 files changed, 227 insertions(+), 34 deletions(-) create mode 100644 plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx create mode 100644 plugins/techdocs/src/overridableComponents.ts diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 7032277a95..d443b8ebe7 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -59,6 +59,7 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/core-compat-api": "workspace:^", diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index 2fbd5bb50a..f4a4e7862a 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -46,15 +46,28 @@ export type DefaultTechDocsHomeProps = TechDocsIndexPageProps; * @public */ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { - const { initialFilter = 'owned', columns, actions, ownerPickerMode } = props; + const { + initialFilter = 'owned', + columns, + actions, + ownerPickerMode, + showHeader, + options, + title, + subtitle, + hideSupport, + } = props; + const Wrapper = showHeader !== false ? TechDocsPageWrapper : React.Fragment; return ( - + - - - Discover documentation in your ecosystem. - - + {hideSupport !== true && ( + + + Discover documentation in your ecosystem. + + + )} @@ -64,11 +77,15 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { - + - + ); }; diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx new file mode 100644 index 0000000000..46293f9228 --- /dev/null +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -0,0 +1,94 @@ +/* + * 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 { rootDocsRouteRef } from '../../../routes'; +import { toLowerMaybe } from '../../../helpers'; +import { Entity } from '@backstage/catalog-model'; +import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api'; +import { ItemCardGrid, InfoCard, Link } from '@backstage/core-components'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +/** @public */ +export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; + +const useStyles = makeStyles( + theme => ({ + linkSpacer: { + paddingTop: theme.spacing(0.2), + }, + readMoreLink: { + paddingTop: theme.spacing(0.2), + }, + }), + { name: 'BackstageInfoCardGrid' }, +); + +/** + * Props for {@link InfoCardGird} + * + * @public + */ +export type InfoCardGirdProps = { + entities: Entity[] | undefined; + linkContent?: string | JSX.Element; + linkDest?: (entity: Entity) => string; +}; + +/** + * Component which accepts a list of entities and renders a info card for each entity + * + * @public + */ +export const InfoCardGird = (props: InfoCardGirdProps) => { + const { entities, linkContent, linkDest } = props; + const classes = useStyles(); + const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); + const config = useApi(configApiRef); + if (!entities) return null; + return ( + + {!entities?.length + ? null + : entities.map(entity => ( + +
{entity?.metadata?.description}
+
+ + {linkContent || 'Read Docs'} + + + ))} + + ); +}; diff --git a/plugins/techdocs/src/home/components/Grids/index.ts b/plugins/techdocs/src/home/components/Grids/index.ts index c28b5a0723..136e31d569 100644 --- a/plugins/techdocs/src/home/components/Grids/index.ts +++ b/plugins/techdocs/src/home/components/Grids/index.ts @@ -16,3 +16,4 @@ export * from './EntityListDocsGrid'; export * from './DocsCardGrid'; +export * from './InfoCardGrid'; diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 56d086acfc..5024eab5c3 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -26,8 +26,9 @@ import { } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { DocsTable } from './Tables'; -import { DocsCardGrid } from './Grids'; +import { DocsCardGrid, InfoCardGird } from './Grids'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; +import { TechDocsIndexPage } from './TechDocsIndexPage'; import { CodeSnippet, @@ -40,10 +41,13 @@ import { } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; +import { EntityFilterQuery } from '@backstage/catalog-client'; const panels = { DocsTable: DocsTable, DocsCardGrid: DocsCardGrid, + TechDocsIndexPage: TechDocsIndexPage, + InfoCardGird: InfoCardGird, }; /** @@ -51,7 +55,11 @@ const panels = { * * @public */ -export type PanelType = 'DocsCardGrid' | 'DocsTable'; +export type PanelType = + | 'DocsCardGrid' + | 'DocsTable' + | 'TechDocsIndexPage' + | 'InfoCardGird'; /** * Type representing a TechDocsCustomHome panel. @@ -64,6 +72,7 @@ export interface PanelConfig { panelType: PanelType; panelCSS?: CSSProperties; filterPredicate: ((entity: Entity) => boolean) | string; + panelProps?: Record; } /** @@ -119,15 +128,21 @@ const CustomPanel = ({ return ( <> - - {index === 0 ? ( - - Discover documentation in your ecosystem. - - ) : null} - + {config.panelProps?.showHeader !== false && ( + + {index === 0 && config.panelProps?.hideSupport !== true && ( + + Discover documentation in your ecosystem. + + )} + + )}
- +
); @@ -140,10 +155,14 @@ const CustomPanel = ({ */ export type TechDocsCustomHomeProps = { tabsConfig: TabsConfig; + filter?: EntityFilterQuery; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { - const { tabsConfig } = props; + const { tabsConfig, filter, title, subtitle, hideSubtitle } = props; const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); @@ -153,7 +172,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { error, } = useAsync(async () => { const response = await catalogApi.getEntities({ - filter: { + filter: filter || { [`metadata.annotations.${TECHDOCS_ANNOTATION}`]: CATALOG_FILTER_EXISTS, }, fields: [ @@ -165,16 +184,18 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { 'spec.type', ], }); - return response.items.filter((entity: Entity) => { - return !!entity.metadata.annotations?.[TECHDOCS_ANNOTATION]; - }); + return response.items; }); const currentTabConfig = tabsConfig[selectedTab]; if (loading) { return ( - + @@ -184,7 +205,11 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { if (error) { return ( - + { } return ( - + setSelectedTab(index)} diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx index adbb68d3e9..baf50b89f3 100644 --- a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx @@ -16,7 +16,11 @@ import React from 'react'; import { useOutlet } from 'react-router-dom'; -import { TableColumn, TableProps } from '@backstage/core-components'; +import { + TableColumn, + TableProps, + TableOptions, +} from '@backstage/core-components'; import { EntityOwnerPickerProps, UserListFilterKind, @@ -34,6 +38,11 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; + showHeader?: boolean; + hideSupport?: boolean; + options?: TableOptions; + title?: string; + subtitle?: string; }; export const TechDocsIndexPage = (props: TechDocsIndexPageProps) => { diff --git a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index a6192c13ee..eedac5ee95 100644 --- a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -26,6 +26,9 @@ import { useApi, configApiRef } from '@backstage/core-plugin-api'; */ export type TechDocsPageWrapperProps = { children?: React.ReactNode; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; /** @@ -34,16 +37,18 @@ export type TechDocsPageWrapperProps = { * @public */ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { - const { children } = props; + const { children, title, subtitle, hideSubtitle } = props; const configApi = useApi(configApiRef); - const generatedSubtitle = `Documentation available in ${ - configApi.getOptionalString('organization.name') ?? 'Backstage' - }`; + const generatedSubtitle = + subtitle || + `Documentation available in ${ + configApi.getOptionalString('organization.name') ?? 'Backstage' + }`; return ( {children} diff --git a/plugins/techdocs/src/overridableComponents.ts b/plugins/techdocs/src/overridableComponents.ts new file mode 100644 index 0000000000..8b371d8798 --- /dev/null +++ b/plugins/techdocs/src/overridableComponents.ts @@ -0,0 +1,36 @@ +/* + * 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 { Overrides } from '@material-ui/core/styles/overrides'; +import { StyleRules } from '@material-ui/core/styles/withStyles'; +import { InfoCardGridClassKey } from './home/components/Grids/InfoCardGrid'; + +/** @public */ +export type CatalogReactComponentsNameToClassKey = { + BackstageInfoCardGrid: InfoCardGridClassKey; +}; + +/** @public */ +export type BackstageOverrides = Overrides & { + [Name in keyof CatalogReactComponentsNameToClassKey]?: Partial< + StyleRules + >; +}; + +declare module '@backstage/theme' { + interface OverrideComponentNameToClassKeys + extends CatalogReactComponentsNameToClassKey {} +} diff --git a/yarn.lock b/yarn.lock index 1e2c2e6742..d172cfc75e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8472,6 +8472,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-techdocs@workspace:plugins/techdocs" dependencies: + "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" From b6a8c56a8161ea9aba865ef67c76be1090e210e7 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 2 Dec 2024 20:09:30 -0800 Subject: [PATCH 02/18] add testing Signed-off-by: nikolar --- .../components/Grids/InfoCardGrid.test.tsx | 191 ++++++++++++++++++ .../home/components/Grids/InfoCardGrid.tsx | 39 ++-- .../components/TechDocsCustomHome.test.tsx | 129 ++++++++++++ .../home/components/TechDocsCustomHome.tsx | 13 +- 4 files changed, 349 insertions(+), 23 deletions(-) create mode 100644 plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx new file mode 100644 index 0000000000..dcb773120e --- /dev/null +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx @@ -0,0 +1,191 @@ +/* + * 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 { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { rootDocsRouteRef } from '../../../routes'; +import { InfoCardGrid } from './InfoCardGrid'; + +describe('Entity Info Card Grid', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should render multiple entities', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('TestTitle')).toBeInTheDocument(); + expect(await screen.findByText('TestTitle2')).toBeInTheDocument(); + }); + + it('should handle missing data gracefully', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('testName')).toBeInTheDocument(); + }); + + it('should render links correctly', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('TestTitle')).toBeInTheDocument(); + expect(await screen.findByText('TestTitle2')).toBeInTheDocument(); + const [button1, button2] = await screen.findAllByTestId('read-docs-link'); + expect(button1.getAttribute('href')).toContain( + '/docs/default/testkind/testname', + ); + expect(button2.getAttribute('href')).toContain( + '/docs/default/testkind2/testname2', + ); + }); + + it('should render entity title if available', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('TestTitle')).toBeInTheDocument(); + }); + + it('should render entity name if title is not available', async () => { + await renderInTestApp( + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.findByText('testName')).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 46293f9228..7a96c2af4c 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -38,11 +38,11 @@ const useStyles = makeStyles( ); /** - * Props for {@link InfoCardGird} + * Props for {@link InfoCardGrid} * * @public */ -export type InfoCardGirdProps = { +export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; linkDest?: (entity: Entity) => string; @@ -53,37 +53,40 @@ export type InfoCardGirdProps = { * * @public */ -export const InfoCardGird = (props: InfoCardGirdProps) => { +export const InfoCardGrid = (props: InfoCardGridProps) => { const { entities, linkContent, linkDest } = props; const classes = useStyles(); const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); + const linkDestination = (entity: Entity) => + typeof linkDest === 'function' + ? linkDest(entity) + : getRouteToReaderPageFor({ + namespace: toLowerMaybe( + entity.metadata.namespace ?? 'default', + config, + ), + kind: toLowerMaybe(entity.kind, config), + name: toLowerMaybe(entity.metadata.name, config), + }); + if (!entities) return null; return ( - + {!entities?.length ? null : entities.map(entity => (
{entity?.metadata?.description}
{linkContent || 'Read Docs'} diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index 409d68a593..66e01ce780 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -96,4 +96,133 @@ describe('TechDocsCustomHome', () => { await screen.findByText('Second Tab Description'), ).toBeInTheDocument(); }); + it('should render ContentHeader based on showHeader prop', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + panelProps: { showHeader: false }, + filterPredicate: () => true, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + screen.queryByText('Discover documentation in your ecosystem.'), + ).not.toBeInTheDocument(); + }); + it('should render SupportButton based on hideSupport prop', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + filterPredicate: () => true, + panelProps: { hideSupport: true }, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + screen.queryByText('Discover documentation in your ecosystem.'), + ).not.toBeInTheDocument(); + }); + it('should hide subtitle when hideSubtitle is true', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + filterPredicate: () => true, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(screen.getByText('Custom Title')).toBeInTheDocument(); + expect(screen.queryByText('Custom Subtitle')).not.toBeInTheDocument(); + }); + it('should render title and subtitle', async () => { + const tabsConfig = [ + { + label: 'First Tab', + panels: [ + { + title: 'First Tab', + description: 'First Tab Description', + panelType: 'DocsCardGrid' as PanelType, + filterPredicate: () => true, + }, + ], + }, + ]; + + await renderInTestApp( + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(screen.getByText('Custom Title')).toBeInTheDocument(); + expect(screen.getByText('Custom Subtitle')).toBeInTheDocument(); + }); }); diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 5024eab5c3..781d4b337c 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -26,7 +26,7 @@ import { } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { DocsTable } from './Tables'; -import { DocsCardGrid, InfoCardGird } from './Grids'; +import { DocsCardGrid, InfoCardGrid } from './Grids'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { TechDocsIndexPage } from './TechDocsIndexPage'; @@ -47,7 +47,7 @@ const panels = { DocsTable: DocsTable, DocsCardGrid: DocsCardGrid, TechDocsIndexPage: TechDocsIndexPage, - InfoCardGird: InfoCardGird, + InfoCardGrid: InfoCardGrid, }; /** @@ -59,7 +59,7 @@ export type PanelType = | 'DocsCardGrid' | 'DocsTable' | 'TechDocsIndexPage' - | 'InfoCardGird'; + | 'InfoCardGrid'; /** * Type representing a TechDocsCustomHome panel. @@ -172,7 +172,8 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { error, } = useAsync(async () => { const response = await catalogApi.getEntities({ - filter: filter || { + filter: { + ...filter, [`metadata.annotations.${TECHDOCS_ANNOTATION}`]: CATALOG_FILTER_EXISTS, }, fields: [ @@ -184,7 +185,9 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { 'spec.type', ], }); - return response.items; + return response.items.filter((entity: Entity) => { + return !!entity.metadata.annotations?.[TECHDOCS_ANNOTATION]; + }); }); const currentTabConfig = tabsConfig[selectedTab]; From 998a8062d6c13c8e31a605adf0c4cc1fae649600 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 2 Dec 2024 20:58:01 -0800 Subject: [PATCH 03/18] add api report Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 36 +++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index c58a946287..04fd8dda39 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -12,6 +12,7 @@ import { Config } from '@backstage/config'; import { CSSProperties } from '@material-ui/styles/withStyles'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { EntityFilterQuery } from '@backstage/catalog-client'; import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; @@ -194,6 +195,21 @@ export const EntityTechdocsContent: (props: { children?: ReactNode; }) => JSX_2.Element; +// @public +export const InfoCardGrid: ( + props: InfoCardGridProps, +) => React_2.JSX.Element | null; + +// @public (undocumented) +export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; + +// @public +export type InfoCardGridProps = { + entities: Entity[] | undefined; + linkContent?: string | JSX.Element; + linkDest?: (entity: Entity) => string; +}; + // @public export const isTechDocsAvailable: (entity: Entity) => boolean; @@ -206,13 +222,19 @@ export interface PanelConfig { // (undocumented) panelCSS?: CSSProperties; // (undocumented) + panelProps?: Record; + // (undocumented) panelType: PanelType; // (undocumented) title: string; } // @public -export type PanelType = 'DocsCardGrid' | 'DocsTable'; +export type PanelType = + | 'DocsCardGrid' + | 'DocsTable' + | 'TechDocsIndexPage' + | 'InfoCardGrid'; // @public @deprecated export const Reader: ( @@ -293,6 +315,10 @@ export const TechDocsCustomHome: ( // @public export type TechDocsCustomHomeProps = { tabsConfig: TabsConfig; + filter?: EntityFilterQuery; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; // @public @deprecated (undocumented) @@ -309,6 +335,11 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; + showHeader?: boolean; + hideSupport?: boolean; + options?: TableOptions; + title?: string; + subtitle?: string; }; // @public @deprecated (undocumented) @@ -325,6 +356,9 @@ export const TechDocsPageWrapper: ( // @public export type TechDocsPageWrapperProps = { children?: React_2.ReactNode; + title?: string; + subtitle?: string; + hideSubtitle?: boolean; }; // @public From 1f40e6bf88379a5a8b0f529d1a65a6e5588c33d7 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 2 Dec 2024 22:35:58 -0800 Subject: [PATCH 04/18] add docs and clean up Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 85 +++++++++++++++++++ docs/features/techdocs/how-to-guides.md | 41 ++++++++- plugins/techdocs/report.api.md | 13 ++- .../home/components/Grids/InfoCardGrid.tsx | 12 +-- .../home/components/TechDocsCustomHome.tsx | 9 +- plugins/techdocs/src/home/components/index.ts | 1 + 6 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 .changeset/warm-masks-ring.md diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md new file mode 100644 index 0000000000..9d1d5e0c5a --- /dev/null +++ b/.changeset/warm-masks-ring.md @@ -0,0 +1,85 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Add optional props to `TechDocCustomHome` to allow for more flexibility: + +```tsx +import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; +//... + +const options = { emptyRowsWhenPaging: false }; +const linkDestination = (entity: Entity): string | undefined => { + return entity.metadata.annotations?.['external-docs']; +}; +const techDocsTabsConfig = [ + { + label: 'Recommended Documentation', + panels: [ + { + title: 'Golden Path', + description: 'Documentation about standards to follow', + panelType: 'DocsCardGrid', + panelProps: { showHeader: false, hideSupport: true }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('golden-path') ?? false, + }, + { + title: 'Recommended', + description: 'Useful documentation', + panelType: 'InfoCardGrid', + panelProps: { + showHeader: false, + hideSupport: true, + linkDestination: linkDestination, + }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + ], + }, + { + label: 'Browse All', + panels: [ + { + description: 'Browse all docs', + filterPredicate: filterEntity, + panelType: 'TechDocsIndexPage', + title: 'All', + panelProps: { showHeader: false, hideSupport: true, options: options }, + }, + ], + }, +]; + +const AppRoutes = () => { + + + } + /> + ; +}; +``` + +Add new Grid option called `InfoCardGrid` which is a more customizable card option for the Docs grid. + +```tsx + entity.metadata['external-docs']} +/> +``` + +Expose existing `CustomDocsPanel` so that it can be used independently if desired. diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index f8be6c9dd5..6bb4383674 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -137,6 +137,10 @@ Modify your `App.tsx` as follows: import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; //... +const options = { emptyRowsWhenPaging: false }; +const linkDestination = (entity: Entity): string | undefined => { + return entity.metadata.annotations?.['external-docs']; +}; const techDocsTabsConfig = [ { label: 'Recommended Documentation', @@ -145,18 +149,53 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', + panelProps: { showHeader: false, hideSupport: true }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('golden-path') ?? false, + }, + { + title: 'Recommended', + description: 'Useful documentation', + panelType: 'InfoCardGrid', + panelProps: { + showHeader: false, + hideSupport: true, + linkDestination: linkDestination, + }, filterPredicate: entity => entity?.metadata?.tags?.includes('recommended') ?? false, }, ], }, + { + label: 'Browse All', + panels: [ + { + description: 'Browse all docs', + filterPredicate: filterEntity, + panelType: 'TechDocsIndexPage', + title: 'All', + panelProps: { showHeader: false, hideSupport: true, options: options }, + }, + ], + }, ]; const AppRoutes = () => { } + element={ + + } /> ; }; diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index 04fd8dda39..ea7a1d7ea1 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -52,6 +52,17 @@ export type ContentStateTypes = /** There is only the latest and greatest content */ | 'CONTENT_FRESH'; +// @public +export const CustomDocsPanel: ({ + config, + entities, + index, +}: { + config: PanelConfig; + entities: Entity[]; + index: number; +}) => React_2.JSX.Element; + // @public export const DefaultTechDocsHome: ( props: TechDocsIndexPageProps, @@ -207,7 +218,7 @@ export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDest?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string; }; // @public diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 7a96c2af4c..3503194bc4 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -45,7 +45,7 @@ const useStyles = makeStyles( export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDest?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string; }; /** @@ -54,13 +54,13 @@ export type InfoCardGridProps = { * @public */ export const InfoCardGrid = (props: InfoCardGridProps) => { - const { entities, linkContent, linkDest } = props; + const { entities, linkContent, linkDestination } = props; const classes = useStyles(); const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); - const linkDestination = (entity: Entity) => - typeof linkDest === 'function' - ? linkDest(entity) + const linkRoute = (entity: Entity) => + typeof linkDestination === 'function' + ? linkDestination(entity) : getRouteToReaderPageFor({ namespace: toLowerMaybe( entity.metadata.namespace ?? 'default', @@ -84,7 +84,7 @@ export const InfoCardGrid = (props: InfoCardGridProps) => {
{entity?.metadata?.description}
diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 781d4b337c..b939d75893 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -92,7 +92,12 @@ export interface TabConfig { */ export type TabsConfig = TabConfig[]; -const CustomPanel = ({ +/** + * Component which can be used to render entities in a custom way. + * + * @public + */ +export const CustomDocsPanel = ({ config, entities, index, @@ -241,7 +246,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { /> {currentTabConfig.panels.map((config, index) => ( - Date: Tue, 3 Dec 2024 11:03:01 -0800 Subject: [PATCH 05/18] add more detail to changeset Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md index 9d1d5e0c5a..783f49474e 100644 --- a/.changeset/warm-masks-ring.md +++ b/.changeset/warm-masks-ring.md @@ -83,3 +83,35 @@ Add new Grid option called `InfoCardGrid` which is a more customizable card opti ``` Expose existing `CustomDocsPanel` so that it can be used independently if desired. + +```tsx +const panels: PanelConfig[] = [ + { + description: '', + filterPredicate: entity => {}, + panelType: 'InfoCardGrid', + title: 'Standards', + panelProps: { + hideSupport: true, + linkContent: 'Read more', + linkDestination: entity => {}, + }, + }, + { + description: '', + filterPredicate: entity => {}, + panelType: 'DocsCardGrid', + title: 'Contribute', + }, +]; +{ + panels.map((config, index) => ( + + )); +} +``` From 351952cb932b37b303362afd9efce092c652ce5a Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 3 Dec 2024 15:41:04 -0800 Subject: [PATCH 06/18] renaming Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 10 +++---- docs/features/techdocs/how-to-guides.md | 8 ++--- plugins/techdocs/report.api.md | 10 ++++--- .../home/components/DefaultTechDocsHome.tsx | 8 ++--- .../components/TechDocsCustomHome.test.tsx | 8 ++--- .../home/components/TechDocsCustomHome.tsx | 30 ++++++++++++++----- .../src/home/components/TechDocsIndexPage.tsx | 2 +- .../home/components/TechDocsPageWrapper.tsx | 6 ++-- 8 files changed, 49 insertions(+), 33 deletions(-) diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md index 783f49474e..aefbcf8013 100644 --- a/.changeset/warm-masks-ring.md +++ b/.changeset/warm-masks-ring.md @@ -20,7 +20,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, hideSupport: true }, + panelProps: { showHeader: false, showSupport: false }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -30,7 +30,7 @@ const techDocsTabsConfig = [ panelType: 'InfoCardGrid', panelProps: { showHeader: false, - hideSupport: true, + showSupport: false, linkDestination: linkDestination, }, filterPredicate: entity => @@ -46,7 +46,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, hideSupport: true, options: options }, + panelProps: { showHeader: false, showSupport: false, options: options }, }, ], }, @@ -60,7 +60,7 @@ const AppRoutes = () => { {}, }, diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 6bb4383674..22f48ccee1 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -149,7 +149,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, hideSupport: true }, + panelProps: { showHeader: false, showSupport: false }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -159,7 +159,7 @@ const techDocsTabsConfig = [ panelType: 'InfoCardGrid', panelProps: { showHeader: false, - hideSupport: true, + showSupport: false, linkDestination: linkDestination, }, filterPredicate: entity => @@ -175,7 +175,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, hideSupport: true, options: options }, + panelProps: { showHeader: false, showSupport: false, options: options }, }, ], }, @@ -189,7 +189,7 @@ const AppRoutes = () => { boolean) | string; // (undocumented) panelCSS?: CSSProperties; + // Warning: (ae-forgotten-export) The symbol "PanelProps" needs to be exported by the entry point index.d.ts + // // (undocumented) - panelProps?: Record; + panelProps?: PanelProps; // (undocumented) panelType: PanelType; // (undocumented) @@ -329,7 +331,7 @@ export type TechDocsCustomHomeProps = { filter?: EntityFilterQuery; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; // @public @deprecated (undocumented) @@ -347,7 +349,7 @@ export type TechDocsIndexPageProps = { actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; showHeader?: boolean; - hideSupport?: boolean; + showSupport?: boolean; options?: TableOptions; title?: string; subtitle?: string; @@ -369,7 +371,7 @@ export type TechDocsPageWrapperProps = { children?: React_2.ReactNode; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; // @public diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index f4a4e7862a..b3dbf08faa 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -51,17 +51,17 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { columns, actions, ownerPickerMode, - showHeader, + showHeader = true, options, title, subtitle, - hideSupport, + showSupport = true, } = props; - const Wrapper = showHeader !== false ? TechDocsPageWrapper : React.Fragment; + const Wrapper = showHeader ? TechDocsPageWrapper : React.Fragment; return ( - {hideSupport !== true && ( + {showSupport && ( Discover documentation in your ecosystem. diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index 66e01ce780..3056f4448c 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -127,7 +127,7 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should render SupportButton based on hideSupport prop', async () => { + it('should render SupportButton based on showSupport prop', async () => { const tabsConfig = [ { label: 'First Tab', @@ -137,7 +137,7 @@ describe('TechDocsCustomHome', () => { description: 'First Tab Description', panelType: 'DocsCardGrid' as PanelType, filterPredicate: () => true, - panelProps: { hideSupport: true }, + panelProps: { showSupport: false }, }, ], }, @@ -158,7 +158,7 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should hide subtitle when hideSubtitle is true', async () => { + it('should hide subtitle when showSubtitle is false', async () => { const tabsConfig = [ { label: 'First Tab', @@ -179,7 +179,7 @@ describe('TechDocsCustomHome', () => { tabsConfig={tabsConfig} title="Custom Title" subtitle="Custom Subtitle" - hideSubtitle + showSubtitle={false} /> , { diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index b939d75893..5067dd07cd 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -25,7 +25,7 @@ import { useEntityOwnership, } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; -import { DocsTable } from './Tables'; +import { DocsTable, DocsTableRow } from './Tables'; import { DocsCardGrid, InfoCardGrid } from './Grids'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { TechDocsIndexPage } from './TechDocsIndexPage'; @@ -38,6 +38,7 @@ import { WarningPanel, SupportButton, ContentHeader, + TableOptions, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { TECHDOCS_ANNOTATION } from '@backstage/plugin-techdocs-common'; @@ -61,6 +62,19 @@ export type PanelType = | 'TechDocsIndexPage' | 'InfoCardGrid'; +/** + * Type representing Panel props + * + * @public + */ +export interface PanelProps { + showHeader?: boolean; + showSupport?: boolean; + options?: TableOptions; + linkContent?: string | JSX.Element; + linkDestination?: (entity: Entity) => string; +} + /** * Type representing a TechDocsCustomHome panel. * @@ -72,7 +86,7 @@ export interface PanelConfig { panelType: PanelType; panelCSS?: CSSProperties; filterPredicate: ((entity: Entity) => boolean) | string; - panelProps?: Record; + panelProps?: PanelProps; } /** @@ -135,7 +149,7 @@ export const CustomDocsPanel = ({ <> {config.panelProps?.showHeader !== false && ( - {index === 0 && config.panelProps?.hideSupport !== true && ( + {index === 0 && config.panelProps?.showSupport !== false && ( Discover documentation in your ecosystem. @@ -163,11 +177,11 @@ export type TechDocsCustomHomeProps = { filter?: EntityFilterQuery; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { - const { tabsConfig, filter, title, subtitle, hideSubtitle } = props; + const { tabsConfig, filter, title, subtitle, showSubtitle = true } = props; const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); @@ -202,7 +216,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { @@ -216,7 +230,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { { ['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; showHeader?: boolean; - hideSupport?: boolean; + showSupport?: boolean; options?: TableOptions; title?: string; subtitle?: string; diff --git a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index eedac5ee95..84f8eee327 100644 --- a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -28,7 +28,7 @@ export type TechDocsPageWrapperProps = { children?: React.ReactNode; title?: string; subtitle?: string; - hideSubtitle?: boolean; + showSubtitle?: boolean; }; /** @@ -37,7 +37,7 @@ export type TechDocsPageWrapperProps = { * @public */ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { - const { children, title, subtitle, hideSubtitle } = props; + const { children, title, subtitle, showSubtitle = true } = props; const configApi = useApi(configApiRef); const generatedSubtitle = subtitle || @@ -48,7 +48,7 @@ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { return ( {children} From 0d7097b705a24613a361e76fb87a91da61adedf7 Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 3 Dec 2024 16:29:04 -0800 Subject: [PATCH 07/18] fix api-report warning Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 16 ++++++++++++++-- plugins/techdocs/src/home/components/index.ts | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index ec99ddfa31..3f9920d270 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -232,8 +232,6 @@ export interface PanelConfig { filterPredicate: ((entity: Entity) => boolean) | string; // (undocumented) panelCSS?: CSSProperties; - // Warning: (ae-forgotten-export) The symbol "PanelProps" needs to be exported by the entry point index.d.ts - // // (undocumented) panelProps?: PanelProps; // (undocumented) @@ -242,6 +240,20 @@ export interface PanelConfig { title: string; } +// @public +export interface PanelProps { + // (undocumented) + linkContent?: string | JSX.Element; + // (undocumented) + linkDestination?: (entity: Entity) => string; + // (undocumented) + options?: TableOptions; + // (undocumented) + showHeader?: boolean; + // (undocumented) + showSupport?: boolean; +} + // @public export type PanelType = | 'DocsCardGrid' diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts index 2d37ab9013..9c625817aa 100644 --- a/plugins/techdocs/src/home/components/index.ts +++ b/plugins/techdocs/src/home/components/index.ts @@ -20,6 +20,7 @@ export * from './DefaultTechDocsHome'; export type { PanelType, PanelConfig, + PanelProps, TabConfig, TabsConfig, TechDocsCustomHomeProps, From d3dc0ec77d03bccdf2e53cf1a720218dc1cb4c5d Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 10 Dec 2024 14:19:43 -0800 Subject: [PATCH 08/18] linkDestination can be undefined Signed-off-by: nikolar --- .../home/components/Grids/InfoCardGrid.tsx | 26 ++++++++++--------- .../home/components/TechDocsCustomHome.tsx | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 3503194bc4..31435debf7 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -45,7 +45,7 @@ const useStyles = makeStyles( export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; }; /** @@ -58,17 +58,19 @@ export const InfoCardGrid = (props: InfoCardGridProps) => { const classes = useStyles(); const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); - const linkRoute = (entity: Entity) => - typeof linkDestination === 'function' - ? linkDestination(entity) - : getRouteToReaderPageFor({ - namespace: toLowerMaybe( - entity.metadata.namespace ?? 'default', - config, - ), - kind: toLowerMaybe(entity.kind, config), - name: toLowerMaybe(entity.metadata.name, config), - }); + const linkRoute = (entity: Entity) => { + if (linkDestination) { + const destination = linkDestination(entity); + if (destination) { + return destination; + } + } + return getRouteToReaderPageFor({ + namespace: toLowerMaybe(entity.metadata.namespace ?? 'default', config), + kind: toLowerMaybe(entity.kind, config), + name: toLowerMaybe(entity.metadata.name, config), + }); + }; if (!entities) return null; return ( diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 5067dd07cd..2712667eae 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -72,7 +72,7 @@ export interface PanelProps { showSupport?: boolean; options?: TableOptions; linkContent?: string | JSX.Element; - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; } /** From 74314c8999f6e7b7bb0ec019bcd79d315ec5db9c Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 10 Dec 2024 17:01:38 -0800 Subject: [PATCH 09/18] fix api report Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index d4e7750435..073d843872 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -13,7 +13,8 @@ import { CSSProperties } from '@material-ui/styles/withStyles'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { EntityFilterQuery } from '@backstage/catalog-client'; -import { EntityListPagination, EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; +import { EntityListPagination } from '@backstage/plugin-catalog-react'; +import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; @@ -218,7 +219,7 @@ export type InfoCardGridClassKey = 'linkSpacer' | 'readMoreLink'; export type InfoCardGridProps = { entities: Entity[] | undefined; linkContent?: string | JSX.Element; - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; }; // @public @@ -245,7 +246,7 @@ export interface PanelProps { // (undocumented) linkContent?: string | JSX.Element; // (undocumented) - linkDestination?: (entity: Entity) => string; + linkDestination?: (entity: Entity) => string | undefined; // (undocumented) options?: TableOptions; // (undocumented) From 4c5df2c59fec667439b1d6d12da7d216ceaad0c2 Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 17 Dec 2024 17:54:01 -0800 Subject: [PATCH 10/18] update review comments Signed-off-by: nikolar --- .../home/components/Grids/InfoCardGrid.tsx | 38 +++++++++---------- .../home/components/TechDocsCustomHome.tsx | 4 +- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 31435debf7..cba00076b1 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -72,28 +72,26 @@ export const InfoCardGrid = (props: InfoCardGridProps) => { }); }; - if (!entities) return null; + if (!entities || !entities?.length) return null; return ( - {!entities?.length - ? null - : entities.map(entity => ( - -
{entity?.metadata?.description}
-
- - {linkContent || 'Read Docs'} - - - ))} + {entities.map(entity => ( + +
{entity?.metadata?.description}
+
+ + {linkContent || 'Read Docs'} + + + ))} ); }; diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 3c213d827c..2dcf6aae80 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -148,9 +148,9 @@ export const CustomDocsPanel = ({ return ( <> - {config.panelProps?.showHeader !== false && ( + {!!config.panelProps?.showHeader && ( - {index === 0 && config.panelProps?.showSupport !== false && ( + {index === 0 && !!config.panelProps?.showSupport && ( Discover documentation in your ecosystem. From ba17e05f6ccb552e9f1327fa8aeb8088af6ff5c8 Mon Sep 17 00:00:00 2001 From: nikolar Date: Wed, 18 Dec 2024 11:52:23 -0800 Subject: [PATCH 11/18] fix error with suggestion for showHeader and showSupport Signed-off-by: nikolar --- plugins/techdocs/src/home/components/TechDocsCustomHome.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index 2dcf6aae80..b8998b8655 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -148,9 +148,9 @@ export const CustomDocsPanel = ({ return ( <> - {!!config.panelProps?.showHeader && ( + {(config.panelProps?.showHeader ?? true) && ( - {index === 0 && !!config.panelProps?.showSupport && ( + {index === 0 && (config.panelProps?.showSupport ?? true) && ( Discover documentation in your ecosystem. From 0fc1936db42bd724f1c93e6df9aa3edc4798913e Mon Sep 17 00:00:00 2001 From: nikolar Date: Fri, 20 Dec 2024 17:39:44 -0800 Subject: [PATCH 12/18] fix custom props Signed-off-by: nikolar --- .changeset/warm-masks-ring.md | 17 ++-- docs/features/techdocs/how-to-guides.md | 10 +-- .../home/components/DefaultTechDocsHome.tsx | 31 +++---- .../components/TechDocsCustomHome.test.tsx | 81 +++---------------- .../home/components/TechDocsCustomHome.tsx | 50 +++++------- .../src/home/components/TechDocsIndexPage.tsx | 8 +- .../home/components/TechDocsPageWrapper.tsx | 34 ++++---- plugins/techdocs/src/index.ts | 2 + 8 files changed, 83 insertions(+), 150 deletions(-) diff --git a/.changeset/warm-masks-ring.md b/.changeset/warm-masks-ring.md index aefbcf8013..bfb521a005 100644 --- a/.changeset/warm-masks-ring.md +++ b/.changeset/warm-masks-ring.md @@ -20,7 +20,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, showSupport: false }, + panelProps: { CustomHeader: () => }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -29,8 +29,7 @@ const techDocsTabsConfig = [ description: 'Useful documentation', panelType: 'InfoCardGrid', panelProps: { - showHeader: false, - showSupport: false, + CustomHeader: () => linkDestination: linkDestination, }, filterPredicate: entity => @@ -46,7 +45,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, showSupport: false, options: options }, + panelProps: { PageWrapper: React.Fragment, CustomHeader: React.Fragment, options: options }, }, ], }, @@ -59,12 +58,11 @@ const AppRoutes = () => { element={ ) => ({children})} /> } /> @@ -92,10 +90,9 @@ const panels: PanelConfig[] = [ panelType: 'InfoCardGrid', title: 'Standards', panelProps: { - showSupport: false, - linkContent: 'Read more', - linkDestination: entity => {}, - }, + CustomHeader: () => + linkDestination: linkDestination, + }, }, { description: '', diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index ea1ac7a372..5a9b8ba140 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -149,7 +149,7 @@ const techDocsTabsConfig = [ title: 'Golden Path', description: 'Documentation about standards to follow', panelType: 'DocsCardGrid', - panelProps: { showHeader: false, showSupport: false }, + panelProps: { CustomHeader: () => }, filterPredicate: entity => entity?.metadata?.tags?.includes('golden-path') ?? false, }, @@ -158,8 +158,7 @@ const techDocsTabsConfig = [ description: 'Useful documentation', panelType: 'InfoCardGrid', panelProps: { - showHeader: false, - showSupport: false, + CustomHeader: () => linkDestination: linkDestination, }, filterPredicate: entity => @@ -175,7 +174,7 @@ const techDocsTabsConfig = [ filterPredicate: filterEntity, panelType: 'TechDocsIndexPage', title: 'All', - panelProps: { showHeader: false, showSupport: false, options: options }, + panelProps: { PageWrapper: React.Fragment, CustomHeader: React.Fragment, options: options }, }, ], }, @@ -188,12 +187,11 @@ const AppRoutes = () => { element={ ) => ({children})} /> } /> diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index 2f79455ad3..f44bc8eca0 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -51,24 +51,27 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { columns, actions, ownerPickerMode, - showHeader = true, - options, - title, - subtitle, - showSupport = true, pagination, + options, + PageWrapper, + CustomHeader, } = props; - const Wrapper = showHeader ? TechDocsPageWrapper : React.Fragment; + const Wrapper: React.FC<{ + children: React.ReactNode; + title?: string; + subtitle?: string; + }> = PageWrapper ? PageWrapper : TechDocsPageWrapper; + const Header: React.FC = + CustomHeader || + (() => ( + + Discover documentation in your ecosystem. + + )); return ( - + - {showSupport && ( - - - Discover documentation in your ecosystem. - - - )} +
diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index a366cc7269..bd9725d9a0 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -19,6 +19,7 @@ import { starredEntitiesApiRef, MockStarredEntitiesApi, } from '@backstage/plugin-catalog-react'; +import { PageWithHeader } from '@backstage/core-components'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; @@ -103,7 +104,7 @@ describe('TechDocsCustomHome', () => { await screen.findByText('Second Tab Description'), ).toBeInTheDocument(); }); - it('should render ContentHeader based on showHeader prop', async () => { + it('should render ContentHeader based on CustomHeader prop', async () => { const tabsConfig = [ { label: 'First Tab', @@ -112,7 +113,7 @@ describe('TechDocsCustomHome', () => { title: 'First Tab', description: 'First Tab Description', panelType: 'DocsCardGrid' as PanelType, - panelProps: { showHeader: false }, + panelProps: { CustomHeader: React.Fragment }, filterPredicate: () => true, }, ], @@ -134,71 +135,6 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should render SupportButton based on showSupport prop', async () => { - const tabsConfig = [ - { - label: 'First Tab', - panels: [ - { - title: 'First Tab', - description: 'First Tab Description', - panelType: 'DocsCardGrid' as PanelType, - filterPredicate: () => true, - panelProps: { showSupport: false }, - }, - ], - }, - ]; - - await renderInTestApp( - - - , - { - mountedRoutes: { - '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, - }, - }, - ); - - expect( - screen.queryByText('Discover documentation in your ecosystem.'), - ).not.toBeInTheDocument(); - }); - it('should hide subtitle when showSubtitle is false', async () => { - const tabsConfig = [ - { - label: 'First Tab', - panels: [ - { - title: 'First Tab', - description: 'First Tab Description', - panelType: 'DocsCardGrid' as PanelType, - filterPredicate: () => true, - }, - ], - }, - ]; - - await renderInTestApp( - - - , - { - mountedRoutes: { - '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, - }, - }, - ); - - expect(screen.getByText('Custom Title')).toBeInTheDocument(); - expect(screen.queryByText('Custom Subtitle')).not.toBeInTheDocument(); - }); it('should render title and subtitle', async () => { const tabsConfig = [ { @@ -218,8 +154,15 @@ describe('TechDocsCustomHome', () => { ) => ( + + {children} + + )} /> , { diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index b8998b8655..e1c5a93161 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -69,11 +69,11 @@ export type PanelType = * @public */ export interface PanelProps { - showHeader?: boolean; - showSupport?: boolean; options?: TableOptions; linkContent?: string | JSX.Element; linkDestination?: (entity: Entity) => string | undefined; + PageWrapper?: React.FC; + CustomHeader?: React.FC; } /** @@ -146,17 +146,21 @@ export const CustomDocsPanel = ({ ); }); + const Header: React.FC = + config.panelProps?.CustomHeader || + (() => ( + + {index === 0 ? ( + + Discover documentation in your ecosystem. + + ) : null} + + )); + return ( <> - {(config.panelProps?.showHeader ?? true) && ( - - {index === 0 && (config.panelProps?.showSupport ?? true) && ( - - Discover documentation in your ecosystem. - - )} - - )} +
{ - const { tabsConfig, filter, title, subtitle, showSubtitle = true } = props; + const { tabsConfig, filter, CustomPageWrapper } = props; const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); @@ -216,11 +218,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { if (loading) { return ( - + @@ -230,11 +228,7 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { if (error) { return ( - + { } return ( - + setSelectedTab(index)} diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx index 50388519ba..221839c492 100644 --- a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx @@ -39,12 +39,10 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; - showHeader?: boolean; - showSupport?: boolean; - options?: TableOptions; - title?: string; - subtitle?: string; pagination?: EntityListPagination; + options?: TableOptions; + PageWrapper?: React.FC; + CustomHeader?: React.FC; }; export const TechDocsIndexPage = (props: TechDocsIndexPageProps) => { diff --git a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index 84f8eee327..c07876f7f9 100644 --- a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -26,9 +26,7 @@ import { useApi, configApiRef } from '@backstage/core-plugin-api'; */ export type TechDocsPageWrapperProps = { children?: React.ReactNode; - title?: string; - subtitle?: string; - showSubtitle?: boolean; + CustomPageWrapper?: React.FC<{ children?: React.ReactNode }>; }; /** @@ -37,21 +35,25 @@ export type TechDocsPageWrapperProps = { * @public */ export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { - const { children, title, subtitle, showSubtitle = true } = props; + const { children, CustomPageWrapper } = props; const configApi = useApi(configApiRef); - const generatedSubtitle = - subtitle || - `Documentation available in ${ - configApi.getOptionalString('organization.name') ?? 'Backstage' - }`; + const generatedSubtitle = `Documentation available in ${ + configApi.getOptionalString('organization.name') ?? 'Backstage' + }`; return ( - - {children} - + <> + {CustomPageWrapper ? ( + {children} + ) : ( + + {children} + + )} + ); }; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index 522bcd0c99..713efc17c6 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -67,3 +67,5 @@ export type { DeprecatedTechDocsEntityMetadata as TechDocsEntityMetadata, DeprecatedTechDocsMetadata as TechDocsMetadata, }; + +export * from './overridableComponents'; From d58d34ee0a39038eb72e09a7c2eb71aa42d6656a Mon Sep 17 00:00:00 2001 From: nikolar Date: Fri, 20 Dec 2024 17:46:02 -0800 Subject: [PATCH 13/18] fix api report Signed-off-by: nikolar --- plugins/techdocs/report.api.md | 38 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/plugins/techdocs/report.api.md b/plugins/techdocs/report.api.md index 073d843872..9fb7c151b7 100644 --- a/plugins/techdocs/report.api.md +++ b/plugins/techdocs/report.api.md @@ -18,12 +18,14 @@ import { EntityOwnerPickerProps } from '@backstage/plugin-catalog-react'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { Overrides } from '@material-ui/core/styles/overrides'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { ResultHighlight } from '@backstage/plugin-search-common'; import { RouteRef } from '@backstage/core-plugin-api'; import { SearchResultListItemExtensionProps } from '@backstage/plugin-search-react'; +import { StyleRules } from '@material-ui/core/styles/withStyles'; import { SyncResult as SyncResult_2 } from '@backstage/plugin-techdocs-react'; import { TableColumn } from '@backstage/core-components'; import { TableOptions } from '@backstage/core-components'; @@ -36,6 +38,18 @@ import { ThemeOptions } from '@material-ui/core/styles'; import { ToolbarProps } from '@material-ui/core/Toolbar'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; +// @public (undocumented) +export type BackstageOverrides = Overrides & { + [Name in keyof CatalogReactComponentsNameToClassKey]?: Partial< + StyleRules + >; +}; + +// @public (undocumented) +export type CatalogReactComponentsNameToClassKey = { + BackstageInfoCardGrid: InfoCardGridClassKey; +}; + // @public export type ContentStateTypes = /** There is nothing to display but a loading indicator */ @@ -243,6 +257,8 @@ export interface PanelConfig { // @public export interface PanelProps { + // (undocumented) + CustomHeader?: React_2.FC; // (undocumented) linkContent?: string | JSX.Element; // (undocumented) @@ -250,9 +266,7 @@ export interface PanelProps { // (undocumented) options?: TableOptions; // (undocumented) - showHeader?: boolean; - // (undocumented) - showSupport?: boolean; + PageWrapper?: React_2.FC; } // @public @@ -342,9 +356,7 @@ export const TechDocsCustomHome: ( export type TechDocsCustomHomeProps = { tabsConfig: TabsConfig; filter?: EntityFilterQuery; - title?: string; - subtitle?: string; - showSubtitle?: boolean; + CustomPageWrapper?: React_2.FC; }; // @public @deprecated (undocumented) @@ -361,12 +373,10 @@ export type TechDocsIndexPageProps = { columns?: TableColumn[]; actions?: TableProps['actions']; ownerPickerMode?: EntityOwnerPickerProps['mode']; - showHeader?: boolean; - showSupport?: boolean; - options?: TableOptions; - title?: string; - subtitle?: string; pagination?: EntityListPagination; + options?: TableOptions; + PageWrapper?: React_2.FC; + CustomHeader?: React_2.FC; }; // @public @deprecated (undocumented) @@ -383,9 +393,9 @@ export const TechDocsPageWrapper: ( // @public export type TechDocsPageWrapperProps = { children?: React_2.ReactNode; - title?: string; - subtitle?: string; - showSubtitle?: boolean; + CustomPageWrapper?: React_2.FC<{ + children?: React_2.ReactNode; + }>; }; // @public From f1a09dd40ef8486278aabcbe1918d9b5d7a910ae Mon Sep 17 00:00:00 2001 From: nikolar Date: Fri, 20 Dec 2024 22:29:16 -0800 Subject: [PATCH 14/18] :broom: Signed-off-by: nikolar --- plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx | 2 -- .../techdocs/src/home/components/TechDocsCustomHome.test.tsx | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index f44bc8eca0..9b6ee490a4 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -58,8 +58,6 @@ export const DefaultTechDocsHome = (props: TechDocsIndexPageProps) => { } = props; const Wrapper: React.FC<{ children: React.ReactNode; - title?: string; - subtitle?: string; }> = PageWrapper ? PageWrapper : TechDocsPageWrapper; const Header: React.FC = CustomHeader || diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx index bd9725d9a0..bf3795d137 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx @@ -135,7 +135,7 @@ describe('TechDocsCustomHome', () => { screen.queryByText('Discover documentation in your ecosystem.'), ).not.toBeInTheDocument(); }); - it('should render title and subtitle', async () => { + it('should render CustomPageWrapper', async () => { const tabsConfig = [ { label: 'First Tab', From 85097cf492ec14d01388a6bb1c8f63bd59b47bb7 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 6 Jan 2025 12:14:10 -0800 Subject: [PATCH 15/18] clarify test actions Signed-off-by: nikolar --- .../components/Grids/InfoCardGrid.test.tsx | 26 ------------------- .../home/components/Grids/InfoCardGrid.tsx | 6 ++--- .../components/TechDocsCustomHome.test.tsx | 15 +++++++---- 3 files changed, 13 insertions(+), 34 deletions(-) diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx index dcb773120e..a2d95950f9 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx @@ -64,32 +64,6 @@ describe('Entity Info Card Grid', () => { expect(await screen.findByText('TestTitle2')).toBeInTheDocument(); }); - it('should handle missing data gracefully', async () => { - await renderInTestApp( - , - { - mountedRoutes: { - '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, - }, - }, - ); - - expect(await screen.findByText('testName')).toBeInTheDocument(); - }); - it('should render links correctly', async () => { await renderInTestApp( { title: 'First Tab', description: 'First Tab Description', panelType: 'DocsCardGrid' as PanelType, - panelProps: { CustomHeader: React.Fragment }, + panelProps: { + CustomHeader: () => ( + + ), + }, filterPredicate: () => true, }, ], @@ -131,9 +138,7 @@ describe('TechDocsCustomHome', () => { }, ); - expect( - screen.queryByText('Discover documentation in your ecosystem.'), - ).not.toBeInTheDocument(); + expect(screen.getByText('Custom Header')).toBeInTheDocument(); }); it('should render CustomPageWrapper', async () => { const tabsConfig = [ From 7dcbc669b7b9e62c9647f718e5078dd0d7adab74 Mon Sep 17 00:00:00 2001 From: nikolar Date: Mon, 13 Jan 2025 11:25:11 -0800 Subject: [PATCH 16/18] clarify howtoguide Signed-off-by: nikolar --- docs/features/techdocs/how-to-guides.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 5a9b8ba140..5a9044842f 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -179,7 +179,12 @@ const techDocsTabsConfig = [ ], }, ]; - +const docsFilter = { + kind: ['Location', 'Resource', 'Component'], + 'metadata.annotations.featured-docs': CATALOG_FILTER_EXISTS, +} +const customPageWrapper = ({ children }: React.PropsWithChildren<{}>) => + ({children}) const AppRoutes = () => { { element={ ) => ({children})} + filter={docsFilter} + CustomPageWrapper={customPageWrapper} /> } /> From be04f3c91c8aab3f91e526d55e3e8f7596a0e4c5 Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 14 Jan 2025 17:55:56 -0800 Subject: [PATCH 17/18] use entityPresentationApi for title Signed-off-by: nikolar --- .../home/components/Grids/DocsCardGrid.tsx | 29 +++++++++++++-- .../home/components/Grids/InfoCardGrid.tsx | 36 ++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx index d2947b5f64..2d5d5fa7c0 100644 --- a/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx @@ -16,12 +16,18 @@ import { rootDocsRouteRef } from '../../../routes'; import { toLowerMaybe } from '../../../helpers'; -import { Entity } from '@backstage/catalog-model'; +import useAsync from 'react-use/esm/useAsync'; +import { + EntityRefPresentationSnapshot, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api'; import { LinkButton, ItemCardGrid, ItemCardHeader, + Progress, } from '@backstage/core-components'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; @@ -47,6 +53,22 @@ export const DocsCardGrid = (props: DocsCardGridProps) => { const { entities } = props; const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); + const entityPresentationApi = useApi(entityPresentationApiRef); + const { value: entityRefToPresentation, loading } = useAsync(async () => { + return new Map( + await Promise.all( + entities?.map(async entity => { + const presentation = await entityPresentationApi.forEntity(entity) + .promise; + return [stringifyEntityRef(entity), presentation] as [ + string, + EntityRefPresentationSnapshot, + ]; + }) || [], + ), + ); + }); + if (loading) return ; if (!entities) return null; return ( @@ -56,7 +78,10 @@ export const DocsCardGrid = (props: DocsCardGridProps) => { {entity.metadata.description} diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx index 769ec8e145..5a71710174 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.tsx @@ -15,9 +15,19 @@ */ import React from 'react'; -import { Entity } from '@backstage/catalog-model'; +import useAsync from 'react-use/esm/useAsync'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api'; -import { ItemCardGrid, InfoCard, Link } from '@backstage/core-components'; +import { + ItemCardGrid, + InfoCard, + Link, + Progress, +} from '@backstage/core-components'; +import { + EntityRefPresentationSnapshot, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; import { makeStyles } from '@material-ui/core/styles'; import { rootDocsRouteRef } from '../../../routes'; import { toLowerMaybe } from '../../../helpers'; @@ -71,7 +81,22 @@ export const InfoCardGrid = (props: InfoCardGridProps) => { name: toLowerMaybe(entity.metadata.name, config), }); }; - + const entityPresentationApi = useApi(entityPresentationApiRef); + const { value: entityRefToPresentation, loading } = useAsync(async () => { + return new Map( + await Promise.all( + entities?.map(async entity => { + const presentation = await entityPresentationApi.forEntity(entity) + .promise; + return [stringifyEntityRef(entity), presentation] as [ + string, + EntityRefPresentationSnapshot, + ]; + }) || [], + ), + ); + }); + if (loading) return ; if (!entities || !entities?.length) return null; return ( @@ -79,7 +104,10 @@ export const InfoCardGrid = (props: InfoCardGridProps) => {
{entity?.metadata?.description}
From 301a40f00ff363efdf8550575ab74b1cabc6882f Mon Sep 17 00:00:00 2001 From: nikolar Date: Tue, 14 Jan 2025 18:54:16 -0800 Subject: [PATCH 18/18] fix tests Signed-off-by: nikolar --- plugins/techdocs/package.json | 1 + .../home/components/Grids/DocsCardGrid.tsx | 29 +-- .../components/Grids/InfoCardGrid.test.tsx | 179 ++++++++++-------- yarn.lock | 1 + 4 files changed, 107 insertions(+), 103 deletions(-) diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index b9cc59fd10..50aeedd02d 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -92,6 +92,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-techdocs-module-addons-contrib": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", diff --git a/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx index 2d5d5fa7c0..d2947b5f64 100644 --- a/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx @@ -16,18 +16,12 @@ import { rootDocsRouteRef } from '../../../routes'; import { toLowerMaybe } from '../../../helpers'; -import useAsync from 'react-use/esm/useAsync'; -import { - EntityRefPresentationSnapshot, - entityPresentationApiRef, -} from '@backstage/plugin-catalog-react'; -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api'; import { LinkButton, ItemCardGrid, ItemCardHeader, - Progress, } from '@backstage/core-components'; import Card from '@material-ui/core/Card'; import CardActions from '@material-ui/core/CardActions'; @@ -53,22 +47,6 @@ export const DocsCardGrid = (props: DocsCardGridProps) => { const { entities } = props; const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); - const entityPresentationApi = useApi(entityPresentationApiRef); - const { value: entityRefToPresentation, loading } = useAsync(async () => { - return new Map( - await Promise.all( - entities?.map(async entity => { - const presentation = await entityPresentationApi.forEntity(entity) - .promise; - return [stringifyEntityRef(entity), presentation] as [ - string, - EntityRefPresentationSnapshot, - ]; - }) || [], - ), - ); - }); - if (loading) return ; if (!entities) return null; return ( @@ -78,10 +56,7 @@ export const DocsCardGrid = (props: DocsCardGridProps) => { {entity.metadata.description} diff --git a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx index a2d95950f9..e1e3ce1511 100644 --- a/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx +++ b/plugins/techdocs/src/home/components/Grids/InfoCardGrid.test.tsx @@ -14,45 +14,66 @@ * limitations under the License. */ -import { renderInTestApp } from '@backstage/test-utils'; +import { TestApiProvider, renderInTestApp } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; +import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; +import { DefaultEntityPresentationApi } from '@backstage/plugin-catalog'; +import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; + import React from 'react'; import { rootDocsRouteRef } from '../../../routes'; import { InfoCardGrid } from './InfoCardGrid'; describe('Entity Info Card Grid', () => { + const catalogApi = catalogApiMock(); + let Wrapper: React.ComponentType>; + beforeEach(() => { jest.resetAllMocks(); + Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + + ); }); it('should render multiple entities', async () => { await renderInTestApp( - + , + ]} + /> + , { mountedRoutes: { '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, @@ -66,32 +87,34 @@ describe('Entity Info Card Grid', () => { it('should render links correctly', async () => { await renderInTestApp( - + , + ]} + /> + , { mountedRoutes: { '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, @@ -112,21 +135,23 @@ describe('Entity Info Card Grid', () => { it('should render entity title if available', async () => { await renderInTestApp( - + , + ]} + /> + , { mountedRoutes: { '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, @@ -139,20 +164,22 @@ describe('Entity Info Card Grid', () => { it('should render entity name if title is not available', async () => { await renderInTestApp( - + , + ]} + /> + , { mountedRoutes: { '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, diff --git a/yarn.lock b/yarn.lock index 16dbd0c921..b38c9eb061 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8694,6 +8694,7 @@ __metadata: "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" "@backstage/plugin-auth-react": "workspace:^" + "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-search-common": "workspace:^" "@backstage/plugin-search-react": "workspace:^"