catalog-client: allow filter param to be specified multiple times
This is possible on the backend, see: - https://github.com/backstage/backstage/blob/f9d077f5f6c3bf1e20255359798dd1febbeb3e27/plugins/catalog-backend/src/database/types.ts#L129-L131 - https://github.com/backstage/backstage/blob/f9d077f5f6c3bf1e20255359798dd1febbeb3e27/plugins/catalog-backend/src/service/request/parseEntityFilterParams.ts#L25-L42 Signed-off-by: Will Sewell <willsewell@monzo.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-client': minor
|
||||
---
|
||||
|
||||
Allow `filter` param to be specified multiple times
|
||||
@@ -79,6 +79,37 @@ describe('CatalogClient', () => {
|
||||
it('builds entity search filters properly', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => {
|
||||
expect(req.url.search).toBe(
|
||||
'?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2',
|
||||
);
|
||||
return res(ctx.json([]));
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await client.getEntities(
|
||||
{
|
||||
filter: [
|
||||
{
|
||||
a: '1',
|
||||
b: ['2', '3'],
|
||||
ö: '=',
|
||||
},
|
||||
{
|
||||
a: '2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ token },
|
||||
);
|
||||
|
||||
expect(response.items).toEqual([]);
|
||||
});
|
||||
|
||||
it('builds entity legacy search filters properly', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => {
|
||||
expect(req.url.search).toBe('?filter=a=1,b=2,b=3,%C3%B6=%3D');
|
||||
@@ -88,6 +119,7 @@ describe('CatalogClient', () => {
|
||||
|
||||
const response = await client.getEntities(
|
||||
{
|
||||
// The legacy value of filter is not an array
|
||||
filter: {
|
||||
a: '1',
|
||||
b: ['2', '3'],
|
||||
|
||||
@@ -56,18 +56,27 @@ export class CatalogClient implements CatalogApi {
|
||||
request?: CatalogEntitiesRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogListResponse<Entity>> {
|
||||
const { filter = {}, fields = [] } = request ?? {};
|
||||
const { filter = [], fields = [] } = request ?? {};
|
||||
const filterItems = [filter].flat();
|
||||
const params: string[] = [];
|
||||
|
||||
const filterParts: string[] = [];
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
for (const v of [value].flat()) {
|
||||
filterParts.push(`${encodeURIComponent(key)}=${encodeURIComponent(v)}`);
|
||||
// filter param can occur multiple times, for example
|
||||
// /api/catalog/entities?filter=metadata.name=wayback-search,kind=component&filter=metadata.name=www-artist,kind=component'
|
||||
// the "outer array" defined by `filter` occurrences corresponds to "anyOf" filters
|
||||
// the "inner array" defined within a `filter` param corresponds to "allOf" filters
|
||||
for (const filterItem of filterItems) {
|
||||
const filterParts: string[] = [];
|
||||
for (const [key, value] of Object.entries(filterItem)) {
|
||||
for (const v of [value].flat()) {
|
||||
filterParts.push(
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(v)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filterParts.length) {
|
||||
params.push(`filter=${filterParts.join(',')}`);
|
||||
if (filterParts.length) {
|
||||
params.push(`filter=${filterParts.join(',')}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.length) {
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
import { Entity, EntityName, Location } from '@backstage/catalog-model';
|
||||
|
||||
export type CatalogEntitiesRequest = {
|
||||
filter?: Record<string, string | string[]> | undefined;
|
||||
filter?:
|
||||
| Record<string, string | string[]>[]
|
||||
| Record<string, string | string[]> // Legacy type preserved for backwards compatibility
|
||||
| undefined;
|
||||
fields?: string[] | undefined;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user