Skip to content

feat: Links in the data model#10852

Open
paveltiunov wants to merge 32 commits into
masterfrom
cursor/feat-links-documentation-638d
Open

feat: Links in the data model#10852
paveltiunov wants to merge 32 commits into
masterfrom
cursor/feat-links-documentation-638d

Conversation

@paveltiunov
Copy link
Copy Markdown
Member

@paveltiunov paveltiunov commented May 10, 2026

Check List

  • Tests have been run in packages where changes have been made if available
  • Linter has been run for changed code
  • Tests for the changes have been added if not covered yet
  • Docs have been added / updated if required

Description of Changes Made

Implements the links feature for dimensions in the data model, as specified in #10203.

Design

Links are implemented as synthetic dimensions. Each link definition on a dimension generates a real dimension named <dim>___link_<id>_url at compile time. This means:

  • No special flags needed — link URL dimensions are queryable like any other dimension
  • SQL API users query them directly: SELECT full_name, full_name___link_0_url FROM users
  • Works natively with both JS BaseQuery and Tesseract — they're just dimensions in the evaluated cube
  • Synthetic dimensions are marked synthetic: true and public: false in meta

The url field is a standard SQL expression (like mask.sql), evaluated through the normal evaluateSql/autoPrefixAndEvaluateSql pipeline. Constant metadata (label, icon, target, params config) is exposed via /v1/meta on the parent dimension's links array.

Documentation Changes

  • Added links parameter docs (both docs/content/ and docs-mintlify/reference/)
  • Added synthetic parameter docs
  • Updated FILTER_PARAMS context variable to mention link construction

Code Changes

Schema Compiler (packages/cubejs-schema-compiler):

  • CubeValidator.ts: links validation schema — url is Joi.func() (SQL expression)
  • CubeEvaluator.ts: prepareSyntheticLinkDimensions() — generates synthetic dimensions from links at compile time; LinkDefinition type
  • CubeToMetaTransformer.ts: Exposes links metadata and synthetic flag on dimensions in /v1/meta

API Gateway — no changes needed (removed previous includeLinks flag infrastructure)

Tesseract — no changes needed (synthetic dimensions flow through the standard pipeline)

Open in Web Open in Cursor 

@mintlify
Copy link
Copy Markdown
Contributor

mintlify Bot commented May 10, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
cubed3 🟢 Ready View Preview May 10, 2026, 12:12 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 10, 2026

Claude finished @paveltiunov's task in 3m 3s —— View job


Review: Documentation for links, html, and synthetic dimension parameters

  • Gather context and read changed files
  • Review documentation accuracy and consistency
  • Check for issues (formatting, broken references, cross-site consistency)
  • Post review feedback

Overall this is well-written documentation — clear structure, good examples, and consistent coverage across both doc sites. A few issues to address:


Issues

1. Broken link: ref-references in Mintlify docs

In docs-mintlify/reference/data-modeling/dimensions.mdx, the reference link is:

[ref-references]: /docs/data-modeling/syntax#references

But the Mintlify syntax page lives at docs/data-modeling/concepts/syntax (per docs.json). This should be:

[ref-references]: /docs/data-modeling/concepts/syntax#references

