From 0950b94ff1bad6eec88e157fa88b1e671f66d408 Mon Sep 17 00:00:00 2001 From: "@oneplace.dev" Date: Mon, 10 Aug 2020 18:22:02 +0700 Subject: [PATCH] feat: add support of array of tuples order format (#973). Thanks to @RusovDmitriy --- docs/Cube.js-Backend/Query-Format.md | 16 ++++++++++++ packages/cubejs-api-gateway/index.js | 25 +++++++++++++----- packages/cubejs-api-gateway/index.test.js | 31 +++++++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/docs/Cube.js-Backend/Query-Format.md b/docs/Cube.js-Backend/Query-Format.md index 2c0f0aedb2299..1577d5f3f3eca 100644 --- a/docs/Cube.js-Backend/Query-Format.md +++ b/docs/Cube.js-Backend/Query-Format.md @@ -66,6 +66,22 @@ If the `order` property is not specified in the query, Cube.js sorts results by - The first measure, descending. If no measure exists... - The first dimension, ascending. +### Аlternative order format + +Also you can control the ordering of the `order` specification, Cube.js support alternative order format - array of tuples: + +```js +{ + ..., + order: [ + ['Stories.time', 'asc'], + ['Stories.count', 'asc'] + ] + }, + ... +} +``` + ## Filters Format A filter is a Javascript object with the following properties: diff --git a/packages/cubejs-api-gateway/index.js b/packages/cubejs-api-gateway/index.js index 7a862935aa764..1308529244f79 100644 --- a/packages/cubejs-api-gateway/index.js +++ b/packages/cubejs-api-gateway/index.js @@ -146,7 +146,10 @@ const querySchema = Joi.object().keys({ Joi.string() ] })), - order: Joi.object().pattern(id, Joi.valid('asc', 'desc')), + order: Joi.alternatives( + Joi.object().pattern(id, Joi.valid('asc', 'desc')), + Joi.array().items(Joi.array().min(2).ordered(id, Joi.valid('asc', 'desc'))) + ), segments: Joi.array().items(id), timezone: Joi.string(), limit: Joi.number().integer().min(1).max(50000), @@ -155,6 +158,20 @@ const querySchema = Joi.object().keys({ ungrouped: Joi.boolean() }); +const normalizeQueryOrder = order => { + let result = []; + const normalizeOrderItem = (k, direction) => ({ + id: k, + desc: direction === 'desc' + }); + if (order) { + result = Array.isArray(order) ? + order.map(([k, direction]) => normalizeOrderItem(k, direction)) : + Object.keys(order).map(k => normalizeOrderItem(k, order[k])); + } + return result; +}; + const DateRegex = /^\d\d\d\d-\d\d-\d\d$/; const normalizeQuery = (query) => { @@ -209,15 +226,11 @@ const normalizeQuery = (query) => { granularity: d.split('.')[2] })); const timezone = query.timezone || 'UTC'; - const order = query.order && Object.keys(query.order).map(k => ({ - id: k, - desc: query.order[k] === 'desc' - })); return { ...query, rowLimit: query.rowLimit || query.limit, timezone, - order, + order: normalizeQueryOrder(query.order), filters: (query.filters || []).map(f => ( { ...f, diff --git a/packages/cubejs-api-gateway/index.test.js b/packages/cubejs-api-gateway/index.test.js index 13615f06342c8..0d54e2069a9cc 100644 --- a/packages/cubejs-api-gateway/index.test.js +++ b/packages/cubejs-api-gateway/index.test.js @@ -90,4 +90,35 @@ describe(`API Gateway`, () => { "2020-01-01T23:59:59.999" ]); }); + + test(`order support object format`, async () => { + const query = { + measures: ["Foo.bar"], + order: { + 'Foo.bar': 'asc' + }, + }; + const res = await request(app) + .get(`/cubejs-api/v1/load?query=${JSON.stringify(query)}`) + .set('Authorization', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M') + .expect(200); + + expect(res.body.query.order).toStrictEqual([{ id: 'Foo.bar', desc: false }]); + }); + + test(`order support array of tuples`, async () => { + const query = { + measures: ["Foo.bar"], + order: [ + ['Foo.bar', 'asc'], + ['Foo.foo', 'desc'] + ], + }; + const res = await request(app) + .get(`/cubejs-api/v1/load?query=${JSON.stringify(query)}`) + .set('Authorization', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.t-IDcSemACt8x4iTMCda8Yhe3iZaWbvV5XKSTbuAn0M') + .expect(200); + + expect(res.body.query.order).toStrictEqual([{ id: 'Foo.bar', desc: false }, { id: 'Foo.foo', desc: true }]); + }); });