diff --git a/.changeset/stale-jokes-hunt.md b/.changeset/stale-jokes-hunt.md deleted file mode 100644 index 1e78a3a446..0000000000 --- a/.changeset/stale-jokes-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-common': minor ---- - -Added a `parseFilterExpression` function to interpret visibility filters, exported from the `/alpha` sub-path. diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index a90fe630b5..34cd78d480 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -14,7 +14,7 @@ app: - entity.cards.labels - entity.cards.links: config: - filter: kind:component + filter: kind:component has:links # Entity page content - entity.content.techdocs diff --git a/plugins/catalog-common/api-report-alpha.md b/plugins/catalog-common/api-report-alpha.md index bd36b499fe..12e0806a9c 100644 --- a/plugins/catalog-common/api-report-alpha.md +++ b/plugins/catalog-common/api-report-alpha.md @@ -4,7 +4,6 @@ ```ts import { BasicPermission } from '@backstage/plugin-permission-common'; -import { Entity } from '@backstage/catalog-model'; import { ResourcePermission } from '@backstage/plugin-permission-common'; // @alpha @@ -39,26 +38,6 @@ export const catalogPermissions: ( | ResourcePermission<'catalog-entity'> )[]; -// @alpha -export function parseFilterExpression( - expression: string, - options?: { - onParseError?: ( - error: Error, - context: { - expression: string; - }, - ) => void; - onEvaluateError?: ( - error: Error, - context: { - expression: string; - entity: Entity; - }, - ) => void; - }, -): (entity: Entity) => boolean; - // @alpha export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity'; diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index ac21b76c2a..45720ee32a 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -46,7 +46,6 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^" }, diff --git a/plugins/catalog-common/src/alpha.ts b/plugins/catalog-common/src/alpha.ts index 015769c8a7..2dfc028332 100644 --- a/plugins/catalog-common/src/alpha.ts +++ b/plugins/catalog-common/src/alpha.ts @@ -26,4 +26,3 @@ export { catalogPermissions, } from './permissions'; export type { CatalogEntityPermission } from './permissions'; -export { parseFilterExpression } from './filter'; diff --git a/plugins/catalog-common/src/filter/index.ts b/plugins/catalog-common/src/filter/index.ts deleted file mode 100644 index c5ad803f4c..0000000000 --- a/plugins/catalog-common/src/filter/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { parseFilterExpression } from './parseFilterExpression'; diff --git a/plugins/catalog/src/alpha/EntityOverviewPage.tsx b/plugins/catalog/src/alpha/EntityOverviewPage.tsx index 5ef6b9a2e5..a71e789522 100644 --- a/plugins/catalog/src/alpha/EntityOverviewPage.tsx +++ b/plugins/catalog/src/alpha/EntityOverviewPage.tsx @@ -15,10 +15,10 @@ */ import { Entity } from '@backstage/catalog-model'; -import { parseFilterExpression } from '@backstage/plugin-catalog-common/alpha'; import { useEntity } from '@backstage/plugin-catalog-react'; import Grid from '@material-ui/core/Grid'; import React, { useMemo } from 'react'; +import { parseFilterExpression } from './filter/parseFilterExpression'; interface EntityOverviewPageProps { cards: Array<{ @@ -27,6 +27,9 @@ interface EntityOverviewPageProps { }>; } +// Keeps track of what filter expression strings that we've emitted warnings for so far +const seenExpressionStrings = new Set(); + function CardWrapper(props: { entity: Entity; element: React.JSX.Element; @@ -40,14 +43,19 @@ function CardWrapper(props: { } else if (typeof filter === 'function') { return subject => filter(subject); } - return parseFilterExpression(filter, { - onParseError(_error) { - // ignore silently - }, - onEvaluateError(_error) { - // ignore silently - }, - }); + const result = parseFilterExpression(filter); + if ( + result.expressionParseErrors.length && + !seenExpressionStrings.has(filter) + ) { + // eslint-disable-next-line no-console + console.warn( + `Error(s) in entity filter expression '${filter}'`, + result.expressionParseErrors, + ); + seenExpressionStrings.add(filter); + } + return result.filterFn; }, [filter]); return filterFn(entity) ? ( diff --git a/plugins/catalog-common/src/filter/matrchers/createHasMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createHasMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts diff --git a/plugins/catalog-common/src/filter/matrchers/createHasMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createHasMatcher.ts rename to plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts diff --git a/plugins/catalog-common/src/filter/matrchers/createIsMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createIsMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts diff --git a/plugins/catalog-common/src/filter/matrchers/createIsMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createIsMatcher.ts rename to plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts diff --git a/plugins/catalog-common/src/filter/matrchers/createKindMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createKindMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts diff --git a/plugins/catalog-common/src/filter/matrchers/createKindMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createKindMatcher.ts rename to plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts diff --git a/plugins/catalog-common/src/filter/matrchers/createTypeMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createTypeMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts diff --git a/plugins/catalog-common/src/filter/matrchers/createTypeMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/createTypeMatcher.ts rename to plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts diff --git a/plugins/catalog-common/src/filter/matrchers/types.ts b/plugins/catalog/src/alpha/filter/matrchers/types.ts similarity index 100% rename from plugins/catalog-common/src/filter/matrchers/types.ts rename to plugins/catalog/src/alpha/filter/matrchers/types.ts diff --git a/plugins/catalog-common/src/filter/parseFilterExpression.test.ts b/plugins/catalog/src/alpha/filter/parseFilterExpression.test.ts similarity index 96% rename from plugins/catalog-common/src/filter/parseFilterExpression.test.ts rename to plugins/catalog/src/alpha/filter/parseFilterExpression.test.ts index a53b84646e..d1335524b4 100644 --- a/plugins/catalog-common/src/filter/parseFilterExpression.test.ts +++ b/plugins/catalog/src/alpha/filter/parseFilterExpression.test.ts @@ -22,11 +22,11 @@ import { describe('parseFilterExpression', () => { function run(expression: string) { - return parseFilterExpression(expression, { - onParseError(e: Error) { - throw e; - }, - }); + const result = parseFilterExpression(expression); + if (result.expressionParseErrors.length) { + throw result.expressionParseErrors[0]; + } + return result.filterFn; } it('supports "kind" expressions', () => { diff --git a/plugins/catalog-common/src/filter/parseFilterExpression.ts b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts similarity index 66% rename from plugins/catalog-common/src/filter/parseFilterExpression.ts rename to plugins/catalog/src/alpha/filter/parseFilterExpression.ts index ffd25d75d3..a4c17cb8da 100644 --- a/plugins/catalog-common/src/filter/parseFilterExpression.ts +++ b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts @@ -39,7 +39,6 @@ const rootMatcherFactories: Record< * Parses a filter expression that decides whether to render an entity component * or not. Returns a function that matches entities based on that expression. * - * @alpha * @remarks * * Filter strings are on the form `kind:user,group is:orphan`. There's @@ -47,61 +46,51 @@ const rootMatcherFactories: Record< * separated parameters. So the example filter string semantically means * "entities that are of either User or Group kind, and also are orphans". * - * The `onParseError` callback is called whenever an error was encountered - * during initial parsing of the expression. If the callback throws, - * `parseFilterExpression` throws that same error. If the callback does not - * throw, the part of the input expression that had an error is ignored entirely - * and parsing continues. - * - * The `onEvaluateError` callback is called whenever an error was encountered - * during evaluation of the returned function. If the callback throws, the - * evaluation call throws the same error. If the callback does not throw, the - * whole evaluation returns false. + * The `expressionParseErrors` array contains any errors that were encountered + * during initial parsing of the expression. Note that the parts of the input + * expression that had errors are ignored entirely and parsing continues as if + * they didn't exist. */ -export function parseFilterExpression( - expression: string, - options?: { - onParseError?: ( - error: Error, - context: { - expression: string; - }, - ) => void; - onEvaluateError?: ( - error: Error, - context: { - expression: string; - entity: Entity; - }, - ) => void; - }, -): (entity: Entity) => boolean { +export function parseFilterExpression(expression: string): { + filterFn: (entity: Entity) => boolean; + expressionParseErrors: Error[]; +} { + const expressionParseErrors: Error[] = []; + const parts = splitFilterExpression(expression, e => - options?.onParseError?.(e, { expression }), + expressionParseErrors.push(e), ); - const matchers = parts.map(part => { + const matchers = parts.flatMap(part => { const factory = rootMatcherFactories[part.key]; if (!factory) { const known = Object.keys(rootMatcherFactories).map(m => `'${m}'`); - throw new InputError( - `'${part.key}' is not a valid filter expression key, expected one of ${known}`, + expressionParseErrors.push( + new InputError( + `'${part.key}' is not a valid filter expression key, expected one of ${known}`, + ), ); + return []; } - return factory(part.parameters, e => - options?.onParseError?.(e, { expression }), + + const matcher = factory(part.parameters, e => + expressionParseErrors.push(e), ); + return [matcher]; }); - return (entity: Entity) => { - return matchers.every(matcher => { + const filterFn = (entity: Entity) => + matchers.every(matcher => { try { return matcher(entity); - } catch (e) { - options?.onEvaluateError?.(e, { expression, entity }); + } catch { return false; } }); + + return { + filterFn, + expressionParseErrors, }; } diff --git a/yarn.lock b/yarn.lock index 90be768746..2399f06ac5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5831,7 +5831,6 @@ __metadata: dependencies: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" - "@backstage/errors": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-search-common": "workspace:^" languageName: unknown