diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index fad0300680..dd2fe84a87 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -37,6 +37,7 @@ app: # Entity page content - entity-content:api-docs/definition + - entity-content:api-docs/apis - entity-content:techdocs - entity-content:azure-devops/pipelines - entity-content:azure-devops/pull-requests diff --git a/plugins/api-docs/api-report-alpha.md b/plugins/api-docs/api-report-alpha.md new file mode 100644 index 0000000000..1d22283884 --- /dev/null +++ b/plugins/api-docs/api-report-alpha.md @@ -0,0 +1,22 @@ +## API Report File for "@backstage/plugin-api-docs" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +const _default: BackstagePlugin< + { + root: RouteRef; + }, + { + registerApi: ExternalRouteRef; + } +>; +export default _default; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx index fcb536e245..98655d56ba 100644 --- a/plugins/api-docs/src/alpha.tsx +++ b/plugins/api-docs/src/alpha.tsx @@ -134,7 +134,7 @@ const ApiDocsDefinitionEntityContent = createEntityContentExtension({ name: 'definition', defaultPath: '/defintion', defaultTitle: 'Definition', - filter: 'is:api', + filter: 'kind:api', loader: async () => import('./components/ApiDefinitionCard').then(m => compatWrapper( diff --git a/plugins/catalog/src/alpha/EntityOverviewPage.tsx b/plugins/catalog/src/alpha/EntityOverviewPage.tsx index 932e13d962..85c318cf3f 100644 --- a/plugins/catalog/src/alpha/EntityOverviewPage.tsx +++ b/plugins/catalog/src/alpha/EntityOverviewPage.tsx @@ -17,8 +17,8 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import Grid from '@material-ui/core/Grid'; -import React, { useMemo } from 'react'; -import { parseFilterExpression } from './filter/parseFilterExpression'; +import React from 'react'; +import { FilterWrapper } from './filter/FilterWrapper'; interface EntityOverviewPageProps { cards: Array<{ @@ -28,80 +28,12 @@ interface EntityOverviewPageProps { }>; } -// Keeps track of what filter expression strings that we've seen duplicates of -// with functions, or which emitted parsing errors for so far -const seenParseErrorExpressionStrings = new Set(); -const seenDuplicateExpressionStrings = new Set(); - -// Given an optional filter function and an optional filter expression, make -// sure that at most one of them was given, and return a filter function that -// does the right thing. -function buildFilterFn( - filterFunction?: (entity: Entity) => boolean, - filterExpression?: string, -): (entity: Entity) => boolean { - if ( - filterFunction && - filterExpression && - !seenDuplicateExpressionStrings.has(filterExpression) - ) { - // eslint-disable-next-line no-console - console.warn( - `Duplicate entity filter methods found, both '${filterExpression}' as well as a callback function, which is not permitted - using the callback`, - ); - seenDuplicateExpressionStrings.add(filterExpression); - } - - const filter = filterFunction || filterExpression; - if (!filter) { - return () => true; - } else if (typeof filter === 'function') { - return subject => filter(subject); - } - - const result = parseFilterExpression(filter); - if ( - result.expressionParseErrors.length && - !seenParseErrorExpressionStrings.has(filter) - ) { - // eslint-disable-next-line no-console - console.warn( - `Error(s) in entity filter expression '${filter}'`, - result.expressionParseErrors, - ); - seenParseErrorExpressionStrings.add(filter); - } - - return result.filterFn; -} - -// Handles the memoized parsing of filter expressions for each card -function CardWrapper(props: { - entity: Entity; - element: React.JSX.Element; - filterFunction?: (entity: Entity) => boolean; - filterExpression?: string; -}) { - const { entity, element, filterFunction, filterExpression } = props; - - const filterFn = useMemo( - () => buildFilterFn(filterFunction, filterExpression), - [filterFunction, filterExpression], - ); - - return filterFn(entity) ? ( - - {element} - - ) : null; -} - export function EntityOverviewPage(props: EntityOverviewPageProps) { const { entity } = useEntity(); return ( {props.cards.map((card, index) => ( - + ))} ); diff --git a/plugins/catalog/src/alpha/filter/FilterWrapper.tsx b/plugins/catalog/src/alpha/filter/FilterWrapper.tsx new file mode 100644 index 0000000000..e0e74fe481 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/FilterWrapper.tsx @@ -0,0 +1,88 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +import Grid from '@material-ui/core/Grid'; +import React, { useMemo } from 'react'; +import { parseFilterExpression } from './parseFilterExpression'; + +// Keeps track of what filter expression strings that we've seen duplicates of +// with functions, or which emitted parsing errors for so far +const seenParseErrorExpressionStrings = new Set(); +const seenDuplicateExpressionStrings = new Set(); + +// Given an optional filter function and an optional filter expression, make +// sure that at most one of them was given, and return a filter function that +// does the right thing. +export function buildFilterFn( + filterFunction?: (entity: Entity) => boolean, + filterExpression?: string, +): (entity: Entity) => boolean { + if ( + filterFunction && + filterExpression && + !seenDuplicateExpressionStrings.has(filterExpression) + ) { + // eslint-disable-next-line no-console + console.warn( + `Duplicate entity filter methods found, both '${filterExpression}' as well as a callback function, which is not permitted - using the callback`, + ); + seenDuplicateExpressionStrings.add(filterExpression); + } + + const filter = filterFunction || filterExpression; + if (!filter) { + return () => true; + } else if (typeof filter === 'function') { + return subject => filter(subject); + } + + const result = parseFilterExpression(filter); + if ( + result.expressionParseErrors.length && + !seenParseErrorExpressionStrings.has(filter) + ) { + // eslint-disable-next-line no-console + console.warn( + `Error(s) in entity filter expression '${filter}'`, + result.expressionParseErrors, + ); + seenParseErrorExpressionStrings.add(filter); + } + + return result.filterFn; +} + +// Handles the memoized parsing of filter expressions +export function FilterWrapper(props: { + entity: Entity; + element: React.JSX.Element; + filterFunction?: (entity: Entity) => boolean; + filterExpression?: string; +}) { + const { entity, element, filterFunction, filterExpression } = props; + + const filterFn = useMemo( + () => buildFilterFn(filterFunction, filterExpression), + [filterFunction, filterExpression], + ); + + return filterFn(entity) ? ( + + {element} + + ) : null; +} diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index 3f40796da9..15f7d6cc91 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -31,6 +31,7 @@ import { import { catalogExtensionData } from '@backstage/plugin-catalog-react/alpha'; import { rootRouteRef } from '../routes'; import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl'; +import { buildFilterFn } from './filter/FilterWrapper'; export const catalogPage = createPageExtension({ defaultPath: '/catalog', @@ -57,24 +58,29 @@ export const catalogEntityPage = createPageExtension({ path: coreExtensionData.routePath, routeRef: coreExtensionData.routeRef.optional(), title: catalogExtensionData.entityContentTitle, + filterFunction: catalogExtensionData.entityFilterFunction.optional(), + filterExpression: catalogExtensionData.entityFilterExpression.optional(), }), }, loader: async ({ inputs }) => { const { EntityLayout } = await import('../components/EntityLayout'); const Component = () => { + const { entity, ...rest } = useEntityFromUrl(); return ( - - - {inputs.contents.map(content => ( - - {content.output.element} - - ))} - + + {entity ? ( + + {inputs.contents + .filter(({ output: { filterFunction, filterExpression } }) => + buildFilterFn(filterFunction, filterExpression)(entity), + ) + .map(({ output: { path, title, element } }) => ( + + {element} + + ))} + + ) : null} ); };