From 8a97d472a3b27b74a9ef6db6402fae53092b9b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 21 Dec 2022 11:47:45 +0100 Subject: [PATCH] change to asc and desc prefixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/features/software-catalog/api.md | 23 +++++++++++++++ .../catalog-client/src/CatalogClient.test.ts | 4 ++- packages/catalog-client/src/CatalogClient.ts | 12 ++++---- .../request/parseEntityOrderParams.test.ts | 27 +++++++++-------- .../service/request/parseEntityOrderParams.ts | 29 ++++++------------- 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/docs/features/software-catalog/api.md b/docs/features/software-catalog/api.md index 3221b9e46a..57de9b7273 100644 --- a/docs/features/software-catalog/api.md +++ b/docs/features/software-catalog/api.md @@ -154,6 +154,29 @@ Some more real world usable examples: `/entities?fields=kind,metadata.namespace,metadata.name` +### Ordering + +By default the entities are returned in an undefined, but stable, order. You can +pass in one or more `order` query parameters to affect that ordering. + +Each parameter starts either with `asc:` for ascending lexicographical order or +`desc:` for descending (reverse) lexicographical order, followed by a +dot-separated path into an entity's keys. The ordering is case insensitive. If +more than one order directive is given, later directives have lower precedence +(they are applied only when directives of higher precedence have equal values). + +Example: + +```text +/entities?order=asc:kind&order=desc:metadata.name +``` + +This will order the output first by kind ascending, and then within each kind +(if there's more than one of a given kind) by their name descending. When given +a field that does NOT exist on all entities in the result set, those entities +that do not have the field will always be sorted last in that particular order +step, no matter what the desired order was. + #### Pagination You may pass the `offset` and `limit` query parameters to do classical diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index f87b12353d..8587a5a2e1 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -199,7 +199,9 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe('?order=kind&order=-metadata.name'); + expect(req.url.search).toBe( + '?order=asc:kind&order=desc:metadata.name', + ); return res(ctx.json([])); }), ); diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 0a5954d4bd..81f7343555 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -139,13 +139,11 @@ export class CatalogClient implements CatalogApi { if (order) { for (const directive of [order].flat()) { if (directive) { - // We could choose to always put in the + prefix, but it's not - // required (ascending sort is the default) and it always gets URL - // encoded to %2B which looks a bit less pretty - so we don't - const str = `${directive.order === 'desc' ? '-' : ''}${ - directive.field - }`; - params.push(`order=${encodeURIComponent(str)}`); + params.push( + `order=${encodeURIComponent(directive.order)}:${encodeURIComponent( + directive.field, + )}`, + ); } } } diff --git a/plugins/catalog-backend/src/service/request/parseEntityOrderParams.test.ts b/plugins/catalog-backend/src/service/request/parseEntityOrderParams.test.ts index 77cf73f57b..1433e6fd2b 100644 --- a/plugins/catalog-backend/src/service/request/parseEntityOrderParams.test.ts +++ b/plugins/catalog-backend/src/service/request/parseEntityOrderParams.test.ts @@ -22,22 +22,21 @@ describe('parseEntityOrderParams', () => { }); it('handles parameters with various orders', () => { - expect(parseEntityOrderParams({ order: ['a', '+b', '-c'] })).toEqual([ + expect( + parseEntityOrderParams({ order: ['asc:a', 'desc:b', 'asc:c:d'] }), + ).toEqual([ { field: 'a', order: 'asc' }, - { field: 'b', order: 'asc' }, - { field: 'c', order: 'desc' }, + { field: 'b', order: 'desc' }, + { field: 'c:d', order: 'asc' }, ]); }); - it('rejects missing order or key', () => { - expect(() => parseEntityOrderParams({ order: [''] })).toThrow( - 'Invalid order parameter "", no field given', - ); - expect(() => parseEntityOrderParams({ order: ['+'] })).toThrow( - 'Invalid order parameter "+", no field given', - ); - expect(() => parseEntityOrderParams({ order: ['-'] })).toThrow( - 'Invalid order parameter "-", no field given', - ); - }); + it.each(['', ':', 'ascii:', 'ascii:ebcdic', ':colon'])( + 'rejects missing/bad order or key, %p', + order => { + expect(() => parseEntityOrderParams({ order: [order] })).toThrow( + `Invalid order parameter "${order}", expected ":"`, + ); + }, + ); }); diff --git a/plugins/catalog-backend/src/service/request/parseEntityOrderParams.ts b/plugins/catalog-backend/src/service/request/parseEntityOrderParams.ts index d10119fd47..ff81a91ca5 100644 --- a/plugins/catalog-backend/src/service/request/parseEntityOrderParams.ts +++ b/plugins/catalog-backend/src/service/request/parseEntityOrderParams.ts @@ -22,27 +22,16 @@ export function parseEntityOrderParams( params: Record, ): EntityOrder[] | undefined { return parseStringsParam(params.order, 'order')?.map(item => { - let order: 'asc' | 'desc'; - let field: string; - switch (item[0]) { - case '+': - order = 'asc'; - field = item.slice(1).trim(); - break; - case '-': - order = 'desc'; - field = item.slice(1).trim(); - break; - default: - order = 'asc'; - field = item.trim(); - break; + const match = item.match(/^(asc|desc):(.+)$/); + if (!match) { + throw new InputError( + `Invalid order parameter "${item}", expected ":"`, + ); } - if (!field) { - throw new InputError(`Invalid order parameter "${item}", no field given`); - } - - return { field, order }; + return { + order: match[1] as 'asc' | 'desc', + field: match[2], + }; }); }