fix(catalog): entity cards tab filtering
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<undefined>;
|
||||
},
|
||||
{
|
||||
registerApi: ExternalRouteRef<undefined, true>;
|
||||
}
|
||||
>;
|
||||
export default _default;
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -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(
|
||||
|
||||
@@ -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<string>();
|
||||
const seenDuplicateExpressionStrings = new Set<string>();
|
||||
|
||||
// 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) ? (
|
||||
<Grid item md={6} xs={12}>
|
||||
{element}
|
||||
</Grid>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export function EntityOverviewPage(props: EntityOverviewPageProps) {
|
||||
const { entity } = useEntity();
|
||||
return (
|
||||
<Grid container spacing={3} alignItems="stretch">
|
||||
{props.cards.map((card, index) => (
|
||||
<CardWrapper key={index} entity={entity} {...card} />
|
||||
<FilterWrapper key={index} entity={entity} {...card} />
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
|
||||
@@ -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<string>();
|
||||
const seenDuplicateExpressionStrings = new Set<string>();
|
||||
|
||||
// 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) ? (
|
||||
<Grid item md={6} xs={12}>
|
||||
{element}
|
||||
</Grid>
|
||||
) : null;
|
||||
}
|
||||
@@ -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 (
|
||||
<AsyncEntityProvider {...useEntityFromUrl()}>
|
||||
<EntityLayout>
|
||||
{inputs.contents.map(content => (
|
||||
<EntityLayout.Route
|
||||
key={content.output.path}
|
||||
path={content.output.path}
|
||||
title={content.output.title}
|
||||
>
|
||||
{content.output.element}
|
||||
</EntityLayout.Route>
|
||||
))}
|
||||
</EntityLayout>
|
||||
<AsyncEntityProvider entity={entity} {...rest}>
|
||||
{entity ? (
|
||||
<EntityLayout>
|
||||
{inputs.contents
|
||||
.filter(({ output: { filterFunction, filterExpression } }) =>
|
||||
buildFilterFn(filterFunction, filterExpression)(entity),
|
||||
)
|
||||
.map(({ output: { path, title, element } }) => (
|
||||
<EntityLayout.Route key={path} path={path} title={title}>
|
||||
{element}
|
||||
</EntityLayout.Route>
|
||||
))}
|
||||
</EntityLayout>
|
||||
) : null}
|
||||
</AsyncEntityProvider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user