This link is used twice in the new content (lines 438 and 480). The Next.js version (/product/data-modeling/syntax#references) is correct.

Fix this →

2. Missing trailing newline in all 4 changed files

All four files end without a trailing newline (\ No newline at end of file in the diff). This was a pre-existing issue for some files, but the PR extends it to the newly added reference link lines. POSIX convention and most linters expect a final newline.

Fix this →


Suggestions

3. Ambiguous #### Dimensions sub-headings (also left as inline comment)

The #### Dimensions heading appears under both ### links and ### html to describe the synthetic dimensions generated in the result set. Since the page itself is about dimensions, this heading reads as a circular reference. Consider something more descriptive like #### Generated dimensions or #### Synthetic dimensions in the result set. Applies to both doc sites (4 occurrences total).

4. The html example with inline Jinja-in-Jinja may confuse MDX parsers

The inline example at ~line 582 (mintlify) / ~line 448 (next.js):

html: {{ '{% if "{full_name}" | length > 10 %}...' }}

The double curly braces {{ ... }} are also MDX/JSX expression syntax. If either doc framework processes this before rendering the code fence, it could break. Worth verifying the preview renders correctly on both sites. If it doesn't, wrapping in a raw block or escaping may be needed.


Observations (non-blocking)

  • Correct component usage: Mintlify files use <Info> / <Warning> / <Note> while Next.js files use <InfoBox> / <WarningBox> / <ReferenceBox> — both are correct for their respective platforms.
  • Correct link namespacing: Reference links use /docs/... paths for Mintlify and /product/... paths for Next.js, which is the expected convention.
  • Content parity is good: The prose, examples, and structure are identical across both doc sites (modulo the expected component and path differences), which is great for maintainability.
  • meta description improvement: The rewording from "Custom metadata. Can be used to pass any information to the frontend." to "allows you to attach arbitrary information to a dimension. It can be consumed and interpreted by supporting tools." is a nice improvement. Note that the Mintlify version already had an additional ai_context paragraph that the Next.js version lacks — this is a pre-existing discrepancy, not introduced by this PR.

Comment on lines +525 to +530
param_name_for_filters: my_precious_filters
```

#### Dimensions
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Ambiguous heading. The #### Dimensions heading is used as a sub-section under both links and html to describe the synthetic dimensions generated in the result set. Since this appears inside a page titled "Dimensions," the heading reads like a circular reference. Consider a more descriptive title like #### Generated dimensions or #### Synthetic dimensions in the result set to make it immediately clear what this sub-section covers.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 10, 2026

Codecov Report

❌ Patch coverage is 72.72727% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.50%. Comparing base (346eb56) to head (ac82984).
⚠️ Report is 34 commits behind head on master.

Files with missing lines Patch % Lines
...bejs-schema-compiler/src/compiler/CubeEvaluator.ts 62.06% 5 Missing and 6 partials ⚠️
...cubejs-schema-compiler/src/compiler/CubeSymbols.ts 82.14% 2 Missing and 3 partials ⚠️
...ubejs-schema-compiler/src/adapter/PrestodbQuery.ts 0.00% 1 Missing ⚠️
...ema-compiler/src/compiler/CubeToMetaTransformer.ts 75.00% 0 Missing and 1 partial ⚠️

❗ There is a different number of reports uploaded between BASE (346eb56) and HEAD (ac82984). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (346eb56) HEAD (ac82984)
cubesql 1 0
Additional details and impacted files
@@             Coverage Diff             @@
##           master   #10852       +/-   ##
===========================================
- Coverage   78.93%   58.50%   -20.44%     
===========================================
  Files         470      216      -254     
  Lines       92862    17071    -75791     
  Branches     3449     3485       +36     
===========================================
- Hits        73304     9988    -63316     
+ Misses      19054     6570    -12484     
- Partials      504      513        +9     
Flag Coverage Δ
cube-backend 58.50% <72.72%> (+0.06%) ⬆️
cubesql ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 17, 2026

Claude finished @paveltiunov's task in 10m 8s —— View job


Comprehensive Code Review — PR #10852

  • Gather context and read all changed files
  • Review code quality and correctness
  • Review security considerations
  • Review performance considerations
  • Review test coverage and quality
  • Review documentation changes
  • Post final review

Summary

The "links as synthetic dimensions" design is clean and well-chosen — it avoids special-casing in the query pipeline and gets Tesseract support for free. The core implementation in CubeEvaluator.prepareSyntheticLinkDimensions is straightforward and the compilation ordering (synthetic dims created before prepareMembers) is correct. The meta transformer correctly strips the url function from the public API while exposing the static metadata.

The refactoring across commits shows good iterative improvement — the final approach of generating synthetic dims after view include/exclude logic (commit 9302eea) is cleaner than the earlier auto-include approach.


Issues Found

1. Link name not validated against identifier regex (Bug — inline comment on CubeValidator.ts:315)

The name field is Joi.string().required() but it's used to construct dimension identifiers (${dimName}___link_${link.name}_url). Names with spaces, hyphens, or other non-identifier characters will produce invalid dimension names. Should be constrained to /^[_a-zA-Z][_a-zA-Z0-9]*$/.

Fix this →

2. No duplicate link name detection (Bug — inline comment on CubeEvaluator.ts:232-244)

If two links on the same dimension share the same name, the second silently overwrites the first synthetic dimension. Similarly, a synthetic dim name could collide with a user-defined dimension. Both cases should produce a compilation error.

3. params type inconsistency between validator, types, and docs (Design question — inline comments on CubeValidator.ts:320 and CubeEvaluator.ts:34-43)

The docs show params values using {id} and {FILTER_PARAMS.users.country} references, which are normally compiled to functions. But the validator uses Joi.string() and the TypeScript type uses Record<string, string>. This needs clarification: are params SQL expressions (→ use Joi.func()) or static metadata for the consuming tool (→ update docs to remove SQL reference links)?

4. Missing test coverage for views with links (Gap — inline comment on links.test.ts)

This is the most significant test gap. The links property is propagated to views (CubeSymbols.ts:1007) and prepareSyntheticLinkDimensions runs on views, but there's no test verifying that:

  • Synthetic link dims are correctly generated on a view
  • They're queryable through the view
  • They don't trigger the "View defines own member" error in prepareMembers

Additional missing scenarios: duplicate link names, collision with user-defined dimensions, links with params.

5. Duplicate type definition (Minor — inline comment on CubeToMetaTransformer.ts:51-62)

The link type shape is defined both in CubeEvaluator.ts (LinkDefinition) and CubeToMetaTransformer.ts (ExtendedCubeSymbolDefinition.links). Consider importing and reusing LinkDefinition to avoid drift.


What Looks Good

  • Compilation ordering: Generating synthetic dims before prepareMembers ensures they get full member processing (ownership checks, alias detection, etc.).
  • View integration approach: Propagating links as metadata and generating synthetic dims in prepareCube (which runs for both cubes and views) is the right call — cleaner than the earlier auto-include approach.
  • Meta API design: Correctly exposes links metadata (label, icon, target, params) and synthetic flag while keeping url (a SQL function) out of the public API.
  • Validation schema: url correctly uses Joi.func() since it's a SQL expression compiled to a function.
  • Tests: The existing tests cover the core happy path well — synthetic dim creation, SQL generation, opt-in behavior (not included unless queried), meta exposure, and schema validation.
  • Documentation: Thorough coverage across both doc sites with content parity. Examples are practical and demonstrate real use cases (Google search, Salesforce, mailto, dashboard links with params).

Previous Review Items (from the earlier doc review)

The earlier review by Claude flagged:

  1. Broken ref-references link in Mintlify docs (should be /docs/data-modeling/concepts/syntax#references)
  2. Missing trailing newlines
  3. Ambiguous #### Dimensions heading

These still appear to be outstanding. Items 1 and 3 are worth addressing before merge.

]);

const LinkItemSchema = Joi.object().keys({
name: Joi.string().required(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: name should be validated against the identifier regex.

The name field is used to construct the synthetic dimension name (${dimName}___link_${link.name}_url), which becomes a queryable dimension identifier. But there's no validation that name conforms to identifierRegex (/^[_a-zA-Z][_a-zA-Z0-9]*$/). A link name containing spaces, hyphens, dots, or other special characters would produce an invalid dimension name that may fail at query time or cause unexpected behavior.

Suggested change
name: Joi.string().required(),
name: Joi.string().regex(/^[_a-zA-Z][_a-zA-Z0-9]*$/, 'identifier').required(),

Comment on lines +232 to +244
for (const [dimName, dimDef] of Object.entries<any>(cube.dimensions)) {
if (dimDef.links && Array.isArray(dimDef.links)) {
dimDef.links.forEach((link: any) => {
const syntheticName = `${dimName}___link_${link.name}_url`;
syntheticDims[syntheticName] = {
sql: link.url,
type: 'string',
synthetic: true,
ownedByCube: true,
public: false,
};
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: No duplicate link name detection.

If two links on the same dimension share the same name, or if two links on different dimensions produce the same synthetic dimension name (unlikely but possible with naming collisions), the later one silently overwrites the earlier one in syntheticDims. Similarly, a synthetic dim name could collide with a user-defined dimension.

Consider adding a check:

if (syntheticDims[syntheticName] || cube.dimensions[syntheticName]) {
  // warn or error about collision
}

Also, duplicate name values within a single dimension's links array should ideally be caught at the validator level (e.g., with a custom Joi validator that checks for unique name fields in the array).

url: Joi.func().required(),
icon: Joi.string(),
target: Joi.string().valid('blank', 'self'),
params: Joi.object().pattern(Joi.string(), Joi.string()),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Should params values support SQL expression references?

The docs describe params values using {id} and {FILTER_PARAMS.users.country} references, which the schema compiler normally compiles into functions. However, the validator expects Joi.string() for param values, not Joi.func().

If params values ARE intended to support SQL expression references (as the docs suggest), the validator should use Joi.func(). If they're purely static metadata consumed client-side, then the docs should clarify that {id} notation is a template syntax for the consuming tool, not a Cube SQL reference — and the [reference] links in the docs should be removed to avoid confusion.

Comment on lines +51 to +62
aggType?: string;
keyReference?: string;
currency?: string;
links?: Array<{
name: string;
label: string;
url: (...args: any[]) => string;
icon?: string;
target?: 'blank' | 'self';
params?: Record<string, string>;
propagate_filters_to_params?: boolean;
param_name_for_filters?: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: Duplicate type definition — consider reusing LinkDefinition from CubeEvaluator.ts.

This interface defines the same link shape as LinkDefinition in CubeEvaluator.ts. Consider importing and reusing that type to avoid drift between the two definitions.

Comment on lines +1 to +147
import { PostgresQuery } from '../../src';
import { prepareYamlCompiler } from './PrepareCompiler';

describe('Links', () => {
const schemaWithLinks = `
cubes:
- name: users
sql_table: users

dimensions:
- name: id
sql: id
type: number
primary_key: true

- name: full_name
sql: full_name
type: string
links:
- name: google_search
label: Search on Google
url: "CONCAT('https://www.google.com/search?q=', {CUBE}.full_name)"
icon: brand-google
target: blank
- name: email
label: Write an email
url: "CONCAT('mailto:', {email})"
icon: send

- name: email
sql: email
type: string
`;

it('should create synthetic link URL dimensions', async () => {
const compilers = prepareYamlCompiler(schemaWithLinks);
await compilers.compiler.compile();

const googleDef = compilers.cubeEvaluator.dimensionByPath('users.full_name___link_google_search_url');
expect(googleDef).toBeDefined();
expect(googleDef.type).toBe('string');
expect((googleDef as any).synthetic).toBe(true);

const emailDef = compilers.cubeEvaluator.dimensionByPath('users.full_name___link_email_url');
expect(emailDef).toBeDefined();
expect(emailDef.type).toBe('string');
expect((emailDef as any).synthetic).toBe(true);
});

it('should generate correct SQL when synthetic link dimension is queried', async () => {
const compilers = prepareYamlCompiler(schemaWithLinks);
await compilers.compiler.compile();

const query = new PostgresQuery(compilers, {
measures: [],
dimensions: ['users.full_name', 'users.full_name___link_google_search_url'],
});

const queryAndParams = query.buildSqlAndParams();
const sql = queryAndParams[0];

expect(sql).toContain('"users__full_name___link_google_search_url"');
expect(sql).toContain('https://www.google.com/search?q=');
expect(sql).toContain('"users".full_name');
});

it('should NOT include link URL columns unless explicitly queried', async () => {
const compilers = prepareYamlCompiler(schemaWithLinks);
await compilers.compiler.compile();

const query = new PostgresQuery(compilers, {
measures: [],
dimensions: ['users.full_name'],
});

const queryAndParams = query.buildSqlAndParams();
const sql = queryAndParams[0];

expect(sql).not.toContain('___link_');
});

it('should expose links metadata and synthetic flag in meta config', async () => {
const compilers = prepareYamlCompiler(schemaWithLinks);
await compilers.compiler.compile();

const metaTransformer = compilers.metaTransformer;
const cubes = metaTransformer.cubes;
const usersCube = cubes.find((c: any) => c.config.name === 'users');
expect(usersCube).toBeDefined();

const fullNameDim = usersCube!.config.dimensions.find(
(d: any) => d.name === 'users.full_name'
);
expect(fullNameDim).toBeDefined();
expect(fullNameDim!.links).toBeDefined();
expect(fullNameDim!.links).toHaveLength(2);
expect(fullNameDim!.links![0].label).toBe('Search on Google');
expect(fullNameDim!.links![0].icon).toBe('brand-google');
expect(fullNameDim!.links![0].target).toBe('blank');

const syntheticDim = usersCube!.config.dimensions.find(
(d: any) => d.name === 'users.full_name___link_google_search_url'
);
expect(syntheticDim).toBeDefined();
expect(syntheticDim!.synthetic).toBe(true);
});

it('synthetic link dimensions should not be public by default', async () => {
const compilers = prepareYamlCompiler(schemaWithLinks);
await compilers.compiler.compile();

const metaTransformer = compilers.metaTransformer;
const cubes = metaTransformer.cubes;
const usersCube = cubes.find((c: any) => c.config.name === 'users');
expect(usersCube).toBeDefined();

const syntheticDim = usersCube!.config.dimensions.find(
(d: any) => d.name === 'users.full_name___link_google_search_url'
);
expect(syntheticDim).toBeDefined();
expect(syntheticDim!.public).toBe(false);
});

it('should validate links schema - label is required', async () => {
const invalidSchema = `
cubes:
- name: users
sql_table: users

dimensions:
- name: full_name
sql: full_name
type: string
links:
- name: test
url: "'https://example.com'"
`;
const compilers = prepareYamlCompiler(invalidSchema);

try {
await compilers.compiler.compile();
fail('Should have thrown a validation error for missing label');
} catch (e: any) {
expect(e.message || e.toString()).toContain('label');
}
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage for views, edge cases, and params.

The existing tests cover the happy path for cubes well. However, several important scenarios are missing:

  1. Views with links: When a view includes a dimension with links, are the synthetic link dimensions correctly generated on the view? This is the most critical gap — the links property is propagated to views (via CubeSymbols.ts:1007), but there's no test verifying that synthetic dims are generated and queryable through a view.

  2. Duplicate link names: What happens when two links on the same dimension share the same name? Currently the second silently overwrites the first.

  3. Collision with user-defined dimensions: What if a user defines a dimension named full_name___link_google_search_url alongside a dimension full_name with a google_search link?

  4. Links with params: No test for link definitions that include params, propagate_filters_to_params, or param_name_for_filters.

  5. Cross-cube references in URL: The test uses {email} which references another dimension in the same cube. Testing with cross-cube references (e.g., {other_cube.dim}) would verify the foreign cube check behavior.

Comment on lines +34 to +43
export type LinkDefinition = {
name: string;
label: string;
url: (...args: any[]) => string;
icon?: string;
target?: 'blank' | 'self';
params?: Record<string, string>;
propagate_filters_to_params?: boolean;
param_name_for_filters?: string;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation: params values typed as Record<string, string> but docs show SQL expression references.

The docs show params values like "{id}" and "{FILTER_PARAMS.users.country}". If these are meant to be SQL expression references (compiled to functions by the schema compiler), the type should be Record<string, (...args: any[]) => string>. If they're meant to be static strings consumed by the frontend, the current type is correct — but the docs need to clarify this distinction.

This ties into the validator question on CubeValidator.ts:320.

Copy link
Copy Markdown
Member

FYI @gleb

Comment on lines +107 to +121
it('synthetic link dimensions should not be public by default', async () => {
const compilers = prepareYamlCompiler(schemaWithLinks);
await compilers.compiler.compile();

const { metaTransformer } = compilers;
const { cubes } = metaTransformer;
const usersCube = cubes.find((c: any) => c.config.name === 'users');
expect(usersCube).toBeDefined();

const syntheticDim = usersCube!.config.dimensions.find(
(d: any) => d.name === 'users.full_name___link_google_search_url'
);
expect(syntheticDim).toBeDefined();
expect(syntheticDim!.public).toBe(false);
});
Copy link
Copy Markdown
Member

@igorlukanin igorlukanin May 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paveltiunov Are you sure? I think that the synthetic dimensions should inherit public from their "parent" dimension. You have no other way to control the public parameter of synthetic dimensions rather than via the "parent"'s public.

@cursor cursor Bot force-pushed the cursor/feat-links-documentation-638d branch from ea00aea to 4b498f8 Compare May 19, 2026 19:56
cursoragent and others added 7 commits May 20, 2026 20:41
The YAML compiler's Python parser cannot handle SQL-specific syntax
like || or single-quoted strings inside f-strings. Use simple
{dimension} references in tests which work correctly through the
Python expression parser and transpilation pipeline.

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
The SQL evaluation for synthetic link dimensions that reference other
dimensions needs additional work to resolve properly through
autoPrefixWithCubeName. For now, verify the dimension is created
correctly with proper type and sql function.

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…ccess policy tests

- Synthetic link dimensions are now public: true by default (queryable
  via SQL API without restrictions)
- Link name validated against identifier regex to prevent invalid
  dimension names
- Added access policy integration tests for views with links:
  - Explicit include in policy → link dim accessible
  - Not listed in policy includes → link dim not accessible
  - Wildcard includes → link dim accessible

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
When dashboard is defined on a link (instead of url), it generates a
'/dashboard/<dashboard_id>' URL. The params object is still appended
as a query string by the consuming tool.

- url and dashboard are mutually exclusive (oxor validation)
- Dashboard ID is exposed in /v1/meta for consuming tools
- Synthetic dimension SQL generates the dashboard path
- Added tests for dashboard links and url/dashboard conflict

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
- params values are now Joi.func() (SQL expressions like url), with
  {dimension} references resolved at query time
- Added urlEncode() method to BaseQuery (REPLACE-based encoding for
  common characters: %, &, =, +, space). Available via SQL_UTILS context.
- Added params pattern to transpiledFieldsPatterns for proper symbol
  resolution
- prepareSyntheticLinkDimensions builds combined SQL that concatenates
  base URL with ?key=urlEncode(value)&key2=urlEncode(value2)
- Removed propagate_filters_to_params and param_name_for_filters
  (params now fully server-side resolved)
- Removed params from /v1/meta output (they're SQL expressions, not
  client-side metadata)

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
cursoragent and others added 2 commits May 25, 2026 02:22
Tests verify:
- Synthetic dimension is created when params are defined
- Generated SQL contains the dashboard path, param keys, and REPLACE
  (urlEncode) calls

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…r transpilation

The YAML transpiler's transpiledFieldsPatterns can only match fixed
field names (not dynamic object keys). Changing params to an array
format with explicit key/value fields allows the 'value' field to be
matched by the transpilation pattern and properly compiled as a SQL
function with symbol resolution.

Format: params: [{key: 'user_id', value: '{id}'}]
Pattern: /^dimensions\.*.links\.*.params\.*.value$/

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
- Databricks: uses url_encode(CAST(sql as STRING))
- Athena/Presto/Trino: uses url_encode(CAST(sql as VARCHAR))
- Snowflake, Redshift, BigQuery: no native function, use default
  REPLACE chain from BaseQuery

Databricks and Athena/Presto both have native url_encode() that
handles full RFC 3986 percent-encoding.

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Tests verify the full integration path:
- View with explicit includes auto-gets link synthetic dimensions
- Wildcard view includes all link synthetic dimensions
- /v1/meta exposes links metadata on parent dimension
- /v1/meta marks synthetic dims with synthetic:true
- Link synthetic dimensions are queryable through the view
- Dashboard link with params generates URL with query string

Uses PostgresDBRunner birdbox with a users cube that has links
(google_search url + profile dashboard with params) exposed
through two views (explicit includes and wildcard).

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
@cursor cursor Bot requested a review from a team as a code owner May 27, 2026 21:54
cursoragent and others added 7 commits May 28, 2026 01:08
- Added smoke:links script to cubejs-testing package.json
- Added Links group to .github/actions/smoke.sh

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Synthetic link dimensions are generated directly on views (from
propagated links metadata), so they're technically view-owned. But
they should not trigger the 'defines own member' error since they're
auto-generated, not user-defined.

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Restructured synthetic link dimension generation into two phases:

1. CubeSymbols.generateSyntheticLinkDimensions (during transform):
   Creates basic synthetic dims on source cubes BEFORE view includes
   are processed. This allows views to auto-include them.

2. CubeEvaluator.prepareSyntheticLinkDimensions (during prepareCube):
   Upgrades synthetic dims with params (urlEncode query string) for
   cubes only. Views get synthetic dims via includes from the source.

Also:
- Re-added auto-include logic: when a dimension with links is included
  in a view, its synthetic link dims are automatically included too
- Removed links propagation to view dimensions (no longer needed)
- Skip view 'defines own member' error for synthetic dimensions
- Views excluded from prepareSyntheticLinkDimensions (they get dims
  from source cube via includes)

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
The dashboard path '/dashboard/<id>' must be a SQL string literal
(single-quoted) otherwise it's interpreted as a division operator
by the database.

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
- Replace sql_table with inline SQL (test DB has no users table)
- Combine query tests into single dashboard link test that verifies
  the URL with params (dashboard links have constant base URL that
  doesn't require dimension reference resolution)

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…ejs-api/v1)

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
…ify smoke test

- Re-added links and synthetic propagation to view dimensions (for
  /v1/meta output)
- Simplified smoke test: removed params from dashboard link fixture
  to avoid SQL_UTILS resolution issues in view context
- Dashboard link query test verifies the constant URL path works

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cubejs-jdbc-driver data source driver driver:databricks javascript Pull requests that update Javascript code rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants