Skip to content

Commit 34fa717

Browse files
authored
feat: Links in the data model (#10852)
1 parent d86ab6f commit 34fa717

20 files changed

Lines changed: 1402 additions & 10 deletions

File tree

.github/actions/smoke.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,7 @@ echo "::endgroup::"
7575
echo "::group::RBAC GraphQL"
7676
yarn lerna run --concurrency 1 --stream --no-prefix smoke:rbac-graphql
7777
echo "::endgroup::"
78+
79+
echo "::group::Links"
80+
yarn lerna run --concurrency 1 --stream --no-prefix smoke:links
81+
echo "::endgroup::"

docs-mintlify/reference/data-modeling/context-variables.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ values from the Cube query during SQL generation.
113113

114114
This is useful for hinting your database optimizer to use a specific index
115115
or filter out partitions or shards in your cloud data warehouse so you won't
116-
be billed for scanning those.
116+
be billed for scanning those. It can also be useful for constructing [links][ref-links].
117117

118118
<Warning>
119119

@@ -816,4 +816,5 @@ cube(`orders`, {
816816
[ref-dynamic-data-models]: /docs/data-modeling/dynamic/jinja
817817
[ref-query-filter]: /reference/rest-api/query-format#query-properties
818818
[ref-dynamic-jinja]: /docs/data-modeling/dynamic/jinja
819-
[ref-filter-boolean]: /reference/rest-api/query-format#boolean-logical-operators
819+
[ref-filter-boolean]: /reference/rest-api/query-format#boolean-logical-operators
820+
[ref-links]: /reference/data-modeling/dimensions#links

docs-mintlify/reference/data-modeling/dimensions.mdx

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,107 @@ Using it with other dimension types will result in a validation error.
426426

427427
</Info>
428428

429+
### `links`
430+
431+
The `links` parameter allows you to define links associated with a dimension.
432+
They can be rendered as HTML links by supporting tools, e.g., [Workbooks][ref-workbooks].
433+
434+
Links are useful to let users navigate to related external resources (e.g., Google
435+
search), internal tools (e.g., Salesforce), or other pages in a BI tool.
436+
437+
Each link must have a `name` and a `label`. The `name` is used as an identifier
438+
in the [synthetic dimension](#synthetic) name.
439+
440+
A link must specify either a `url` or a `dashboard`:
441+
- `url` is a SQL expression that constructs the link URL. It can [reference][ref-references]
442+
column and dimension values, just like the [`sql` parameter](#sql) or [`mask` parameter](#mask).
443+
- `dashboard` is a dashboard identifier. When set, the link URL is generated as
444+
`/dashboard/<dashboard_id>`. The `params` object is still appended as a query string.
445+
446+
Optionally, a link might use the `icon` parameter to reference an icon from a [supported
447+
icon set][link-tabler] to be displayed alongside the link label.
448+
449+
Optionally, a link might use the `target` parameter to specify [where to open it][link-target]:
450+
`blank` (default) to open in a new tab/window or `self` to open in the same tab/window.
451+
452+
```yaml
453+
cubes:
454+
- name: users
455+
456+
dimensions:
457+
# Definitions of dimensions such as `email`, etc.
458+
459+
- name: full_name
460+
sql: full_name
461+
type: string
462+
links:
463+
- name: google_search
464+
label: Search on Google
465+
url: "CONCAT('https://www.google.com/search?q=', {CUBE}.full_name)"
466+
icon: brand-google
467+
target: blank
468+
469+
- name: salesforce_search
470+
label: Search in Salesforce
471+
url: "CONCAT('https://your-company.salesforce.com/search/results/?q=', {email})"
472+
target: blank
473+
474+
- name: send_email
475+
label: Write an email
476+
url: "CONCAT('mailto:', {email})"
477+
icon: send
478+
```
479+
480+
#### `params`
481+
482+
The optional `params` parameter can be used to add additional query parameters to the
483+
URL. It accepts a map of key-value pairs, where keys are parameter names and values are
484+
SQL expressions (just like `url`).
485+
486+
Values in `params` can [reference][ref-references] columns and dimension values.
487+
All parameter values will be [URL-encoded][link-encode-uri-component] in the generated SQL
488+
using a database-specific encoding function.
489+
490+
```yaml
491+
cubes:
492+
- name: users
493+
494+
dimensions:
495+
# Definitions of dimensions such as `id`, `country`, etc.
496+
497+
- name: full_name
498+
sql: full_name
499+
type: string
500+
links:
501+
- name: performance
502+
label: Check performance dashboard
503+
dashboard: KSqDYdUz6Ble
504+
params:
505+
- key: filter_user_id
506+
value: "{id}"
507+
- key: filter_country
508+
value: "{country}"
509+
```
510+
511+
#### Synthetic dimensions
512+
513+
Each link will be rendered as an additional [synthetic](#synthetic) dimension in the
514+
result set, with the following naming convention:
515+
516+
- `<dimension_name>___link_<link_name>_url`
517+
518+
<Info>
519+
520+
All references in link URLs and parameters must resolve to a single value for a given
521+
value of the dimension on which the link is defined. Otherwise, it will result in
522+
duplicate rows in the result set.
523+
524+
</Info>
525+
429526
### `meta`
430527

431-
Custom metadata. Can be used to pass any information to the frontend.
528+
The `meta` parameter allows you to attach arbitrary information to a dimension.
529+
It can be consumed and interpreted by supporting tools.
432530

433531
You can also use the `ai_context` key to provide context to the
434532
[AI agent][ref-ai-context] without exposing it in the user interface.
@@ -902,6 +1000,13 @@ cube(`orders`, {
9021000

9031001
</CodeGroup>
9041002

1003+
### `synthetic`
1004+
1005+
The `synthetic` parameter can't be set by a user directly. It is used to mark dimensions
1006+
that are automatically created by Cube, e.g., for [links](#links).
1007+
1008+
You can check if a dimension is synthetic via the [`/v1/meta` API endpoint][ref-meta-api].
1009+
9051010
### `granularities`
9061011

9071012
By default, the following granularities are available for time dimensions:
@@ -1222,4 +1327,11 @@ cube(`fiscal_calendar`, {
12221327
[link-tesseract]: https://cube.dev/blog/introducing-next-generation-data-modeling-engine
12231328
[ref-case-measures]: /reference/data-modeling/measures#case
12241329
[ref-meta-api]: /reference/rest-api/reference#base_pathv1meta
1225-
[ref-string-time-dims]: /recipes/data-modeling/string-time-dimensions
1330+
[ref-string-time-dims]: /recipes/data-modeling/string-time-dimensions
1331+
[ref-workbooks]: /docs/explore-analyze/workbooks
1332+
[link-target]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#target
1333+
[link-tabler]: https://tabler.io/icons
1334+
[ref-references]: /docs/data-modeling/syntax#references
1335+
[ref-filter-params]: /reference/data-modeling/context-variables#filter_params
1336+
[link-encode-uri-component]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
1337+
[ref-rest-filters]: /reference/rest-api/query-format#query-properties

docs/content/product/data-modeling/reference/context-variables.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ values from the Cube query during SQL generation.
113113

114114
This is useful for hinting your database optimizer to use a specific index
115115
or filter out partitions or shards in your cloud data warehouse so you won't
116-
be billed for scanning those.
116+
be billed for scanning those. It can also be useful for constructing [links][ref-links].
117117

118118
<WarningBox>
119119

@@ -816,4 +816,5 @@ cube(`orders`, {
816816
[ref-dynamic-data-models]: /product/data-modeling/dynamic/jinja
817817
[ref-query-filter]: /product/apis-integrations/rest-api/query-format#query-properties
818818
[ref-dynamic-jinja]: /product/data-modeling/dynamic/jinja
819-
[ref-filter-boolean]: /product/apis-integrations/rest-api/query-format#boolean-logical-operators
819+
[ref-filter-boolean]: /product/apis-integrations/rest-api/query-format#boolean-logical-operators
820+
[ref-links]: /product/data-modeling/reference/dimensions#links

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

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,107 @@ cubes:
293293
294294
</CodeTabs>
295295
296+
### `links`
297+
298+
The `links` parameter allows you to define links associated with a dimension.
299+
They can be rendered as HTML links by supporting tools, e.g., [Workbooks][ref-workbooks].
300+
301+
Links are useful to let users navigate to related external resources (e.g., Google
302+
search), internal tools (e.g., Salesforce), or other pages in a BI tool.
303+
304+
Each link must have a `name` and a `label`. The `name` is used as an identifier
305+
in the [synthetic dimension](#synthetic) name.
306+
307+
A link must specify either a `url` or a `dashboard`:
308+
- `url` is a SQL expression that constructs the link URL. It can [reference][ref-references]
309+
column and dimension values, just like the [`sql` parameter](#sql) or [`mask` parameter](#mask).
310+
- `dashboard` is a dashboard identifier. When set, the link URL is generated as
311+
`/dashboard/<dashboard_id>`. The `params` object is still appended as a query string.
312+
313+
Optionally, a link might use the `icon` parameter to reference an icon from a [supported
314+
icon set][link-tabler] to be displayed alongside the link label.
315+
316+
Optionally, a link might use the `target` parameter to specify [where to open it][link-target]:
317+
`blank` (default) to open in a new tab/window or `self` to open in the same tab/window.
318+
319+
```yaml
320+
cubes:
321+
- name: users
322+
323+
dimensions:
324+
# Definitions of dimensions such as `email`, etc.
325+
326+
- name: full_name
327+
sql: full_name
328+
type: string
329+
links:
330+
- name: google_search
331+
label: Search on Google
332+
url: "CONCAT('https://www.google.com/search?q=', {CUBE}.full_name)"
333+
icon: brand-google
334+
target: blank
335+
336+
- name: salesforce_search
337+
label: Search in Salesforce
338+
url: "CONCAT('https://your-company.salesforce.com/search/results/?q=', {email})"
339+
target: blank
340+
341+
- name: send_email
342+
label: Write an email
343+
url: "CONCAT('mailto:', {email})"
344+
icon: send
345+
```
346+
347+
#### `params`
348+
349+
The optional `params` parameter can be used to add additional query parameters to the
350+
URL. It accepts a map of key-value pairs, where keys are parameter names and values are
351+
SQL expressions (just like `url`).
352+
353+
Values in `params` can [reference][ref-references] columns and dimension values.
354+
All parameter values will be [URL-encoded][link-encode-uri-component] in the generated SQL
355+
using a database-specific encoding function.
356+
357+
```yaml
358+
cubes:
359+
- name: users
360+
361+
dimensions:
362+
# Definitions of dimensions such as `id`, `country`, etc.
363+
364+
- name: full_name
365+
sql: full_name
366+
type: string
367+
links:
368+
- name: performance
369+
label: Check performance dashboard
370+
dashboard: KSqDYdUz6Ble
371+
params:
372+
- key: filter_user_id
373+
value: "{id}"
374+
- key: filter_country
375+
value: "{country}"
376+
```
377+
378+
#### Synthetic dimensions
379+
380+
Each link will be rendered as an additional [synthetic](#synthetic) dimension in the
381+
result set, with the following naming convention:
382+
383+
- `<dimension_name>___link_<link_name>_url`
384+
385+
<InfoBox>
386+
387+
All references in link URLs and parameters must resolve to a single value for a given
388+
value of the dimension on which the link is defined. Otherwise, it will result in
389+
duplicate rows in the result set.
390+
391+
</InfoBox>
392+
296393
### `meta`
297394

298-
Custom metadata. Can be used to pass any information to the frontend.
395+
The `meta` parameter allows you to attach arbitrary information to a dimension.
396+
It can be consumed and interpreted by supporting tools.
299397

300398
<CodeTabs>
301399

@@ -701,6 +799,13 @@ cubes:
701799
702800
</CodeTabs>
703801
802+
### `synthetic`
803+
804+
The `synthetic` parameter can't be set by a user directly. It is used to mark dimensions
805+
that are automatically created by Cube, e.g., for [links](#links).
806+
807+
You can check if a dimension is synthetic via the [`/v1/meta` API endpoint][ref-meta].
808+
704809
### `granularities`
705810

706811
By default, the following granularities are available for time dimensions:
@@ -1015,3 +1120,11 @@ cube(`fiscal_calendar`, {
10151120
[ref-cube-calendar]: /product/data-modeling/reference/cube#calendar
10161121
[ref-measure-time-shift]: /product/data-modeling/reference/measures#time_shift
10171122
[ref-data-masking]: /product/auth/data-access-policies#data-masking
1123+
[ref-workbooks]: /product/exploration/workbooks
1124+
[link-target]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#target
1125+
[link-tabler]: https://tabler.io/icons
1126+
[ref-references]: /product/data-modeling/syntax#references
1127+
[ref-filter-params]: /product/data-modeling/reference/context-variables#filter_params
1128+
[link-encode-uri-component]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
1129+
[ref-rest-filters]: /product/apis-integrations/rest-api/query-format#query-properties
1130+
[ref-meta]: /product/apis-integrations/rest-api/reference#base_pathv1meta

packages/cubejs-databricks-jdbc-driver/src/DatabricksQuery.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,8 @@ export class DatabricksQuery extends BaseQuery {
216216
delete templates.types.interval;
217217
return templates;
218218
}
219+
220+
public urlEncode(sql: string): string {
221+
return `url_encode(CAST(${sql} as STRING))`;
222+
}
219223
}

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3989,6 +3989,17 @@ export class BaseQuery {
39893989
throw new Error('Not implemented');
39903990
}
39913991

3992+
/**
3993+
* URL-encode a SQL expression. Override in dialect-specific query classes
3994+
* for native URL encoding support. Default implementation uses REPLACE
3995+
* chains for the most common characters.
3996+
* @param {string} sql
3997+
* @return {string}
3998+
*/
3999+
urlEncode(sql) {
4000+
return `REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(CAST(${sql} as TEXT), '%', '%25'), '&', '%26'), '=', '%3D'), '+', '%2B'), ' ', '%20')`;
4001+
}
4002+
39924003
/**
39934004
* @param {string} granularity
39944005
* @param {string} dimension
@@ -5007,7 +5018,8 @@ export class BaseQuery {
50075018
filterParams: this.filtersProxy(),
50085019
filterGroup: this.filterGroupFunction(),
50095020
sqlUtils: {
5010-
convertTz: this.convertTz.bind(this)
5021+
convertTz: this.convertTz.bind(this),
5022+
urlEncode: this.urlEncode.bind(this)
50115023
}
50125024
}, R.map(
50135025
(symbols) => this.contextSymbolsProxy(symbols),
@@ -5023,6 +5035,7 @@ export class BaseQuery {
50235035
filterGroup: () => '1 = 1',
50245036
sqlUtils: {
50255037
convertTz: (field) => field,
5038+
urlEncode: (sql) => sql,
50265039
},
50275040
securityContext: CubeSymbols.contextSymbolsProxyFrom({}, allocateParam),
50285041
};
@@ -5034,7 +5047,8 @@ export class BaseQuery {
50345047

50355048
sqlUtilsForRust() {
50365049
return {
5037-
convertTz: this.convertTz.bind(this)
5050+
convertTz: this.convertTz.bind(this),
5051+
urlEncode: this.urlEncode.bind(this)
50385052
};
50395053
}
50405054

packages/cubejs-schema-compiler/src/adapter/PrestodbQuery.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,8 @@ export class PrestodbQuery extends BaseQuery {
199199
public castToString(sql: any): string {
200200
return `CAST(${sql} as VARCHAR)`;
201201
}
202+
203+
public urlEncode(sql: string): string {
204+
return `url_encode(CAST(${sql} as VARCHAR))`;
205+
}
202206
}

0 commit comments

Comments
 (0)