From d1b1306d98a51f4a6e9814fb18a4abdb7179c2e8 Mon Sep 17 00:00:00 2001 From: Will Date: Tue, 27 Apr 2021 15:36:01 +0100 Subject: [PATCH 1/5] 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 --- .changeset/twenty-peas-deny.md | 5 +++ .../catalog-client/src/CatalogClient.test.ts | 32 +++++++++++++++++++ packages/catalog-client/src/CatalogClient.ts | 25 ++++++++++----- packages/catalog-client/src/types.ts | 5 ++- 4 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 .changeset/twenty-peas-deny.md diff --git a/.changeset/twenty-peas-deny.md b/.changeset/twenty-peas-deny.md new file mode 100644 index 0000000000..61c9cb3587 --- /dev/null +++ b/.changeset/twenty-peas-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': minor +--- + +Allow `filter` param to be specified multiple times diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index 19da45e4ec..fefe670a55 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -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'], diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 3de25e9808..a246b3abab 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -56,18 +56,27 @@ export class CatalogClient implements CatalogApi { request?: CatalogEntitiesRequest, options?: CatalogRequestOptions, ): Promise> { - 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) { diff --git a/packages/catalog-client/src/types.ts b/packages/catalog-client/src/types.ts index 0d25bf7483..9035657ddc 100644 --- a/packages/catalog-client/src/types.ts +++ b/packages/catalog-client/src/types.ts @@ -17,7 +17,10 @@ import { Entity, EntityName, Location } from '@backstage/catalog-model'; export type CatalogEntitiesRequest = { - filter?: Record | undefined; + filter?: + | Record[] + | Record // Legacy type preserved for backwards compatibility + | undefined; fields?: string[] | undefined; }; From 17644ca68eb67dea67b1691f34181c61b811326a Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 28 Apr 2021 14:23:10 +0100 Subject: [PATCH 2/5] Don't treat singuler `filter` type as legacy Instead it's a convenience type for when only a single instance of a param is required. Signed-off-by: Will Sewell --- packages/catalog-client/src/CatalogClient.test.ts | 5 ++--- packages/catalog-client/src/types.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index fefe670a55..359e3a2c60 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -76,7 +76,7 @@ describe('CatalogClient', () => { expect(response).toEqual(defaultResponse); }); - it('builds entity search filters properly', async () => { + it('builds multiple entity search filters properly', async () => { expect.assertions(2); server.use( @@ -107,7 +107,7 @@ describe('CatalogClient', () => { expect(response.items).toEqual([]); }); - it('builds entity legacy search filters properly', async () => { + it('builds single entity search filter properly', async () => { expect.assertions(2); server.use( @@ -119,7 +119,6 @@ describe('CatalogClient', () => { const response = await client.getEntities( { - // The legacy value of filter is not an array filter: { a: '1', b: ['2', '3'], diff --git a/packages/catalog-client/src/types.ts b/packages/catalog-client/src/types.ts index 9035657ddc..ef907eafa9 100644 --- a/packages/catalog-client/src/types.ts +++ b/packages/catalog-client/src/types.ts @@ -19,7 +19,7 @@ import { Entity, EntityName, Location } from '@backstage/catalog-model'; export type CatalogEntitiesRequest = { filter?: | Record[] - | Record // Legacy type preserved for backwards compatibility + | Record | undefined; fields?: string[] | undefined; }; From bd877d4518ec75be2ce9d63ad6cd26da93d1808c Mon Sep 17 00:00:00 2001 From: Will Date: Wed, 28 Apr 2021 14:25:05 +0100 Subject: [PATCH 3/5] Make "patch"-level change instead of "minor" Signed-off-by: Will Sewell --- .changeset/twenty-peas-deny.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/twenty-peas-deny.md b/.changeset/twenty-peas-deny.md index 61c9cb3587..df335ea95a 100644 --- a/.changeset/twenty-peas-deny.md +++ b/.changeset/twenty-peas-deny.md @@ -1,5 +1,5 @@ --- -'@backstage/catalog-client': minor +'@backstage/catalog-client': patch --- Allow `filter` param to be specified multiple times From 86891fd2742b8b7624cb80619781aff0383b3385 Mon Sep 17 00:00:00 2001 From: Will Sewell Date: Thu, 29 Apr 2021 12:36:41 +0100 Subject: [PATCH 4/5] Rename 'param' -> 'parameter' in changeset Signed-off-by: Will Sewell --- .changeset/twenty-peas-deny.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/twenty-peas-deny.md b/.changeset/twenty-peas-deny.md index df335ea95a..d149a9337c 100644 --- a/.changeset/twenty-peas-deny.md +++ b/.changeset/twenty-peas-deny.md @@ -2,4 +2,4 @@ '@backstage/catalog-client': patch --- -Allow `filter` param to be specified multiple times +Allow `filter` parameter to be specified multiple times From 4e6168da41070c0f58d2f7bf95ed7a433a6e5f52 Mon Sep 17 00:00:00 2001 From: Will Sewell Date: Thu, 29 Apr 2021 16:16:24 +0100 Subject: [PATCH 5/5] Update public API docs Result of running `yarn build:api-reports`. Signed-off-by: Will Sewell --- packages/catalog-client/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index a17a6cdc10..4b489ff2e4 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -67,7 +67,7 @@ export class CatalogClient implements CatalogApi { // @public (undocumented) export type CatalogEntitiesRequest = { - filter?: Record | undefined; + filter?: Record[] | Record | undefined; fields?: string[] | undefined; };