Skip to content

Commit f2e1a98

Browse files
authored
feat: Allow to specify default dimension order in schema (#10224)
1 parent 09f0fa6 commit f2e1a98

File tree

12 files changed

+166
-0
lines changed

12 files changed

+166
-0
lines changed

docs/pages/product/data-modeling/reference/dimensions.mdx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,58 @@ cubes:
330330
331331
</CodeTabs>
332332
333+
### `order`
334+
335+
The `order` parameter specifies the default sort order for a dimension. Valid
336+
values are `asc` (ascending) and `desc` (descending). This parameter is optional.
337+
338+
When set, the dimension's default sort order is exposed via
339+
[APIs and integrations][ref-apis]. Consuming applications, such as BI tools
340+
and custom frontends, can use this metadata to apply consistent default sorting
341+
when displaying dimension values, ensuring a uniform user experience across
342+
different tools connected to the semantic layer.
343+
344+
<CodeTabs>
345+
346+
```javascript
347+
cube(`orders`, {
348+
// ...
349+
350+
dimensions: {
351+
status: {
352+
sql: `status`,
353+
type: `string`,
354+
order: `asc`
355+
},
356+
357+
created_at: {
358+
sql: `created_at`,
359+
type: `time`,
360+
order: `desc`
361+
}
362+
}
363+
})
364+
```
365+
366+
```yaml
367+
cubes:
368+
- name: orders
369+
# ...
370+
371+
dimensions:
372+
- name: status
373+
sql: status
374+
type: string
375+
order: asc
376+
377+
- name: created_at
378+
sql: created_at
379+
type: time
380+
order: desc
381+
```
382+
383+
</CodeTabs>
384+
333385
### `primary_key`
334386

335387
Specify if a dimension is a primary key for a cube. The default value is

packages/cubejs-api-gateway/openspec.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ components:
145145
type: "object"
146146
format:
147147
$ref: "#/components/schemas/V1CubeMetaFormat"
148+
order:
149+
$ref: "#/components/schemas/V1CubeMetaDimensionOrder"
150+
V1CubeMetaDimensionOrder:
151+
type: "string"
152+
enum:
153+
- "asc"
154+
- "desc"
148155
V1CubeMetaMeasure:
149156
type: "object"
150157
required:

packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export type DimensionDefinition = {
3636
fieldType?: string;
3737
multiStage?: boolean;
3838
shiftInterval?: string;
39+
order?: 'asc' | 'desc';
3940
};
4041

4142
export type TimeShiftDefinition = {

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export type CubeSymbolDefinition = {
3535
granularities?: Record<string, GranularityDefinition>;
3636
timeShift?: TimeshiftDefinition[];
3737
format?: string;
38+
order?: 'asc' | 'desc';
3839
};
3940

4041
export type HierarchyDefinition = {

packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ export type DimensionConfig = {
9797
primaryKey: boolean;
9898
aliasMember?: string;
9999
granularities?: GranularityDefinition[];
100+
order?: 'asc' | 'desc';
100101
};
101102

102103
export type SegmentConfig = {
@@ -274,6 +275,7 @@ export class CubeToMetaTransformer implements CompilerInterface {
274275
origin: gDef.origin,
275276
}))
276277
: undefined,
278+
order: extendedDimDef.order,
277279
};
278280
}),
279281
segments: Object.entries(extendedCube.segments || {}).map((nameToSegment: [string, any]) => {

packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ const BaseDimensionWithoutSubQuery = {
278278
otherwise: formatSchema
279279
}),
280280
meta: Joi.any(),
281+
order: Joi.string().valid('asc', 'desc'),
281282
values: Joi.when('type', {
282283
is: 'switch',
283284
then: Joi.array().items(Joi.string()),

packages/cubejs-schema-compiler/test/unit/__snapshots__/views.test.ts.snap

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Object {
1515
"key": "Meta.key for CubeA.id",
1616
},
1717
"name": "simple_view.id",
18+
"order": undefined,
1819
"primaryKey": false,
1920
"public": true,
2021
"shortTitle": "Title for CubeA.id",
@@ -32,6 +33,7 @@ Object {
3233
"key": "Meta.key for CubeB.other_id",
3334
},
3435
"name": "simple_view.other_id",
36+
"order": undefined,
3537
"primaryKey": false,
3638
"public": true,
3739
"shortTitle": "Title for CubeB.other_id",
@@ -114,6 +116,7 @@ Object {
114116
"key": "Meta.key for CubeB.other_id",
115117
},
116118
"name": "simple_view.CubeB_other_id",
119+
"order": undefined,
117120
"primaryKey": false,
118121
"public": true,
119122
"shortTitle": "Title for CubeB.other_id",
@@ -196,6 +199,7 @@ Object {
196199
"key": "Meta.key for CubeA.id",
197200
},
198201
"name": "simple_view.CubeA_id",
202+
"order": undefined,
199203
"primaryKey": false,
200204
"public": true,
201205
"shortTitle": "Title for CubeA.id",
@@ -213,6 +217,7 @@ Object {
213217
"key": "Meta.key for CubeB.id",
214218
},
215219
"name": "simple_view.CubeB_id",
220+
"order": undefined,
216221
"primaryKey": false,
217222
"public": true,
218223
"shortTitle": "Title for CubeB.id",
@@ -230,6 +235,7 @@ Object {
230235
"key": "Meta.key for CubeB.other_id",
231236
},
232237
"name": "simple_view.CubeB_other_id",
238+
"order": undefined,
233239
"primaryKey": false,
234240
"public": true,
235241
"shortTitle": "Title for CubeB.other_id",
@@ -312,6 +318,7 @@ Object {
312318
"key": "Meta.key for CubeB.other_id",
313319
},
314320
"name": "simple_view.CubeB_other_id",
321+
"order": undefined,
315322
"primaryKey": false,
316323
"public": true,
317324
"shortTitle": "Title for CubeB.other_id",

packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,5 +1675,68 @@ describe('Cube Validation', () => {
16751675
const validationResult = cubeValidator.validate(cube, new ConsoleErrorReporter());
16761676
expect(validationResult.error).toBeTruthy();
16771677
});
1678+
1679+
it('dimension with valid order asc - correct', async () => {
1680+
const cubeValidator = new CubeValidator(new CubeSymbols());
1681+
const cube = {
1682+
name: 'name',
1683+
sql: () => 'SELECT * FROM public.Orders',
1684+
dimensions: {
1685+
status: {
1686+
sql: () => 'status',
1687+
type: 'string',
1688+
order: 'asc'
1689+
},
1690+
},
1691+
fileName: 'fileName',
1692+
};
1693+
1694+
const validationResult = cubeValidator.validate(cube, new ConsoleErrorReporter());
1695+
expect(validationResult.error).toBeFalsy();
1696+
});
1697+
1698+
it('dimension with valid order desc - correct', async () => {
1699+
const cubeValidator = new CubeValidator(new CubeSymbols());
1700+
const cube = {
1701+
name: 'name',
1702+
sql: () => 'SELECT * FROM public.Orders',
1703+
dimensions: {
1704+
createdAt: {
1705+
sql: () => 'created_at',
1706+
type: 'time',
1707+
order: 'desc'
1708+
},
1709+
},
1710+
fileName: 'fileName',
1711+
};
1712+
1713+
const validationResult = cubeValidator.validate(cube, new ConsoleErrorReporter());
1714+
expect(validationResult.error).toBeFalsy();
1715+
});
1716+
1717+
it('dimension with invalid order value - error', async () => {
1718+
const cubeValidator = new CubeValidator(new CubeSymbols());
1719+
const cube = {
1720+
name: 'name',
1721+
sql: () => 'SELECT * FROM public.Orders',
1722+
dimensions: {
1723+
status: {
1724+
sql: () => 'status',
1725+
type: 'string',
1726+
order: 'invalid' // should only accept 'asc' or 'desc'
1727+
},
1728+
},
1729+
fileName: 'fileName',
1730+
};
1731+
1732+
const validationResult = cubeValidator.validate(cube, {
1733+
error: (message: any, _e: any) => {
1734+
console.log(message);
1735+
expect(message).toContain('order');
1736+
}
1737+
} as any);
1738+
1739+
expect(validationResult.error).toBeTruthy();
1740+
});
16781741
});
16791742
});

