introduce the @backstage/filter-predicates package
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-graph': patch
|
||||
'@backstage/plugin-kubernetes': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-api-docs': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-catalog': patch
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
---
|
||||
|
||||
**BREAKING ALPHA**: All of the predicate types and functions have been moved to the `@backstage/filter-predicates` package.
|
||||
|
||||
When moving into the more general package, they were renamed as follows:
|
||||
|
||||
- `EntityPredicate` -> `FilterPredicate`
|
||||
- `EntityPredicateExpression` -> `FilterPredicateExpression`
|
||||
- `EntityPredicatePrimitive` -> `FilterPredicatePrimitive`
|
||||
- `entityPredicateToFilterFunction` -> `filterPredicateToFilterFunction`
|
||||
- `EntityPredicateValue` -> `FilterPredicateValue`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/filter-predicates': minor
|
||||
---
|
||||
|
||||
Introduced package, basically as the extracted predicate types from `@backstage/plugin-catalog-react/alpha`
|
||||
+4
-1
@@ -186,4 +186,7 @@ docs.json
|
||||
tsconfig.typedoc.tmp.json
|
||||
|
||||
# Storybook
|
||||
dist-storybook/
|
||||
dist-storybook/
|
||||
|
||||
# Personal allow patterns etc
|
||||
.claude/settings.local.json
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,18 @@
|
||||
# @backstage/filter-predicates
|
||||
|
||||
Contains types and implementations related to the concept of
|
||||
[filter predicate expressions](https://backstage.io/docs/features/software-catalog/catalog-customization#entity-predicate-queries).
|
||||
|
||||
These allow you to uniformly express filters, including logical operators and
|
||||
advanced matchers, for filtering through structured data.
|
||||
|
||||
Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"filter": {
|
||||
"kind": "Component",
|
||||
"spec.type": { "$in": ["service", "website"] }
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: backstage-filter-predicates
|
||||
title: '@backstage/filter-predicates'
|
||||
description: A library for expressing filter predicates and evaluating them against values
|
||||
spec:
|
||||
lifecycle: experimental
|
||||
type: backstage-common-library
|
||||
owner: framework-maintainers
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@backstage/filter-predicates",
|
||||
"version": "0.0.0",
|
||||
"description": "A library for expressing filter predicates and evaluating them against values",
|
||||
"backstage": {
|
||||
"role": "common-library"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
],
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/filter-predicates"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"sideEffects": false,
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
"zod": "^3.25.76",
|
||||
"zod-validation-error": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
## API Report File for "@backstage/filter-predicates"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Config } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import * as zodV3 from 'zod/v3';
|
||||
|
||||
// @public
|
||||
export function createZodV3FilterPredicateSchema(
|
||||
z: typeof zodV3.z,
|
||||
): zodV3.ZodType<FilterPredicate>;
|
||||
|
||||
// @public
|
||||
export function evaluateFilterPredicate(
|
||||
predicate: FilterPredicate,
|
||||
value: unknown,
|
||||
): boolean;
|
||||
|
||||
// @public
|
||||
export type FilterPredicate =
|
||||
| FilterPredicateExpression
|
||||
| FilterPredicatePrimitive
|
||||
| {
|
||||
$all: FilterPredicate[];
|
||||
}
|
||||
| {
|
||||
$any: FilterPredicate[];
|
||||
}
|
||||
| {
|
||||
$not: FilterPredicate;
|
||||
}
|
||||
| UnknownFilterPredicateOperator;
|
||||
|
||||
// @public
|
||||
export type FilterPredicateExpression = {
|
||||
[KPath in string]: FilterPredicateValue;
|
||||
} & {
|
||||
[KPath in `$${string}`]: never;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type FilterPredicatePrimitive = string | number | boolean;
|
||||
|
||||
// @public
|
||||
export function filterPredicateToFilterFunction<T = unknown>(
|
||||
predicate: FilterPredicate,
|
||||
): (value: T) => boolean;
|
||||
|
||||
// @public
|
||||
export type FilterPredicateValue =
|
||||
| FilterPredicatePrimitive
|
||||
| {
|
||||
$exists: boolean;
|
||||
}
|
||||
| {
|
||||
$in: FilterPredicatePrimitive[];
|
||||
}
|
||||
| {
|
||||
$contains: FilterPredicate;
|
||||
}
|
||||
| {
|
||||
$startsWith: string;
|
||||
}
|
||||
| UnknownFilterPredicateValueMatcher;
|
||||
|
||||
// @public
|
||||
export function getJsonValueAtPath(
|
||||
value: JsonValue | undefined,
|
||||
path: string,
|
||||
): JsonValue | undefined;
|
||||
|
||||
// @public
|
||||
export function parseFilterPredicate(value: unknown): FilterPredicate;
|
||||
|
||||
// @public
|
||||
export function readFilterPredicateFromConfig(
|
||||
config: Config,
|
||||
options?: ReadFilterPredicateFromConfigOptions,
|
||||
): FilterPredicate;
|
||||
|
||||
// @public
|
||||
export interface ReadFilterPredicateFromConfigOptions {
|
||||
key?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function readOptionalFilterPredicateFromConfig(
|
||||
config: Config,
|
||||
options?: ReadFilterPredicateFromConfigOptions,
|
||||
): FilterPredicate | undefined;
|
||||
|
||||
// @public
|
||||
export type UnknownFilterPredicateOperator = {
|
||||
[KOperator in `$${string}`]: JsonValue;
|
||||
} & {
|
||||
[KOperator in '$all' | '$any' | '$not']: never;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type UnknownFilterPredicateValueMatcher = {
|
||||
[KMatcher in `$${string}`]: JsonValue;
|
||||
} & {
|
||||
[KMatcher in '$exists' | '$in' | '$contains' | '$startsWith']: never;
|
||||
};
|
||||
```
|
||||
+7
-7
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type {
|
||||
EntityPredicate,
|
||||
EntityPredicateExpression,
|
||||
EntityPredicatePrimitive,
|
||||
EntityPredicateValue,
|
||||
} from './types';
|
||||
export { entityPredicateToFilterFunction } from './entityPredicateToFilterFunction';
|
||||
/**
|
||||
* Contains types and implementations related to the concept of filter predicate expressions.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './predicates';
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2025 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 { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
readFilterPredicateFromConfig,
|
||||
readOptionalFilterPredicateFromConfig,
|
||||
} from './config';
|
||||
|
||||
describe('readFilterPredicateFromConfig', () => {
|
||||
it('should read a filter predicate from config', () => {
|
||||
const config = new ConfigReader({
|
||||
predicate: { kind: 'component', 'spec.type': 'service' },
|
||||
});
|
||||
|
||||
const result = readFilterPredicateFromConfig(config, { key: 'predicate' });
|
||||
|
||||
expect(result).toEqual({ kind: 'component', 'spec.type': 'service' });
|
||||
});
|
||||
|
||||
it('should read a filter predicate from the root config', () => {
|
||||
const config = new ConfigReader({
|
||||
kind: 'component',
|
||||
'spec.type': 'service',
|
||||
});
|
||||
|
||||
const result = readFilterPredicateFromConfig(config);
|
||||
|
||||
expect(result).toEqual({ kind: 'component', 'spec.type': 'service' });
|
||||
});
|
||||
|
||||
it('should throw when filter predicate is missing', () => {
|
||||
const config = new ConfigReader({});
|
||||
|
||||
expect(() =>
|
||||
readFilterPredicateFromConfig(config, { key: 'predicate' }),
|
||||
).toThrow(/predicate/);
|
||||
});
|
||||
|
||||
it('should throw when filter predicate is invalid', () => {
|
||||
const config = new ConfigReader({
|
||||
predicate: { kind: { $invalid: 'foo' } },
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
readFilterPredicateFromConfig(config, { key: 'predicate' }),
|
||||
).toThrow(/Could not read filter predicate from config at 'predicate':/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readOptionalFilterPredicateFromConfig', () => {
|
||||
it('should read a filter predicate from config', () => {
|
||||
const config = new ConfigReader({
|
||||
predicate: { kind: 'component' },
|
||||
});
|
||||
|
||||
const result = readOptionalFilterPredicateFromConfig(config, {
|
||||
key: 'predicate',
|
||||
});
|
||||
|
||||
expect(result).toEqual({ kind: 'component' });
|
||||
});
|
||||
|
||||
it('should return undefined when filter predicate is missing', () => {
|
||||
const config = new ConfigReader({});
|
||||
|
||||
const result = readOptionalFilterPredicateFromConfig(config, {
|
||||
key: 'predicate',
|
||||
});
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should throw when filter predicate is invalid', () => {
|
||||
const config = new ConfigReader({
|
||||
predicate: { kind: { $invalid: 'foo' } },
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
readOptionalFilterPredicateFromConfig(config, { key: 'predicate' }),
|
||||
).toThrow(/Could not read filter predicate from config at 'predicate':/);
|
||||
});
|
||||
|
||||
it('should read complex filter predicates', () => {
|
||||
const config = new ConfigReader({
|
||||
filter: {
|
||||
$any: [{ kind: 'component', 'spec.type': 'service' }, { kind: 'api' }],
|
||||
},
|
||||
});
|
||||
|
||||
const result = readOptionalFilterPredicateFromConfig(config, {
|
||||
key: 'filter',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
$any: [{ kind: 'component', 'spec.type': 'service' }, { kind: 'api' }],
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2025 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 { Config } from '@backstage/config';
|
||||
import { InputError, stringifyError } from '@backstage/errors';
|
||||
import { parseFilterPredicate } from './schema';
|
||||
import { FilterPredicate } from './types';
|
||||
|
||||
/**
|
||||
* Options for {@link readFilterPredicateFromConfig} and {@link readOptionalFilterPredicateFromConfig}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ReadFilterPredicateFromConfigOptions {
|
||||
/**
|
||||
* The key to read from the config. If not provided, the entire config is used.
|
||||
*/
|
||||
key?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a filter predicate expression from a config object.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function readFilterPredicateFromConfig(
|
||||
config: Config,
|
||||
options?: ReadFilterPredicateFromConfigOptions,
|
||||
): FilterPredicate {
|
||||
const key = options?.key;
|
||||
const value = key ? config.get(key) : config.get();
|
||||
|
||||
try {
|
||||
return parseFilterPredicate(value);
|
||||
} catch (error) {
|
||||
const where = key ? ` at '${key}'` : '';
|
||||
throw new InputError(
|
||||
`Could not read filter predicate from config${where}: ${stringifyError(
|
||||
error,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an optional filter predicate expression from a config object.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function readOptionalFilterPredicateFromConfig(
|
||||
config: Config,
|
||||
options?: ReadFilterPredicateFromConfigOptions,
|
||||
): FilterPredicate | undefined {
|
||||
const key = options?.key;
|
||||
const value = key ? config.getOptional(key) : config.getOptional();
|
||||
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return readFilterPredicateFromConfig(config, options);
|
||||
}
|
||||
+35
-11
@@ -14,10 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { entityPredicateToFilterFunction } from './entityPredicateToFilterFunction';
|
||||
import { EntityPredicate } from './types';
|
||||
import {
|
||||
evaluateFilterPredicate,
|
||||
filterPredicateToFilterFunction,
|
||||
} from './evaluate';
|
||||
import { FilterPredicate } from './types';
|
||||
|
||||
describe('entityPredicateToFilterFunction', () => {
|
||||
describe('evaluate', () => {
|
||||
const entities = [
|
||||
{
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
@@ -129,7 +132,7 @@ describe('entityPredicateToFilterFunction', () => {
|
||||
},
|
||||
];
|
||||
|
||||
it.each([
|
||||
describe.each([
|
||||
['s', { kind: 'component', 'spec.type': 'service' }],
|
||||
['s', { 'metadata.tags': { $contains: 'java' } }],
|
||||
[
|
||||
@@ -186,7 +189,7 @@ describe('entityPredicateToFilterFunction', () => {
|
||||
metadata: { $contains: { name: 'a' } },
|
||||
},
|
||||
],
|
||||
['', { $unknown: 'ignored' } as unknown as EntityPredicate],
|
||||
['', { $unknown: 'ignored' } as unknown as FilterPredicate],
|
||||
[
|
||||
's,w',
|
||||
{ kind: 'component', 'spec.type': { $in: ['service', 'website'] } },
|
||||
@@ -234,12 +237,33 @@ describe('entityPredicateToFilterFunction', () => {
|
||||
'metadata.annotations.github.com/repo': { $exists: true },
|
||||
},
|
||||
],
|
||||
['a', { 'spec.type': { $startsWith: 'g' } }],
|
||||
])('filter entry %#', (expected, filter) => {
|
||||
const filtered = entities.filter(entity =>
|
||||
entityPredicateToFilterFunction(filter)(entity),
|
||||
);
|
||||
expect(filtered.map(e => e.metadata.name).sort()).toEqual(
|
||||
expected.split(',').filter(Boolean).sort(),
|
||||
);
|
||||
it('filterPredicateToFilterFunction', () => {
|
||||
const filtered = entities.filter(entity =>
|
||||
filterPredicateToFilterFunction(filter)(entity),
|
||||
);
|
||||
expect(filtered.map(e => e.metadata.name).sort()).toEqual(
|
||||
expected.split(',').filter(Boolean).sort(),
|
||||
);
|
||||
});
|
||||
|
||||
it('evaluateFilterPredicate', () => {
|
||||
const filtered = entities.filter(entity =>
|
||||
evaluateFilterPredicate(filter, entity),
|
||||
);
|
||||
expect(filtered.map(e => e.metadata.name).sort()).toEqual(
|
||||
expected.split(',').filter(Boolean).sort(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles unknown filter predicate operators and matchers', () => {
|
||||
const operator = { $unknown: 'foo' } as unknown as FilterPredicate;
|
||||
const value = { kind: { $unknown: 'foo' } } as unknown as FilterPredicate;
|
||||
expect(evaluateFilterPredicate(operator, entities[0])).toBe(false);
|
||||
expect(evaluateFilterPredicate(value, entities[0])).toBe(false);
|
||||
expect(filterPredicateToFilterFunction(operator)(entities[0])).toBe(false);
|
||||
expect(filterPredicateToFilterFunction(value)(entities[0])).toBe(false);
|
||||
});
|
||||
});
|
||||
+50
-37
@@ -15,51 +15,48 @@
|
||||
*/
|
||||
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { EntityPredicate, EntityPredicateValue } from './types';
|
||||
import { valueAtPath } from './valueAtPath';
|
||||
import { FilterPredicate, FilterPredicateValue } from './types';
|
||||
import { getJsonValueAtPath } from './getJsonValueAtPath';
|
||||
|
||||
/**
|
||||
* Convert an entity predicate to a filter function that can be used to filter entities.
|
||||
* @alpha
|
||||
*/
|
||||
export function entityPredicateToFilterFunction<T extends JsonValue>(
|
||||
entityPredicate: EntityPredicate,
|
||||
): (value: T) => boolean {
|
||||
return value => evaluateEntityPredicate(entityPredicate, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a entity predicate against a value, typically an entity.
|
||||
* Evaluate a filter predicate against a value.
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
function evaluateEntityPredicate(
|
||||
filter: EntityPredicate,
|
||||
value: JsonValue,
|
||||
export function evaluateFilterPredicate(
|
||||
predicate: FilterPredicate,
|
||||
value: unknown,
|
||||
): boolean {
|
||||
if (typeof filter !== 'object' || filter === null || Array.isArray(filter)) {
|
||||
return valuesAreEqual(value, filter);
|
||||
if (
|
||||
typeof predicate !== 'object' ||
|
||||
predicate === null ||
|
||||
Array.isArray(predicate)
|
||||
) {
|
||||
return valuesAreEqual(value, predicate);
|
||||
}
|
||||
|
||||
if ('$all' in filter) {
|
||||
return filter.$all.every(f => evaluateEntityPredicate(f, value));
|
||||
if ('$all' in predicate) {
|
||||
return predicate.$all.every(f => evaluateFilterPredicate(f, value));
|
||||
}
|
||||
if ('$any' in filter) {
|
||||
return filter.$any.some(f => evaluateEntityPredicate(f, value));
|
||||
if ('$any' in predicate) {
|
||||
return predicate.$any.some(f => evaluateFilterPredicate(f, value));
|
||||
}
|
||||
if ('$not' in filter) {
|
||||
return !evaluateEntityPredicate(filter.$not, value);
|
||||
if ('$not' in predicate) {
|
||||
return !evaluateFilterPredicate(predicate.$not, value);
|
||||
}
|
||||
|
||||
for (const filterKey in filter) {
|
||||
if (!Object.hasOwn(filter, filterKey)) {
|
||||
for (const filterKey in predicate) {
|
||||
if (!Object.hasOwn(predicate, filterKey)) {
|
||||
continue;
|
||||
}
|
||||
if (filterKey.startsWith('$')) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!evaluatePredicateValue(filter[filterKey], valueAtPath(value, filterKey))
|
||||
!evaluateFilterPredicateValue(
|
||||
predicate[filterKey],
|
||||
getJsonValueAtPath(value as JsonValue, filterKey),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -69,13 +66,24 @@ function evaluateEntityPredicate(
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a single value against a predicate value.
|
||||
* Convert a filter predicate to a filter function.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function filterPredicateToFilterFunction<T = unknown>(
|
||||
predicate: FilterPredicate,
|
||||
): (value: T) => boolean {
|
||||
return value => evaluateFilterPredicate(predicate, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a single value against a filter predicate value.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function evaluatePredicateValue(
|
||||
filter: EntityPredicateValue,
|
||||
value: JsonValue | undefined,
|
||||
function evaluateFilterPredicateValue(
|
||||
filter: FilterPredicateValue,
|
||||
value: unknown,
|
||||
): boolean {
|
||||
if (typeof filter !== 'object' || filter === null || Array.isArray(filter)) {
|
||||
return valuesAreEqual(value, filter);
|
||||
@@ -85,7 +93,7 @@ function evaluatePredicateValue(
|
||||
if (!Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
return value.some(v => evaluateEntityPredicate(filter.$contains, v));
|
||||
return value.some(v => evaluateFilterPredicate(filter.$contains, v));
|
||||
}
|
||||
if ('$in' in filter) {
|
||||
return filter.$in.some(search => valuesAreEqual(value, search));
|
||||
@@ -96,14 +104,19 @@ function evaluatePredicateValue(
|
||||
}
|
||||
return value === undefined;
|
||||
}
|
||||
if ('$startsWith' in filter) {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
return value
|
||||
.toLocaleUpperCase('en-US')
|
||||
.startsWith(filter.$startsWith.toLocaleUpperCase('en-US'));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function valuesAreEqual(
|
||||
a: JsonValue | undefined,
|
||||
b: JsonValue | undefined,
|
||||
): boolean {
|
||||
function valuesAreEqual(a: unknown, b: unknown): boolean {
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
}
|
||||
+3
-3
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { valueAtPath } from './valueAtPath';
|
||||
import { getJsonValueAtPath } from './getJsonValueAtPath';
|
||||
|
||||
describe('valueAtPath', () => {
|
||||
describe('getJsonValueAtPath', () => {
|
||||
const subject = {
|
||||
name: 'Test',
|
||||
fields: {
|
||||
@@ -58,6 +58,6 @@ describe('valueAtPath', () => {
|
||||
['mixed.annotations.example.com/description', 'A test subject'],
|
||||
['mixed.annotations.long.domain.example.com/custom', 'long'],
|
||||
])(`should find value at path %s`, (path, expected) => {
|
||||
expect(valueAtPath(subject, path)).toEqual(expected);
|
||||
expect(getJsonValueAtPath(subject, path)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
+3
-3
@@ -27,9 +27,9 @@ import { JsonValue } from '@backstage/types';
|
||||
*
|
||||
* This lookup does not traverse into arrays, returning `undefined` instead.
|
||||
*
|
||||
* @internal
|
||||
* @public
|
||||
*/
|
||||
export function valueAtPath(
|
||||
export function getJsonValueAtPath(
|
||||
value: JsonValue | undefined,
|
||||
path: string,
|
||||
): JsonValue | undefined {
|
||||
@@ -55,7 +55,7 @@ export function valueAtPath(
|
||||
}
|
||||
}
|
||||
if (path.startsWith(`${valueKey}.`)) {
|
||||
const found = valueAtPath(
|
||||
const found = getJsonValueAtPath(
|
||||
value[valueKey],
|
||||
path.slice(valueKey.length + 1),
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 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 {
|
||||
readFilterPredicateFromConfig,
|
||||
readOptionalFilterPredicateFromConfig,
|
||||
} from './config';
|
||||
export type { ReadFilterPredicateFromConfigOptions } from './config';
|
||||
export {
|
||||
evaluateFilterPredicate,
|
||||
filterPredicateToFilterFunction,
|
||||
} from './evaluate';
|
||||
export { getJsonValueAtPath } from './getJsonValueAtPath';
|
||||
export {
|
||||
createZodV3FilterPredicateSchema,
|
||||
parseFilterPredicate,
|
||||
} from './schema';
|
||||
export type {
|
||||
FilterPredicate,
|
||||
FilterPredicateExpression,
|
||||
FilterPredicatePrimitive,
|
||||
FilterPredicateValue,
|
||||
UnknownFilterPredicateOperator,
|
||||
UnknownFilterPredicateValueMatcher,
|
||||
} from './types';
|
||||
+110
-6
@@ -15,14 +15,17 @@
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { createEntityPredicateSchema } from './createEntityPredicateSchema';
|
||||
import { EntityPredicate } from './types';
|
||||
import {
|
||||
createZodV3FilterPredicateSchema,
|
||||
parseFilterPredicate,
|
||||
} from './schema';
|
||||
import { FilterPredicate } from './types';
|
||||
|
||||
describe('createEntityPredicateSchema', () => {
|
||||
const schema = createEntityPredicateSchema(z);
|
||||
describe('createZodV3FilterPredicateSchema', () => {
|
||||
const schema = createZodV3FilterPredicateSchema(z);
|
||||
|
||||
describe('valid predicates', () => {
|
||||
const predicates: EntityPredicate[] = [
|
||||
const predicates: FilterPredicate[] = [
|
||||
'string',
|
||||
'',
|
||||
1,
|
||||
@@ -88,7 +91,7 @@ describe('createEntityPredicateSchema', () => {
|
||||
|
||||
describe('invalid predicates', () => {
|
||||
const predicates: Array<
|
||||
Exclude<EntityPredicate | unknown, EntityPredicate>
|
||||
Exclude<FilterPredicate | unknown, FilterPredicate>
|
||||
> = [
|
||||
[],
|
||||
['foo', 'bar'],
|
||||
@@ -113,3 +116,104 @@ describe('createEntityPredicateSchema', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseFilterPredicate', () => {
|
||||
describe('valid predicates', () => {
|
||||
const predicates: FilterPredicate[] = [
|
||||
'string',
|
||||
'',
|
||||
1,
|
||||
{ kind: 'component', 'spec.type': 'service' },
|
||||
{ 'metadata.tags': { $in: ['java'] } },
|
||||
{ 'metadata.tags': { $contains: 'java' } },
|
||||
{
|
||||
$all: [
|
||||
{ 'metadata.tags': { $contains: 'java' } },
|
||||
{ 'metadata.tags': { $contains: 'spring' } },
|
||||
],
|
||||
},
|
||||
{ 'metadata.tags': { $in: ['go'] } },
|
||||
{ 'metadata.tags.0': 'java' },
|
||||
{ $not: { 'metadata.tags': { $in: ['java'] } } },
|
||||
{
|
||||
$any: [
|
||||
{ kind: 'component', 'spec.type': 'service' },
|
||||
{ kind: 'group' },
|
||||
],
|
||||
},
|
||||
{
|
||||
relations: {
|
||||
$contains: { type: 'ownedBy', targetRef: 'group:default/g' },
|
||||
},
|
||||
},
|
||||
{
|
||||
metadata: { $contains: { name: 'a' } },
|
||||
},
|
||||
{ kind: 'component', 'spec.type': { $in: ['service', 'website'] } },
|
||||
{
|
||||
$any: [
|
||||
{
|
||||
$all: [
|
||||
{
|
||||
kind: 'component',
|
||||
'spec.type': { $in: ['service', 'website'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ $all: [{ kind: 'api', 'spec.type': 'grpc' }] },
|
||||
],
|
||||
},
|
||||
{ kind: 'component', 'spec.type': { $in: ['service'] } },
|
||||
{ 'spec.owner': { $exists: true } },
|
||||
{ 'spec.owner': { $exists: false } },
|
||||
{ 'spec.type': 'service' },
|
||||
{ $not: { 'spec.type': 'service' } },
|
||||
{
|
||||
kind: 'component',
|
||||
'metadata.annotations.github.com/repo': { $exists: true },
|
||||
},
|
||||
{ $all: [{ x: { $exists: true } }] },
|
||||
{ $any: [{ x: { $exists: true } }] },
|
||||
{ $not: { x: { $exists: true } } },
|
||||
{ $not: { $all: [{ x: { $exists: true } }] } },
|
||||
];
|
||||
|
||||
it.each(predicates)(
|
||||
'should return the predicate for valid input %j',
|
||||
predicate => {
|
||||
expect(parseFilterPredicate(predicate)).toEqual(predicate);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('invalid predicates', () => {
|
||||
const predicates: Array<
|
||||
Exclude<FilterPredicate | unknown, FilterPredicate>
|
||||
> = [
|
||||
[],
|
||||
['foo', 'bar'],
|
||||
{ kind: { 1: 'foo' } },
|
||||
{ kind: { foo: 'bar' } },
|
||||
{ kind: { $unknown: 'foo' } },
|
||||
{ kind: { $in: 'foo' } },
|
||||
{ kind: { $in: [{ x: 'foo' }] } },
|
||||
{ kind: { $in: [{ x: 'foo' }] } },
|
||||
{ 'spec.type': null },
|
||||
{ $all: [{ x: { $unknown: true } }] },
|
||||
{ $any: [{ x: { $unknown: true } }] },
|
||||
{ $not: { x: { $unknown: true } } },
|
||||
{ $not: { $all: [{ x: { $unknown: true } }] } },
|
||||
{ $unknown: 'foo' },
|
||||
{ 'metadata.tags': ['foo', 'bar'] },
|
||||
];
|
||||
|
||||
it.each(predicates)(
|
||||
'should throw InputError for invalid predicate %j',
|
||||
predicate => {
|
||||
expect(() => parseFilterPredicate(predicate)).toThrow(
|
||||
/Invalid filter predicate/,
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
+39
-12
@@ -14,31 +14,39 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { fromZodError } from 'zod-validation-error/v3';
|
||||
import * as zodV3 from 'zod/v3';
|
||||
import {
|
||||
EntityPredicate,
|
||||
EntityPredicateExpression,
|
||||
EntityPredicatePrimitive,
|
||||
EntityPredicateValue,
|
||||
FilterPredicate,
|
||||
FilterPredicateExpression,
|
||||
FilterPredicatePrimitive,
|
||||
FilterPredicateValue,
|
||||
} from './types';
|
||||
import type { z as zImpl, ZodType } from 'zod';
|
||||
|
||||
/** @internal */
|
||||
export function createEntityPredicateSchema(z: typeof zImpl) {
|
||||
/**
|
||||
* Create a Zod schema for validating filter predicates.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createZodV3FilterPredicateSchema(
|
||||
z: typeof zodV3.z,
|
||||
): zodV3.ZodType<FilterPredicate> {
|
||||
const primitiveSchema = z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
]) as ZodType<EntityPredicatePrimitive>;
|
||||
]) as zodV3.ZodType<FilterPredicatePrimitive>;
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let valuePredicateSchema: ZodType<EntityPredicateValue>;
|
||||
let valuePredicateSchema: zodV3.ZodType<FilterPredicateValue>;
|
||||
|
||||
const expressionSchema = z.lazy(() =>
|
||||
z.union([
|
||||
z.record(z.string().regex(/^(?!\$).*$/), valuePredicateSchema),
|
||||
z.record(z.string().regex(/^\$/), z.never()),
|
||||
]),
|
||||
) as ZodType<EntityPredicateExpression>;
|
||||
) as zodV3.ZodType<FilterPredicateExpression>;
|
||||
|
||||
const predicateSchema = z.lazy(() =>
|
||||
z.union([
|
||||
@@ -48,14 +56,33 @@ export function createEntityPredicateSchema(z: typeof zImpl) {
|
||||
z.object({ $any: z.array(predicateSchema) }),
|
||||
z.object({ $not: predicateSchema }),
|
||||
]),
|
||||
) as ZodType<EntityPredicate>;
|
||||
) as zodV3.ZodType<FilterPredicate>;
|
||||
|
||||
valuePredicateSchema = z.union([
|
||||
primitiveSchema,
|
||||
z.object({ $exists: z.boolean() }),
|
||||
z.object({ $in: z.array(primitiveSchema) }),
|
||||
z.object({ $contains: predicateSchema }),
|
||||
]) as ZodType<EntityPredicateValue>;
|
||||
]) as zodV3.ZodType<FilterPredicateValue>;
|
||||
|
||||
return predicateSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a value to check that it's a valid filter predicate.
|
||||
*
|
||||
* @public
|
||||
* @param value - The value to parse.
|
||||
* @returns A valid filter predicate.
|
||||
* @throws An error if the value is not a valid filter predicate.
|
||||
*/
|
||||
export function parseFilterPredicate(value: unknown): FilterPredicate {
|
||||
const schema = createZodV3FilterPredicateSchema(zodV3.z);
|
||||
const result = schema.safeParse(value);
|
||||
if (!result.success) {
|
||||
throw new InputError(
|
||||
`Invalid filter predicate: ${fromZodError(result.error)}`,
|
||||
);
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2025 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 { JsonValue } from '@backstage/types';
|
||||
|
||||
/**
|
||||
* Represents future additions to the set of filter predicate operators.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* If you write code that explicitly inspects filter predicate expressions, you
|
||||
* should be ready for the appearance of such operators and deliberately
|
||||
* gracefully fail to match them.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UnknownFilterPredicateOperator = {
|
||||
[KOperator in `$${string}`]: JsonValue;
|
||||
} & {
|
||||
[KOperator in '$all' | '$any' | '$not']: never;
|
||||
};
|
||||
|
||||
/**
|
||||
* A filter predicate that can be evaluated against a value.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* A predicate is always an object at the root. The most basic use case is to
|
||||
* declare keys that are dot-separated paths into a structured data value, and
|
||||
* values that all need to match the corresponding property value in the data.
|
||||
*
|
||||
* The equality test is case-insensitive and numbers are converted to strings,
|
||||
* i.e. `"Component"` and `"component"` are considered equal, and so are `7` and
|
||||
* `"7"`.
|
||||
*
|
||||
* Example that matches catalog entity components that are of type `service`:
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "kind": "Component",
|
||||
* "spec.type": "service"
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The special keys `$all`, `$any`, and `$not` are logical operators that can be
|
||||
* used to combine multiple predicates or negate their result. These must be
|
||||
* used standalone, i.e. they cannot be combined with other matchers.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "$all": [
|
||||
* {
|
||||
* "kind": "Component"
|
||||
* },
|
||||
* {
|
||||
* "$any": [{ "spec.type": "service" }, { "spec.type": "website" }]
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Objects with the special keys `$exists`, `$in`, and `$contains` are value
|
||||
* operators that can be used to perform more advanced matching against property
|
||||
* values than just equality.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```json
|
||||
* {
|
||||
* "filter": {
|
||||
* "kind": "Component",
|
||||
* "spec.type": { "$in": ["service", "website"] },
|
||||
* "metadata.annotations.github.com/project-slug": { $exists: true }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FilterPredicate =
|
||||
| FilterPredicateExpression
|
||||
| FilterPredicatePrimitive
|
||||
| {
|
||||
/**
|
||||
* Asserts that all of the given predicates must be true.
|
||||
*/
|
||||
$all: FilterPredicate[];
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Asserts that at least one of the given predicates must be true.
|
||||
*/
|
||||
$any: FilterPredicate[];
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Asserts that the given predicate must not be true.
|
||||
*/
|
||||
$not: FilterPredicate;
|
||||
}
|
||||
| UnknownFilterPredicateOperator;
|
||||
|
||||
/**
|
||||
* A filter predicate expression that matches against one or more object
|
||||
* properties.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Each key of a record is a dot-separated path into the entity structure, e.g.
|
||||
* `metadata.name`.
|
||||
*
|
||||
* The values are filter predicates that are evaluated against the value of the
|
||||
* property at the given path.
|
||||
*
|
||||
* For values that are given as primitives, the equality test is
|
||||
* case-insensitive and numbers are converted to strings, i.e. `"Component"` and
|
||||
* `"component"` are considered equal, and so are `7` and `"7"`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FilterPredicateExpression = {
|
||||
[KPath in string]: FilterPredicateValue;
|
||||
} & {
|
||||
[KPath in `$${string}`]: never;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents future additions to the set of filter predicate value matchers.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* If you write code that explicitly inspects filter predicate expressions, you
|
||||
* should be ready for the appearance of such matchers and deliberately
|
||||
* gracefully fail to match them.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type UnknownFilterPredicateValueMatcher = {
|
||||
[KMatcher in `$${string}`]: JsonValue;
|
||||
} & {
|
||||
[KMatcher in '$exists' | '$in' | '$contains' | '$startsWith']: never;
|
||||
};
|
||||
|
||||
/**
|
||||
* A filter predicate value that can be used to match against a property value.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FilterPredicateValue =
|
||||
| FilterPredicatePrimitive
|
||||
| {
|
||||
/**
|
||||
* Asserts that the property exists and has any value (`true`) - or not
|
||||
* (`false`).
|
||||
*/
|
||||
$exists: boolean;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Asserts that the property value is any one of the given possible
|
||||
* values.
|
||||
*/
|
||||
$in: FilterPredicatePrimitive[];
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Asserts that the property value is an array, and that at least one of
|
||||
* its elements matches the given predicate.
|
||||
*/
|
||||
$contains: FilterPredicate;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Asserts that the property value is string, and that it starts with the given string.
|
||||
*/
|
||||
$startsWith: string;
|
||||
}
|
||||
| UnknownFilterPredicateValueMatcher;
|
||||
|
||||
/**
|
||||
* A primitive value that can be used in filter predicates.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FilterPredicatePrimitive = string | number | boolean;
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2026 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 {};
|
||||
@@ -9,10 +9,10 @@ import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityCardType } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { IconComponent } from '@backstage/frontend-plugin-api';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
@@ -85,11 +85,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'consumed-apis';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -118,7 +118,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -126,11 +126,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'consuming-components';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -159,7 +159,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -167,11 +167,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'definition';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -200,7 +200,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -208,11 +208,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'has-apis';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -241,7 +241,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -249,11 +249,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'provided-apis';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -282,7 +282,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -290,11 +290,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'providing-components';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -323,7 +323,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -333,11 +333,11 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
path: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
group: string | false | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
title?: string | undefined;
|
||||
path?: string | undefined;
|
||||
group?: string | false | undefined;
|
||||
@@ -384,7 +384,7 @@ const _default: OverridableFrontendPlugin<
|
||||
group?: keyof defaultEntityContentGroups | (string & {});
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef_2;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'entity-content:api-docs/definition': OverridableExtensionDefinition<{
|
||||
@@ -393,11 +393,11 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
path: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
group: string | false | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
title?: string | undefined;
|
||||
path?: string | undefined;
|
||||
group?: string | false | undefined;
|
||||
@@ -444,7 +444,7 @@ const _default: OverridableFrontendPlugin<
|
||||
group?: keyof defaultEntityContentGroups | (string & {});
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef_2;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'nav-item:api-docs': OverridableExtensionDefinition<{
|
||||
|
||||
@@ -8,10 +8,10 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityCardType } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
@@ -91,7 +91,7 @@ const _default: OverridableFrontendPlugin<
|
||||
curve: 'curveStepBefore' | 'curveMonotoneX' | undefined;
|
||||
title: string | undefined;
|
||||
height: number | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
@@ -106,7 +106,7 @@ const _default: OverridableFrontendPlugin<
|
||||
mergeRelations?: boolean | undefined;
|
||||
relationPairs?: [string, string][] | undefined;
|
||||
unidirectional?: boolean | undefined;
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -137,7 +137,7 @@ const _default: OverridableFrontendPlugin<
|
||||
name: 'relations';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/filter-predicates": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/frontend-test-utils": "workspace:^",
|
||||
"@backstage/integration-react": "workspace:^",
|
||||
|
||||
@@ -10,8 +10,8 @@ import { Entity } from '@backstage/catalog-model';
|
||||
import { ExtensionBlueprint } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { IconLinkVerticalProps } from '@backstage/core-components';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { ResourcePermission } from '@backstage/plugin-permission-common';
|
||||
@@ -130,7 +130,7 @@ export function convertLegacyEntityCardExtension(
|
||||
LegacyExtension: ComponentType<{}>,
|
||||
overrides?: {
|
||||
name?: string;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
},
|
||||
): ExtensionDefinition;
|
||||
@@ -140,7 +140,7 @@ export function convertLegacyEntityContentExtension(
|
||||
LegacyExtension: ComponentType<{}>,
|
||||
overrides?: {
|
||||
name?: string;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
path?: string;
|
||||
title?: string;
|
||||
defaultPath?: [Error: `Use the 'path' override instead`];
|
||||
@@ -163,7 +163,7 @@ export const EntityCardBlueprint: ExtensionBlueprint<{
|
||||
kind: 'entity-card';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
output:
|
||||
@@ -191,11 +191,11 @@ export const EntityCardBlueprint: ExtensionBlueprint<{
|
||||
>;
|
||||
inputs: {};
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
dataRefs: {
|
||||
@@ -232,7 +232,7 @@ export const EntityContentBlueprint: ExtensionBlueprint<{
|
||||
group?: keyof typeof defaultEntityContentGroups | (string & {});
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
output:
|
||||
| ExtensionDataRef<string, 'core.routing.path', {}>
|
||||
@@ -270,11 +270,11 @@ export const EntityContentBlueprint: ExtensionBlueprint<{
|
||||
config: {
|
||||
path: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
group: string | false | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
title?: string | undefined;
|
||||
path?: string | undefined;
|
||||
group?: string | false | undefined;
|
||||
@@ -307,7 +307,7 @@ export const EntityContentBlueprint: ExtensionBlueprint<{
|
||||
export const EntityContentLayoutBlueprint: ExtensionBlueprint<{
|
||||
kind: 'entity-content-layout';
|
||||
params: {
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
loader: () => Promise<(props: EntityContentLayoutProps) => JSX_2.Element>;
|
||||
};
|
||||
output:
|
||||
@@ -333,10 +333,10 @@ export const EntityContentLayoutBlueprint: ExtensionBlueprint<{
|
||||
inputs: {};
|
||||
config: {
|
||||
type: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: string | undefined;
|
||||
};
|
||||
dataRefs: {
|
||||
@@ -382,10 +382,10 @@ export const EntityContextMenuItemBlueprint: ExtensionBlueprint<{
|
||||
>;
|
||||
inputs: {};
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
};
|
||||
dataRefs: {
|
||||
filterFunction: ConfigurableExtensionDataRef<
|
||||
@@ -400,7 +400,7 @@ export const EntityContextMenuItemBlueprint: ExtensionBlueprint<{
|
||||
export type EntityContextMenuItemParams = {
|
||||
useProps: UseProps;
|
||||
icon: JSX_2.Element;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
@@ -408,7 +408,7 @@ export const EntityHeaderBlueprint: ExtensionBlueprint<{
|
||||
kind: 'entity-header';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
output:
|
||||
| ExtensionDataRef<
|
||||
@@ -434,10 +434,10 @@ export const EntityHeaderBlueprint: ExtensionBlueprint<{
|
||||
>;
|
||||
inputs: {};
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
};
|
||||
dataRefs: {
|
||||
filterFunction: ConfigurableExtensionDataRef<
|
||||
@@ -458,7 +458,7 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{
|
||||
kind: 'entity-icon-link';
|
||||
params: {
|
||||
useProps: () => Omit<IconLinkVerticalProps, 'color'>;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
output:
|
||||
| ExtensionDataRef<
|
||||
@@ -484,10 +484,10 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
};
|
||||
@@ -510,48 +510,6 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{
|
||||
};
|
||||
}>;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type EntityPredicate =
|
||||
| EntityPredicateExpression
|
||||
| EntityPredicatePrimitive
|
||||
| {
|
||||
$all: EntityPredicate[];
|
||||
}
|
||||
| {
|
||||
$any: EntityPredicate[];
|
||||
}
|
||||
| {
|
||||
$not: EntityPredicate;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type EntityPredicateExpression = {
|
||||
[KPath in string]: EntityPredicateValue;
|
||||
} & {
|
||||
[KPath in `$${string}`]: never;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type EntityPredicatePrimitive = string | number | boolean;
|
||||
|
||||
// @alpha
|
||||
export function entityPredicateToFilterFunction<T extends JsonValue>(
|
||||
entityPredicate: EntityPredicate,
|
||||
): (value: T) => boolean;
|
||||
|
||||
// @alpha (undocumented)
|
||||
export type EntityPredicateValue =
|
||||
| EntityPredicatePrimitive
|
||||
| {
|
||||
$exists: boolean;
|
||||
}
|
||||
| {
|
||||
$in: EntityPredicatePrimitive[];
|
||||
}
|
||||
| {
|
||||
$contains: EntityPredicate;
|
||||
};
|
||||
|
||||
// @alpha (undocumented)
|
||||
export const EntityTableColumnTitle: ({
|
||||
translationKey,
|
||||
|
||||
@@ -26,8 +26,10 @@ import {
|
||||
entityCardTypes,
|
||||
EntityCardType,
|
||||
} from './extensionData';
|
||||
import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema';
|
||||
import { EntityPredicate } from '../predicates';
|
||||
import {
|
||||
FilterPredicate,
|
||||
createZodV3FilterPredicateSchema,
|
||||
} from '@backstage/filter-predicates';
|
||||
import { resolveEntityFilterData } from './resolveEntityFilterData';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
@@ -52,7 +54,7 @@ export const EntityCardBlueprint = createExtensionBlueprint({
|
||||
config: {
|
||||
schema: {
|
||||
filter: z =>
|
||||
z.union([z.string(), createEntityPredicateSchema(z)]).optional(),
|
||||
z.union([z.string(), createZodV3FilterPredicateSchema(z)]).optional(),
|
||||
type: z => z.enum(entityCardTypes).optional(),
|
||||
},
|
||||
},
|
||||
@@ -63,7 +65,7 @@ export const EntityCardBlueprint = createExtensionBlueprint({
|
||||
type,
|
||||
}: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
},
|
||||
{ node, config },
|
||||
|
||||
@@ -27,9 +27,11 @@ import {
|
||||
entityContentGroupDataRef,
|
||||
defaultEntityContentGroups,
|
||||
} from './extensionData';
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import {
|
||||
FilterPredicate,
|
||||
createZodV3FilterPredicateSchema,
|
||||
} from '@backstage/filter-predicates';
|
||||
import { resolveEntityFilterData } from './resolveEntityFilterData';
|
||||
import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
/**
|
||||
@@ -59,7 +61,7 @@ export const EntityContentBlueprint = createExtensionBlueprint({
|
||||
path: z => z.string().optional(),
|
||||
title: z => z.string().optional(),
|
||||
filter: z =>
|
||||
z.union([z.string(), createEntityPredicateSchema(z)]).optional(),
|
||||
z.union([z.string(), createZodV3FilterPredicateSchema(z)]).optional(),
|
||||
group: z => z.literal(false).or(z.string()).optional(),
|
||||
},
|
||||
},
|
||||
@@ -82,7 +84,7 @@ export const EntityContentBlueprint = createExtensionBlueprint({
|
||||
group?: keyof typeof defaultEntityContentGroups | (string & {});
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
},
|
||||
{ node, config },
|
||||
) {
|
||||
|
||||
@@ -25,9 +25,11 @@ import {
|
||||
EntityCardType,
|
||||
} from './extensionData';
|
||||
import { JSX } from 'react';
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import {
|
||||
FilterPredicate,
|
||||
createZodV3FilterPredicateSchema,
|
||||
} from '@backstage/filter-predicates';
|
||||
import { resolveEntityFilterData } from './resolveEntityFilterData';
|
||||
import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
/** @alpha */
|
||||
@@ -62,7 +64,7 @@ export const EntityContentLayoutBlueprint = createExtensionBlueprint({
|
||||
schema: {
|
||||
type: z => z.string().optional(),
|
||||
filter: z =>
|
||||
z.union([z.string(), createEntityPredicateSchema(z)]).optional(),
|
||||
z.union([z.string(), createZodV3FilterPredicateSchema(z)]).optional(),
|
||||
},
|
||||
},
|
||||
*factory(
|
||||
@@ -70,7 +72,7 @@ export const EntityContentLayoutBlueprint = createExtensionBlueprint({
|
||||
loader,
|
||||
filter,
|
||||
}: {
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
loader: () => Promise<(props: EntityContentLayoutProps) => JSX.Element>;
|
||||
},
|
||||
{ node, config },
|
||||
|
||||
@@ -24,11 +24,13 @@ import MenuItem from '@material-ui/core/MenuItem';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import { useEntityContextMenu } from '../../hooks/useEntityContextMenu';
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import { entityPredicateToFilterFunction } from '../predicates/entityPredicateToFilterFunction';
|
||||
import {
|
||||
FilterPredicate,
|
||||
filterPredicateToFilterFunction,
|
||||
createZodV3FilterPredicateSchema,
|
||||
} from '@backstage/filter-predicates';
|
||||
import type { Entity } from '@backstage/catalog-model';
|
||||
import { entityFilterFunctionDataRef } from './extensionData';
|
||||
import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema';
|
||||
/** @alpha */
|
||||
export type UseProps = () =>
|
||||
| {
|
||||
@@ -46,7 +48,7 @@ export type UseProps = () =>
|
||||
export type EntityContextMenuItemParams = {
|
||||
useProps: UseProps;
|
||||
icon: JSX.Element;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
@@ -62,7 +64,7 @@ export const EntityContextMenuItemBlueprint = createExtensionBlueprint({
|
||||
},
|
||||
config: {
|
||||
schema: {
|
||||
filter: z => createEntityPredicateSchema(z).optional(),
|
||||
filter: z => createZodV3FilterPredicateSchema(z).optional(),
|
||||
},
|
||||
},
|
||||
*factory(params: EntityContextMenuItemParams, { node, config }) {
|
||||
@@ -98,13 +100,13 @@ export const EntityContextMenuItemBlueprint = createExtensionBlueprint({
|
||||
|
||||
if (config.filter) {
|
||||
yield entityFilterFunctionDataRef(
|
||||
entityPredicateToFilterFunction(config.filter),
|
||||
filterPredicateToFilterFunction(config.filter),
|
||||
);
|
||||
} else if (typeof params.filter === 'function') {
|
||||
yield entityFilterFunctionDataRef(params.filter);
|
||||
} else if (params.filter) {
|
||||
yield entityFilterFunctionDataRef(
|
||||
entityPredicateToFilterFunction(params.filter),
|
||||
filterPredicateToFilterFunction(params.filter),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,10 +19,12 @@ import {
|
||||
coreExtensionData,
|
||||
ExtensionBoundary,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import {
|
||||
FilterPredicate,
|
||||
createZodV3FilterPredicateSchema,
|
||||
} from '@backstage/filter-predicates';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { resolveEntityFilterData } from './resolveEntityFilterData';
|
||||
import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema';
|
||||
import {
|
||||
entityFilterExpressionDataRef,
|
||||
entityFilterFunctionDataRef,
|
||||
@@ -38,7 +40,7 @@ export const EntityHeaderBlueprint = createExtensionBlueprint({
|
||||
},
|
||||
config: {
|
||||
schema: {
|
||||
filter: z => createEntityPredicateSchema(z).optional(),
|
||||
filter: z => createZodV3FilterPredicateSchema(z).optional(),
|
||||
},
|
||||
},
|
||||
output: [
|
||||
@@ -49,7 +51,7 @@ export const EntityHeaderBlueprint = createExtensionBlueprint({
|
||||
*factory(
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
},
|
||||
{ node, config },
|
||||
) {
|
||||
|
||||
@@ -20,8 +20,10 @@ import {
|
||||
createExtensionDataRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import { createEntityPredicateSchema } from '../predicates/createEntityPredicateSchema';
|
||||
import {
|
||||
FilterPredicate,
|
||||
createZodV3FilterPredicateSchema,
|
||||
} from '@backstage/filter-predicates';
|
||||
|
||||
import {
|
||||
entityFilterExpressionDataRef,
|
||||
@@ -54,13 +56,13 @@ export const EntityIconLinkBlueprint = createExtensionBlueprint({
|
||||
schema: {
|
||||
label: z => z.string().optional(),
|
||||
title: z => z.string().optional(),
|
||||
filter: z => createEntityPredicateSchema(z).optional(),
|
||||
filter: z => createZodV3FilterPredicateSchema(z).optional(),
|
||||
},
|
||||
},
|
||||
*factory(
|
||||
params: {
|
||||
useProps: () => Omit<IconLinkVerticalProps, 'color'>;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
},
|
||||
{ config, node },
|
||||
) {
|
||||
|
||||
@@ -19,15 +19,15 @@ import {
|
||||
entityFilterFunctionDataRef,
|
||||
} from './extensionData';
|
||||
import {
|
||||
EntityPredicate,
|
||||
entityPredicateToFilterFunction,
|
||||
} from '../predicates';
|
||||
FilterPredicate,
|
||||
filterPredicateToFilterFunction,
|
||||
} from '@backstage/filter-predicates';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { AppNode } from '@backstage/frontend-plugin-api';
|
||||
|
||||
export function* resolveEntityFilterData(
|
||||
filter: ((entity: Entity) => boolean) | EntityPredicate | string | undefined,
|
||||
config: { filter?: EntityPredicate | string },
|
||||
filter: ((entity: Entity) => boolean) | FilterPredicate | string | undefined,
|
||||
config: { filter?: FilterPredicate | string },
|
||||
node: AppNode,
|
||||
) {
|
||||
if (typeof config.filter === 'string') {
|
||||
@@ -38,7 +38,7 @@ export function* resolveEntityFilterData(
|
||||
yield entityFilterExpressionDataRef(config.filter);
|
||||
} else if (config.filter) {
|
||||
yield entityFilterFunctionDataRef(
|
||||
entityPredicateToFilterFunction(config.filter),
|
||||
filterPredicateToFilterFunction(config.filter),
|
||||
);
|
||||
} else if (typeof filter === 'function') {
|
||||
yield entityFilterFunctionDataRef(filter);
|
||||
@@ -49,6 +49,6 @@ export function* resolveEntityFilterData(
|
||||
);
|
||||
yield entityFilterExpressionDataRef(filter);
|
||||
} else if (filter) {
|
||||
yield entityFilterFunctionDataRef(entityPredicateToFilterFunction(filter));
|
||||
yield entityFilterFunctionDataRef(filterPredicateToFilterFunction(filter));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
import { EntityCardBlueprint } from '../blueprints/EntityCardBlueprint';
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityCardType } from '../blueprints/extensionData';
|
||||
|
||||
@@ -29,7 +29,7 @@ export function convertLegacyEntityCardExtension(
|
||||
LegacyExtension: ComponentType<{}>,
|
||||
overrides?: {
|
||||
name?: string;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
},
|
||||
): ExtensionDefinition {
|
||||
|
||||
@@ -28,7 +28,7 @@ import kebabCase from 'lodash/kebabCase';
|
||||
import startCase from 'lodash/startCase';
|
||||
import { ComponentType } from 'react';
|
||||
import { EntityContentBlueprint } from '../blueprints/EntityContentBlueprint';
|
||||
import { EntityPredicate } from '../predicates/types';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
/** @alpha */
|
||||
@@ -36,7 +36,7 @@ export function convertLegacyEntityContentExtension(
|
||||
LegacyExtension: ComponentType<{}>,
|
||||
overrides?: {
|
||||
name?: string;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
path?: string;
|
||||
title?: string;
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
export * from './blueprints';
|
||||
export * from './converters';
|
||||
export * from './predicates';
|
||||
export { catalogReactTranslationRef } from '../translation';
|
||||
export { isOwnerOf } from '../utils/isOwnerOf';
|
||||
export { useEntityPermission } from '../hooks/useEntityPermission';
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
|
||||
/** @alpha */
|
||||
export type EntityPredicate =
|
||||
| EntityPredicateExpression
|
||||
| EntityPredicatePrimitive
|
||||
| { $all: EntityPredicate[] }
|
||||
| { $any: EntityPredicate[] }
|
||||
| { $not: EntityPredicate };
|
||||
|
||||
/** @alpha */
|
||||
export type EntityPredicateExpression = {
|
||||
[KPath in string]: EntityPredicateValue;
|
||||
} & {
|
||||
[KPath in `$${string}`]: never;
|
||||
};
|
||||
|
||||
/** @alpha */
|
||||
export type EntityPredicateValue =
|
||||
| EntityPredicatePrimitive
|
||||
| { $exists: boolean }
|
||||
| { $in: EntityPredicatePrimitive[] }
|
||||
| { $contains: EntityPredicate };
|
||||
|
||||
/** @alpha */
|
||||
export type EntityPredicatePrimitive = string | number | boolean;
|
||||
@@ -12,11 +12,11 @@ import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityCardType } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityContentLayoutProps } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityContextMenuItemParams } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionInput } from '@backstage/frontend-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { IconComponent } from '@backstage/frontend-plugin-api';
|
||||
import { IconLinkVerticalProps } from '@backstage/core-components';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
@@ -292,11 +292,11 @@ const _default: OverridableFrontendPlugin<
|
||||
}>;
|
||||
'entity-card:catalog/about': OverridableExtensionDefinition<{
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -354,7 +354,7 @@ const _default: OverridableFrontendPlugin<
|
||||
name: 'about';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -362,11 +362,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'depends-on-components';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -395,7 +395,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -403,11 +403,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'depends-on-resources';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -436,7 +436,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -444,11 +444,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'has-components';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -477,7 +477,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -485,11 +485,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'has-resources';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -518,7 +518,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -526,11 +526,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'has-subcomponents';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -559,7 +559,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -567,11 +567,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'has-subdomains';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -600,7 +600,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -608,11 +608,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'has-systems';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -641,7 +641,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -649,11 +649,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'labels';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -682,7 +682,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -690,11 +690,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'links';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -723,7 +723,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -731,11 +731,11 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
path: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
group: string | false | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
title?: string | undefined;
|
||||
path?: string | undefined;
|
||||
group?: string | false | undefined;
|
||||
@@ -840,17 +840,17 @@ const _default: OverridableFrontendPlugin<
|
||||
group?: keyof defaultEntityContentGroups | (string & {});
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef_2;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'entity-context-menu-item:catalog/copy-entity-url': OverridableExtensionDefinition<{
|
||||
kind: 'entity-context-menu-item';
|
||||
name: 'copy-entity-url';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
};
|
||||
output:
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
@@ -868,10 +868,10 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-context-menu-item';
|
||||
name: 'inspect-entity';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
};
|
||||
output:
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
@@ -889,10 +889,10 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-context-menu-item';
|
||||
name: 'unregister-entity';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
};
|
||||
output:
|
||||
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
|
||||
@@ -912,10 +912,10 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
};
|
||||
@@ -942,7 +942,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
useProps: () => Omit<IconLinkVerticalProps, 'color'>;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'nav-item:catalog': OverridableExtensionDefinition<{
|
||||
|
||||
@@ -8,9 +8,9 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
|
||||
import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
@@ -91,11 +91,11 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
path: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
group: string | false | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
title?: string | undefined;
|
||||
path?: string | undefined;
|
||||
group?: string | false | undefined;
|
||||
@@ -142,7 +142,7 @@ const _default: OverridableFrontendPlugin<
|
||||
group?: keyof defaultEntityContentGroups | (string & {});
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef_2;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'page:kubernetes': OverridableExtensionDefinition<{
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
```ts
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityCardType } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
|
||||
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
|
||||
@@ -24,11 +24,11 @@ const _default: OverridableFrontendPlugin<
|
||||
kind: 'entity-card';
|
||||
name: 'group-profile';
|
||||
config: {
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -57,7 +57,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -65,13 +65,13 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
initialRelationAggregation: 'direct' | 'aggregated' | undefined;
|
||||
showAggregateMembersToggle: boolean | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
showAggregateMembersToggle?: boolean | undefined;
|
||||
initialRelationAggregation?: 'direct' | 'aggregated' | undefined;
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -102,7 +102,7 @@ const _default: OverridableFrontendPlugin<
|
||||
name: 'members-list';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -111,14 +111,14 @@ const _default: OverridableFrontendPlugin<
|
||||
initialRelationAggregation: 'direct' | 'aggregated' | undefined;
|
||||
showAggregateMembersToggle: boolean | undefined;
|
||||
ownedKinds: string[] | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
showAggregateMembersToggle?: boolean | undefined;
|
||||
initialRelationAggregation?: 'direct' | 'aggregated' | undefined;
|
||||
ownedKinds?: string[] | undefined;
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -149,7 +149,7 @@ const _default: OverridableFrontendPlugin<
|
||||
name: 'ownership';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
@@ -157,13 +157,13 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
maxRelations: number | undefined;
|
||||
hideIcons: boolean;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
type: 'content' | 'info' | undefined;
|
||||
};
|
||||
configInput: {
|
||||
hideIcons?: boolean | undefined;
|
||||
maxRelations?: number | undefined;
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
type?: 'content' | 'info' | undefined;
|
||||
};
|
||||
output:
|
||||
@@ -194,7 +194,7 @@ const _default: OverridableFrontendPlugin<
|
||||
name: 'user-profile';
|
||||
params: {
|
||||
loader: () => Promise<JSX.Element>;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
type?: EntityCardType;
|
||||
};
|
||||
}>;
|
||||
|
||||
@@ -10,12 +10,12 @@ import { ApiRef } from '@backstage/frontend-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionInput } from '@backstage/frontend-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { FormField } from '@backstage/plugin-scaffolder-react/alpha';
|
||||
import type { FormProps as FormProps_2 } from '@rjsf/core';
|
||||
import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react';
|
||||
@@ -137,10 +137,10 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
};
|
||||
@@ -167,7 +167,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
useProps: () => Omit<IconLinkVerticalProps, 'color'>;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'nav-item:scaffolder': OverridableExtensionDefinition<{
|
||||
|
||||
@@ -9,10 +9,10 @@ import { ApiFactory } from '@backstage/frontend-plugin-api';
|
||||
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
|
||||
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
|
||||
import { ExtensionInput } from '@backstage/frontend-plugin-api';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
import { IconComponent } from '@backstage/frontend-plugin-api';
|
||||
import { IconLinkVerticalProps } from '@backstage/core-components';
|
||||
import { JSX as JSX_2 } from 'react';
|
||||
@@ -124,11 +124,11 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
path: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
group: string | false | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
title?: string | undefined;
|
||||
path?: string | undefined;
|
||||
group?: string | false | undefined;
|
||||
@@ -204,7 +204,7 @@ const _default: OverridableFrontendPlugin<
|
||||
group?: keyof defaultEntityContentGroups | (string & {});
|
||||
loader: () => Promise<JSX.Element>;
|
||||
routeRef?: RouteRef_2;
|
||||
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'entity-icon-link:techdocs/read-docs': OverridableExtensionDefinition<{
|
||||
@@ -213,10 +213,10 @@ const _default: OverridableFrontendPlugin<
|
||||
config: {
|
||||
label: string | undefined;
|
||||
title: string | undefined;
|
||||
filter: EntityPredicate | undefined;
|
||||
filter: FilterPredicate | undefined;
|
||||
};
|
||||
configInput: {
|
||||
filter?: EntityPredicate | undefined;
|
||||
filter?: FilterPredicate | undefined;
|
||||
label?: string | undefined;
|
||||
title?: string | undefined;
|
||||
};
|
||||
@@ -243,7 +243,7 @@ const _default: OverridableFrontendPlugin<
|
||||
inputs: {};
|
||||
params: {
|
||||
useProps: () => Omit<IconLinkVerticalProps, 'color'>;
|
||||
filter?: EntityPredicate | ((entity: Entity) => boolean);
|
||||
filter?: FilterPredicate | ((entity: Entity) => boolean);
|
||||
};
|
||||
}>;
|
||||
'nav-item:techdocs': OverridableExtensionDefinition<{
|
||||
|
||||
@@ -3816,6 +3816,19 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/filter-predicates@workspace:^, @backstage/filter-predicates@workspace:packages/filter-predicates":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/filter-predicates@workspace:packages/filter-predicates"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/config": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/types": "workspace:^"
|
||||
zod: "npm:^3.25.76"
|
||||
zod-validation-error: "npm:^4.0.2"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@backstage/frontend-app-api@workspace:^, @backstage/frontend-app-api@workspace:packages/frontend-app-api":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@backstage/frontend-app-api@workspace:packages/frontend-app-api"
|
||||
@@ -5391,6 +5404,7 @@ __metadata:
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/filter-predicates": "workspace:^"
|
||||
"@backstage/frontend-plugin-api": "workspace:^"
|
||||
"@backstage/frontend-test-utils": "workspace:^"
|
||||
"@backstage/integration-react": "workspace:^"
|
||||
|
||||
Reference in New Issue
Block a user