feat(catalog): Add predicate-based filtering to the by-refs endpoint
Adds support for predicate-based filtering (`$all`, `$any`, `$not`, `$exists`, `$in`, `$contains`, `$hasPrefix`) to the catalog `/entities/by-refs` endpoint via a `query` field in the request body. The existing `filter` query parameter behavior is preserved for backward compatibility. The `query` predicate is only used when explicitly provided, and when both `filter` and `query` are given, they are merged with `$all`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -236,6 +236,7 @@ export interface GetEntitiesByRefsRequest {
|
||||
entityRefs: string[];
|
||||
fields?: EntityFieldsQuery | undefined;
|
||||
filter?: EntityFilterQuery;
|
||||
query?: FilterPredicate;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -302,6 +302,103 @@ describe('CatalogClient', () => {
|
||||
|
||||
expect(response).toEqual({ items: [entity, undefined] });
|
||||
});
|
||||
|
||||
it('sends only query predicate in the body when query is provided without filter', async () => {
|
||||
expect.assertions(3);
|
||||
const entity = {
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'Test2',
|
||||
namespace: 'test1',
|
||||
},
|
||||
};
|
||||
server.use(
|
||||
rest.post(`${mockBaseUrl}/entities/by-refs`, async (req, res, ctx) => {
|
||||
expect(req.url.search).toBe('');
|
||||
await expect(req.json()).resolves.toEqual({
|
||||
entityRefs: ['k:n/a'],
|
||||
query: { kind: 'Component' },
|
||||
});
|
||||
return res(ctx.json({ items: [entity] }));
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await client.getEntitiesByRefs(
|
||||
{
|
||||
entityRefs: ['k:n/a'],
|
||||
query: { kind: 'Component' },
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
expect(response).toEqual({ items: [entity] });
|
||||
});
|
||||
|
||||
it('merges filter and query into $all predicate when both are provided', async () => {
|
||||
expect.assertions(4);
|
||||
const entity = {
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'Test2',
|
||||
namespace: 'test1',
|
||||
},
|
||||
};
|
||||
server.use(
|
||||
rest.post(`${mockBaseUrl}/entities/by-refs`, async (req, res, ctx) => {
|
||||
expect(req.url.search).toBe('');
|
||||
const body = await req.json();
|
||||
expect(body.entityRefs).toEqual(['k:n/a']);
|
||||
expect(body.query).toEqual({
|
||||
$all: [{ kind: 'Component' }, { kind: 'API' }],
|
||||
});
|
||||
return res(ctx.json({ items: [entity] }));
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await client.getEntitiesByRefs(
|
||||
{
|
||||
entityRefs: ['k:n/a'],
|
||||
query: { kind: 'Component' },
|
||||
filter: { kind: ['API'] },
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
expect(response).toEqual({ items: [entity] });
|
||||
});
|
||||
|
||||
it('sends filter as query parameter when only filter is provided (backward compat)', async () => {
|
||||
expect.assertions(4);
|
||||
const entity = {
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'Test2',
|
||||
namespace: 'test1',
|
||||
},
|
||||
};
|
||||
server.use(
|
||||
rest.post(`${mockBaseUrl}/entities/by-refs`, async (req, res, ctx) => {
|
||||
expect(req.url.search).toBe('?filter=kind%3DAPI');
|
||||
const body = await req.json();
|
||||
expect(body).toEqual({ entityRefs: ['k:n/a'] });
|
||||
expect(body.query).toBeUndefined();
|
||||
return res(ctx.json({ items: [entity] }));
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await client.getEntitiesByRefs(
|
||||
{
|
||||
entityRefs: ['k:n/a'],
|
||||
filter: { kind: ['API'] },
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
expect(response).toEqual({ items: [entity] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryEntities', () => {
|
||||
|
||||
@@ -237,11 +237,36 @@ export class CatalogClient implements CatalogApi {
|
||||
request: GetEntitiesByRefsRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<GetEntitiesByRefsResponse> {
|
||||
const { filter, query } = request;
|
||||
|
||||
// Only convert and merge if both filter and query are provided, or if
|
||||
// query alone is provided. When only filter is given, preserve the old
|
||||
// query-parameter behavior for backward compatibility.
|
||||
let filterPredicate: FilterPredicate | undefined;
|
||||
if (query !== undefined) {
|
||||
if (typeof query !== 'object' || query === null || Array.isArray(query)) {
|
||||
throw new InputError('Query must be an object');
|
||||
}
|
||||
filterPredicate = query;
|
||||
if (filter !== undefined) {
|
||||
const converted = convertFilterToPredicate(filter);
|
||||
filterPredicate = { $all: [filterPredicate, converted] };
|
||||
}
|
||||
}
|
||||
|
||||
const getOneChunk = async (refs: string[]) => {
|
||||
const response = await this.apiClient.getEntitiesByRefs(
|
||||
{
|
||||
body: { entityRefs: refs, fields: request.fields },
|
||||
query: { filter: this.getFilterValue(request.filter) },
|
||||
body: {
|
||||
entityRefs: refs,
|
||||
fields: request.fields,
|
||||
...(filterPredicate && {
|
||||
query: filterPredicate as unknown as { [key: string]: any },
|
||||
}),
|
||||
},
|
||||
query: filterPredicate
|
||||
? {}
|
||||
: { filter: this.getFilterValue(request.filter) },
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
+4
@@ -24,4 +24,8 @@
|
||||
export interface GetEntitiesByRefsRequest {
|
||||
entityRefs: Array<string>;
|
||||
fields?: Array<string>;
|
||||
/**
|
||||
* A type representing all allowed JSON object values.
|
||||
*/
|
||||
query?: { [key: string]: any };
|
||||
}
|
||||
|
||||
@@ -212,6 +212,16 @@ export interface GetEntitiesByRefsRequest {
|
||||
* If given, return only entities that match the given filter.
|
||||
*/
|
||||
filter?: EntityFilterQuery;
|
||||
/**
|
||||
* If given, return only entities that match the given predicate query.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Supports operators like `$all`, `$any`, `$not`, `$exists`, `$in`,
|
||||
* `$contains`, and `$hasPrefix`. When both `filter` and `query` are
|
||||
* provided, they are combined with `$all`.
|
||||
*/
|
||||
query?: FilterPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user