use a structured orderBy for ordering

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2026-02-20 10:35:15 +01:00
parent c01f240069
commit f0876da49f
12 changed files with 122 additions and 24 deletions
@@ -751,9 +751,11 @@ describe('CatalogClient', () => {
expect(mockedEndpoint).toHaveBeenCalledTimes(1);
});
it('should send orderFields with correct format (field,order)', async () => {
it('should send orderFields with correct format', async () => {
const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
expect(req.body.orderField).toEqual(['metadata.name,asc']);
expect(req.body.orderBy).toEqual([
{ field: 'metadata.name', order: 'asc' },
]);
return res(ctx.json(defaultResponse));
});
@@ -769,9 +771,9 @@ describe('CatalogClient', () => {
it('should send multiple orderFields with correct format', async () => {
const mockedEndpoint = jest.fn().mockImplementation((req, res, ctx) => {
expect(req.body.orderField).toEqual([
'metadata.name,asc',
'spec.type,desc',
expect(req.body.orderBy).toEqual([
{ field: 'metadata.name', order: 'asc' },
{ field: 'spec.type', order: 'desc' },
]);
return res(ctx.json(defaultResponse));
});
+1 -3
View File
@@ -368,9 +368,7 @@ export class CatalogClient implements CatalogApi {
body.limit = limit;
}
if (orderFields !== undefined) {
body.orderField = (
Array.isArray(orderFields) ? orderFields : [orderFields]
).map(({ field, order }) => `${field},${order}`);
body.orderBy = [orderFields].flat();
}
if (fullTextFilter) {
body.fullTextFilter = fullTextFilter;
@@ -19,6 +19,7 @@
// ******************************************************************
import { QueryEntitiesByPredicateRequestFullTextFilter } from '../models/QueryEntitiesByPredicateRequestFullTextFilter.model';
import { QueryEntitiesByPredicateRequestOrderByInner } from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
/**
* @public
@@ -26,7 +27,7 @@ import { QueryEntitiesByPredicateRequestFullTextFilter } from '../models/QueryEn
export interface QueryEntitiesByPredicateRequest {
cursor?: string;
limit?: number;
orderField?: Array<string>;
orderBy?: Array<QueryEntitiesByPredicateRequestOrderByInner>;
fullTextFilter?: QueryEntitiesByPredicateRequestFullTextFilter;
fields?: Array<string>;
/**
@@ -0,0 +1,34 @@
/*
* Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface QueryEntitiesByPredicateRequestOrderByInner {
field: string;
order: QueryEntitiesByPredicateRequestOrderByInnerOrderEnum;
}
/**
* @public
*/
export type QueryEntitiesByPredicateRequestOrderByInnerOrderEnum =
| 'asc'
| 'desc';
@@ -47,6 +47,7 @@ export * from '../models/ModelError.model';
export * from '../models/NullableEntity.model';
export * from '../models/QueryEntitiesByPredicateRequest.model';
export * from '../models/QueryEntitiesByPredicateRequestFullTextFilter.model';
export * from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
export * from '../models/RecursivePartialEntity.model';
export * from '../models/RecursivePartialEntityMeta.model';
export * from '../models/RecursivePartialEntityMetaAllOf.model';
@@ -1125,10 +1125,21 @@ paths:
type: string
limit:
type: number
orderField:
orderBy:
type: array
items:
type: string
type: object
required:
- field
- order
properties:
field:
type: string
order:
type: string
enum:
- asc
- desc
fullTextFilter:
type: object
properties:
@@ -19,6 +19,7 @@
// ******************************************************************
import { QueryEntitiesByPredicateRequestFullTextFilter } from '../models/QueryEntitiesByPredicateRequestFullTextFilter.model';
import { QueryEntitiesByPredicateRequestOrderByInner } from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
/**
* @public
@@ -26,7 +27,7 @@ import { QueryEntitiesByPredicateRequestFullTextFilter } from '../models/QueryEn
export interface QueryEntitiesByPredicateRequest {
cursor?: string;
limit?: number;
orderField?: Array<string>;
orderBy?: Array<QueryEntitiesByPredicateRequestOrderByInner>;
fullTextFilter?: QueryEntitiesByPredicateRequestFullTextFilter;
fields?: Array<string>;
/**
@@ -0,0 +1,34 @@
/*
* Copyright 2026 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// ******************************************************************
// * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. *
// ******************************************************************
/**
* @public
*/
export interface QueryEntitiesByPredicateRequestOrderByInner {
field: string;
order: QueryEntitiesByPredicateRequestOrderByInnerOrderEnum;
}
/**
* @public
*/
export type QueryEntitiesByPredicateRequestOrderByInnerOrderEnum =
| 'asc'
| 'desc';
@@ -47,6 +47,7 @@ export * from '../models/ModelError.model';
export * from '../models/NullableEntity.model';
export * from '../models/QueryEntitiesByPredicateRequest.model';
export * from '../models/QueryEntitiesByPredicateRequestFullTextFilter.model';
export * from '../models/QueryEntitiesByPredicateRequestOrderByInner.model';
export * from '../models/RecursivePartialEntity.model';
export * from '../models/RecursivePartialEntityMeta.model';
export * from '../models/RecursivePartialEntityMetaAllOf.model';
@@ -1269,10 +1269,20 @@ export const spec = {
limit: {
type: 'number',
},
orderField: {
orderBy: {
type: 'array',
items: {
type: 'string',
type: 'object',
required: ['field', 'order'],
properties: {
field: {
type: 'string',
},
order: {
type: 'string',
enum: ['asc', 'desc'],
},
},
},
},
fullTextFilter: {
@@ -77,9 +77,12 @@ describe('parseEntityQuery', () => {
);
});
it('parses orderField into orderFields', () => {
it('parses orderBy into orderFields', () => {
const result = parseEntityQuery({
orderField: ['metadata.name,asc', 'metadata.namespace,desc'],
orderBy: [
{ field: 'metadata.name', order: 'asc' },
{ field: 'metadata.namespace', order: 'desc' },
],
});
expect(result).toEqual(
expect.objectContaining({
@@ -128,9 +131,12 @@ describe('parseEntityQuery', () => {
);
});
it('throws on invalid orderField order value', () => {
it('throws on invalid orderBy order value', () => {
expect(() =>
parseEntityQuery({ orderField: ['metadata.name,sideways'] }),
parseEntityQuery({
// @ts-expect-error - invalid order value
orderBy: [{ field: 'metadata.name', order: 'sideways' }],
}),
).toThrow(/Invalid order field order/);
});
});
@@ -41,17 +41,16 @@ function isSupportedFilterPredicateRoot(
}
function parseOrderFields(
orderField: string[] | undefined,
orderField: Array<{ field: string; order: string }> | undefined,
): EntityOrder[] | undefined {
if (!orderField?.length) {
return undefined;
}
return orderField.map(entry => {
const [field, order] = entry.split(',');
if (order !== undefined && order !== 'asc' && order !== 'desc') {
return orderField.map(({ field, order }) => {
if (order !== 'asc' && order !== 'desc') {
throw new InputError('Invalid order field order, must be asc or desc');
}
return { field, order: order as 'asc' | 'desc' };
return { field, order };
});
}
@@ -98,7 +97,7 @@ export function parseEntityQuery(
query = result.data;
}
const orderFields = parseOrderFields(request.orderField);
const orderFields = parseOrderFields(request.orderBy);
return {
query,