rust/cubesql/cubeclient/src/models/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ pub use self::v1_cube_meta_custom_time_format::Type as V1CubeMetaCustomTimeForma
1111
pub mod v1_cube_meta_dimension;
1212
pub use self::v1_cube_meta_dimension::V1CubeMetaDimension;
1313
pub mod v1_cube_meta_dimension_granularity;
14+
pub mod v1_cube_meta_dimension_order;
1415
pub use self::v1_cube_meta_dimension_granularity::V1CubeMetaDimensionGranularity;
16+
pub use self::v1_cube_meta_dimension_order::V1CubeMetaDimensionOrder;
1517
pub mod v1_cube_meta_folder;
1618
pub use self::v1_cube_meta_folder::V1CubeMetaFolder;
1719
pub mod v1_cube_meta_format;

rust/cubesql/cubeclient/src/models/v1_cube_meta_dimension.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct V1CubeMetaDimension {
3232
pub meta: Option<serde_json::Value>,
3333
#[serde(rename = "format", skip_serializing_if = "Option::is_none")]
3434
pub format: Option<Box<models::V1CubeMetaFormat>>,
35+
#[serde(rename = "order", skip_serializing_if = "Option::is_none")]
36+
pub order: Option<models::V1CubeMetaDimensionOrder>,
3537
}
3638

3739
impl V1CubeMetaDimension {
@@ -46,6 +48,7 @@ impl V1CubeMetaDimension {
4648
granularities: None,
4749
meta: None,
4850
format: None,
51+
order: None,
4952
}
5053
}
5154
}

0 commit comments

Comments
 (0)