refactor(catalog): use @backstage/filter-predicates package types
- Replace EntityPredicate with FilterPredicate - update package.json and yarn.lock - Add InputError handling for invalid predicate values in filter application - Update catalog-client utils.ts to use browser-compatible atob() for base64 decoding Signed-off-by: Nilay1999 <nilayparmar19@gmail.com>
This commit is contained in:
@@ -32,7 +32,8 @@ export function isQueryEntitiesInitialRequest(
|
||||
*/
|
||||
export function cursorContainsQuery(cursor: string): boolean {
|
||||
try {
|
||||
const decoded = JSON.parse(Buffer.from(cursor, 'base64').toString('utf8'));
|
||||
// Use browser-compatible base64 decoding
|
||||
const decoded = JSON.parse(atob(cursor));
|
||||
return !!decoded.query;
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityFilter, EntityPredicate } from '@backstage/plugin-catalog-node';
|
||||
import { EntityFilter } from '@backstage/plugin-catalog-node';
|
||||
import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
|
||||
/**
|
||||
* A pagination rule for entities.
|
||||
@@ -53,7 +54,7 @@ export type EntitiesRequest = {
|
||||
};
|
||||
|
||||
export type EntityPredicateRequest = {
|
||||
query?: EntityPredicate;
|
||||
query?: FilterPredicate;
|
||||
order?: EntityOrder[];
|
||||
pagination?: EntityPagination;
|
||||
credentials: BackstageCredentials;
|
||||
@@ -232,7 +233,7 @@ export interface QueryEntitiesInitialRequest {
|
||||
* Predicate-based query for filtering entities.
|
||||
* Mutually exclusive with filter.
|
||||
*/
|
||||
query?: EntityPredicate;
|
||||
query?: FilterPredicate;
|
||||
orderFields?: EntityOrder[];
|
||||
fullTextFilter?: {
|
||||
term: string;
|
||||
@@ -298,7 +299,7 @@ export type Cursor = {
|
||||
* A predicate-based query to be applied to the full list of entities.
|
||||
* Mutually exclusive with filter.
|
||||
*/
|
||||
query?: EntityPredicate;
|
||||
query?: FilterPredicate;
|
||||
/**
|
||||
* true if the cursor is a previous cursor.
|
||||
*/
|
||||
|
||||
@@ -30,10 +30,8 @@ import {
|
||||
} from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { InputError, serializeError } from '@backstage/errors';
|
||||
import {
|
||||
EntityPredicate,
|
||||
LocationAnalyzer,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
import { parseFilterPredicate } from '@backstage/filter-predicates';
|
||||
import { LocationAnalyzer } from '@backstage/plugin-catalog-node';
|
||||
import express from 'express';
|
||||
import yn from 'yn';
|
||||
import { z } from 'zod';
|
||||
@@ -272,7 +270,10 @@ export async function createRouter(
|
||||
});
|
||||
|
||||
try {
|
||||
const query = req.body.query as EntityPredicate | undefined;
|
||||
// Validate the query using the Zod schema from @backstage/filter-predicates
|
||||
const query = req.body.query
|
||||
? parseFilterPredicate(req.body.query)
|
||||
: undefined;
|
||||
const order = parseEntityOrderFieldParams(req.query);
|
||||
const pagination = parseEntityPaginationParams(req.query);
|
||||
const credentials = await httpAuth.credentials(req);
|
||||
|
||||
@@ -15,32 +15,33 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
EntityPredicate,
|
||||
EntityPredicatePrimitive,
|
||||
EntityPredicateValue,
|
||||
} from '@backstage/plugin-catalog-node';
|
||||
FilterPredicate,
|
||||
FilterPredicatePrimitive,
|
||||
FilterPredicateValue,
|
||||
} from '@backstage/filter-predicates';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { Knex } from 'knex';
|
||||
import { DbSearchRow } from '../../database/tables';
|
||||
|
||||
function isAllPredicate(
|
||||
filter: EntityPredicate,
|
||||
): filter is { $all: EntityPredicate[] } {
|
||||
filter: FilterPredicate,
|
||||
): filter is { $all: FilterPredicate[] } {
|
||||
return typeof filter === 'object' && filter !== null && '$all' in filter;
|
||||
}
|
||||
|
||||
function isAnyPredicate(
|
||||
filter: EntityPredicate,
|
||||
): filter is { $any: EntityPredicate[] } {
|
||||
filter: FilterPredicate,
|
||||
): filter is { $any: FilterPredicate[] } {
|
||||
return typeof filter === 'object' && filter !== null && '$any' in filter;
|
||||
}
|
||||
|
||||
function isNotPredicate(
|
||||
filter: EntityPredicate,
|
||||
): filter is { $not: EntityPredicate } {
|
||||
filter: FilterPredicate,
|
||||
): filter is { $not: FilterPredicate } {
|
||||
return typeof filter === 'object' && filter !== null && '$not' in filter;
|
||||
}
|
||||
|
||||
function isPrimitive(value: unknown): value is EntityPredicatePrimitive {
|
||||
function isPrimitive(value: unknown): value is FilterPredicatePrimitive {
|
||||
return (
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'number' ||
|
||||
@@ -49,18 +50,18 @@ function isPrimitive(value: unknown): value is EntityPredicatePrimitive {
|
||||
}
|
||||
|
||||
function isExistsValue(
|
||||
value: EntityPredicateValue,
|
||||
value: FilterPredicateValue,
|
||||
): value is { $exists: boolean } {
|
||||
return typeof value === 'object' && value !== null && '$exists' in value;
|
||||
}
|
||||
|
||||
function isInValue(
|
||||
value: EntityPredicateValue,
|
||||
): value is { $in: EntityPredicatePrimitive[] } {
|
||||
value: FilterPredicateValue,
|
||||
): value is { $in: FilterPredicatePrimitive[] } {
|
||||
return typeof value === 'object' && value !== null && '$in' in value;
|
||||
}
|
||||
|
||||
function isFieldExpression(filter: EntityPredicate): boolean {
|
||||
function isFieldExpression(filter: FilterPredicate): boolean {
|
||||
if (typeof filter !== 'object' || filter === null) {
|
||||
return false;
|
||||
}
|
||||
@@ -109,7 +110,7 @@ function isFieldExpression(filter: EntityPredicate): boolean {
|
||||
*/
|
||||
|
||||
function applyPredicateInStrategy(
|
||||
filter: EntityPredicate,
|
||||
filter: FilterPredicate,
|
||||
targetQuery: Knex.QueryBuilder,
|
||||
onEntityIdField: string,
|
||||
knex: Knex,
|
||||
@@ -211,6 +212,13 @@ function applyPredicateInStrategy(
|
||||
value: String(value).toLowerCase(),
|
||||
});
|
||||
this.andWhere(onEntityIdField, 'in', matchQuery);
|
||||
} else {
|
||||
// Reject unsupported/invalid predicate values
|
||||
throw new InputError(
|
||||
`Invalid filter predicate value for field "${key}": expected a primitive value, $exists, or $in operator, but got ${JSON.stringify(
|
||||
value,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -221,7 +229,7 @@ function applyPredicateInStrategy(
|
||||
}
|
||||
|
||||
export function applyPredicateEntityFilterToQuery(options: {
|
||||
filter: EntityPredicate;
|
||||
filter: FilterPredicate;
|
||||
targetQuery: Knex.QueryBuilder;
|
||||
onEntityIdField: string;
|
||||
knex: Knex;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { InputError, NotAllowedError } from '@backstage/errors';
|
||||
import { createZodV3FilterPredicateSchema } from '@backstage/filter-predicates';
|
||||
import { Request } from 'express';
|
||||
import lodash from 'lodash';
|
||||
import { z } from 'zod';
|
||||
@@ -109,6 +110,8 @@ const entityFilterParser: z.ZodSchema<EntityFilter> = z.lazy(() =>
|
||||
.or(z.object({ allOf: z.array(entityFilterParser) })),
|
||||
);
|
||||
|
||||
const filterPredicateSchema = createZodV3FilterPredicateSchema(z);
|
||||
|
||||
export const cursorParser: z.ZodSchema<Cursor> = z.object({
|
||||
orderFields: z.array(
|
||||
z.object({ field: z.string(), order: z.enum(['asc', 'desc']) }),
|
||||
@@ -122,7 +125,7 @@ export const cursorParser: z.ZodSchema<Cursor> = z.object({
|
||||
orderFieldValues: z.array(z.string().or(z.null())),
|
||||
filter: entityFilterParser.optional(),
|
||||
isPrevious: z.boolean(),
|
||||
query: z.any().optional(),
|
||||
query: filterPredicateSchema.optional(),
|
||||
firstSortFieldValues: z.array(z.string().or(z.null())).optional(),
|
||||
totalItems: z.number().optional(),
|
||||
});
|
||||
|
||||
@@ -85,69 +85,3 @@ export type EntitiesSearchFilter = {
|
||||
*/
|
||||
values?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines a predicate used to filter entities using logical and comparison operators.
|
||||
*
|
||||
* This type supports:
|
||||
* - Primitive equality matching
|
||||
* - Field existence checks
|
||||
* - Inclusion checks
|
||||
* - Logical composition using $all, $any, and $not
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityPredicate =
|
||||
| EntityPredicateExpression
|
||||
| EntityPredicatePrimitive
|
||||
| { $all: EntityPredicate[] }
|
||||
| { $any: EntityPredicate[] }
|
||||
| { $not: EntityPredicate };
|
||||
|
||||
/**
|
||||
* Represents a field-based predicate expression.
|
||||
*
|
||||
* Each key is treated as a JSON path into the entity object.
|
||||
* Keys starting with `$` are reserved for operators and are not allowed.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* {
|
||||
* "spec.type": "service",
|
||||
* "metadata.name": { $in: ["payments", "orders"] }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityPredicateExpression = {
|
||||
/** JSON path of the entity field */
|
||||
[KPath in string]: EntityPredicateValue;
|
||||
} & {
|
||||
/** Operator keys are not allowed as field paths */
|
||||
[KPath in `$${string}`]: never;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a value condition for a predicate field.
|
||||
*
|
||||
* Supported forms:
|
||||
* - Primitive equality match
|
||||
* - Existence check using `$exists`
|
||||
* - Inclusion check using `$in`
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityPredicateValue =
|
||||
| EntityPredicatePrimitive
|
||||
| { $exists: boolean }
|
||||
| { $in: EntityPredicatePrimitive[] };
|
||||
|
||||
/**
|
||||
* Represents a primitive predicate value.
|
||||
*
|
||||
* Used for direct equality comparison.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type EntityPredicatePrimitive = string | number | boolean;
|
||||
|
||||
@@ -20,10 +20,6 @@ export type {
|
||||
LocationSpec,
|
||||
EntitiesSearchFilter,
|
||||
EntityFilter,
|
||||
EntityPredicate,
|
||||
EntityPredicateExpression,
|
||||
EntityPredicateValue,
|
||||
EntityPredicatePrimitive,
|
||||
} from './common';
|
||||
export type {
|
||||
CatalogProcessor,
|
||||
|
||||
Reference in New Issue
Block a user