@bonnard/cli 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bon.mjs +1919 -100
- package/dist/bin/models-IsV2sX74.mjs +76 -0
- package/dist/bin/{validate-Bd1D39Bj.mjs → validate-C4EHvJzJ.mjs} +47 -4
- package/dist/docs/README.md +78 -0
- package/dist/docs/_index.md +70 -0
- package/dist/docs/topics/cubes.data-source.md +92 -0
- package/dist/docs/topics/cubes.dimensions.format.md +195 -0
- package/dist/docs/topics/cubes.dimensions.md +184 -0
- package/dist/docs/topics/cubes.dimensions.primary-key.md +106 -0
- package/dist/docs/topics/cubes.dimensions.sub-query.md +174 -0
- package/dist/docs/topics/cubes.dimensions.time.md +111 -0
- package/dist/docs/topics/cubes.dimensions.types.md +107 -0
- package/dist/docs/topics/cubes.extends.md +149 -0
- package/dist/docs/topics/cubes.hierarchies.md +174 -0
- package/dist/docs/topics/cubes.joins.md +115 -0
- package/dist/docs/topics/cubes.md +117 -0
- package/dist/docs/topics/cubes.measures.calculated.md +99 -0
- package/dist/docs/topics/cubes.measures.drill-members.md +158 -0
- package/dist/docs/topics/cubes.measures.filters.md +86 -0
- package/dist/docs/topics/cubes.measures.format.md +153 -0
- package/dist/docs/topics/cubes.measures.md +162 -0
- package/dist/docs/topics/cubes.measures.rolling.md +119 -0
- package/dist/docs/topics/cubes.measures.types.md +122 -0
- package/dist/docs/topics/cubes.public.md +172 -0
- package/dist/docs/topics/cubes.refresh-key.md +153 -0
- package/dist/docs/topics/cubes.segments.md +121 -0
- package/dist/docs/topics/cubes.sql.md +61 -0
- package/dist/docs/topics/pre-aggregations.md +126 -0
- package/dist/docs/topics/pre-aggregations.rollup.md +162 -0
- package/dist/docs/topics/syntax.context-variables.md +153 -0
- package/dist/docs/topics/syntax.md +133 -0
- package/dist/docs/topics/syntax.references.md +174 -0
- package/dist/docs/topics/views.cubes.md +162 -0
- package/dist/docs/topics/views.folders.md +154 -0
- package/dist/docs/topics/views.includes.md +139 -0
- package/dist/docs/topics/views.md +138 -0
- package/dist/docs/topics/workflow.deploy.md +128 -0
- package/dist/docs/topics/workflow.mcp.md +100 -0
- package/dist/docs/topics/workflow.md +147 -0
- package/dist/docs/topics/workflow.query.md +198 -0
- package/dist/docs/topics/workflow.validate.md +152 -0
- package/dist/templates/claude/rules/bonnard.md +15 -0
- package/dist/templates/claude/settings.json +7 -0
- package/dist/templates/claude/skills/bonnard-cli/SKILL.md +59 -0
- package/dist/templates/claude/skills/bonnard-queries/SKILL.md +68 -0
- package/dist/templates/cursor/rules/bonnard-cli.mdc +47 -0
- package/dist/templates/cursor/rules/bonnard-queries.mdc +49 -0
- package/dist/templates/cursor/rules/bonnard.mdc +20 -0
- package/dist/templates/shared/bonnard.md +81 -0
- package/package.json +13 -8
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
|
|
5
|
+
//#region src/lib/models/datasources.ts
|
|
6
|
+
/**
|
|
7
|
+
* Extract datasource references from Cube model files
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Collect all YAML files from a directory recursively
|
|
11
|
+
*/
|
|
12
|
+
function collectYamlFiles(dir) {
|
|
13
|
+
if (!fs.existsSync(dir)) return [];
|
|
14
|
+
const results = [];
|
|
15
|
+
function walk(current) {
|
|
16
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
17
|
+
const fullPath = path.join(current, entry.name);
|
|
18
|
+
if (entry.isDirectory()) walk(fullPath);
|
|
19
|
+
else if (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) results.push(fullPath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
walk(dir);
|
|
23
|
+
return results;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse a single model file and extract datasource references
|
|
27
|
+
*/
|
|
28
|
+
function extractFromFile(filePath) {
|
|
29
|
+
const datasourceToCubes = /* @__PURE__ */ new Map();
|
|
30
|
+
try {
|
|
31
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
32
|
+
const parsed = YAML.parse(content);
|
|
33
|
+
if (!parsed) return datasourceToCubes;
|
|
34
|
+
if (parsed.cubes) for (const cube of parsed.cubes) {
|
|
35
|
+
const ds = cube.data_source || "default";
|
|
36
|
+
const existing = datasourceToCubes.get(ds) || [];
|
|
37
|
+
existing.push(cube.name);
|
|
38
|
+
datasourceToCubes.set(ds, existing);
|
|
39
|
+
}
|
|
40
|
+
if (parsed.views) {
|
|
41
|
+
for (const view of parsed.views) if (view.data_source) {
|
|
42
|
+
const existing = datasourceToCubes.get(view.data_source) || [];
|
|
43
|
+
existing.push(view.name);
|
|
44
|
+
datasourceToCubes.set(view.data_source, existing);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
return datasourceToCubes;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Extract all unique datasource references from models/ and views/ directories
|
|
52
|
+
* Returns datasource names mapped to the cubes that use them
|
|
53
|
+
*/
|
|
54
|
+
function extractDatasourcesFromModels(projectPath) {
|
|
55
|
+
const modelsDir = path.join(projectPath, "models");
|
|
56
|
+
const viewsDir = path.join(projectPath, "views");
|
|
57
|
+
const allFiles = [...collectYamlFiles(modelsDir), ...collectYamlFiles(viewsDir)];
|
|
58
|
+
const aggregated = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const file of allFiles) {
|
|
60
|
+
const fileRefs = extractFromFile(file);
|
|
61
|
+
for (const [ds, cubes] of fileRefs) {
|
|
62
|
+
const existing = aggregated.get(ds) || [];
|
|
63
|
+
existing.push(...cubes);
|
|
64
|
+
aggregated.set(ds, existing);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const results = [];
|
|
68
|
+
for (const [name, cubes] of aggregated) if (name !== "default") results.push({
|
|
69
|
+
name,
|
|
70
|
+
cubes
|
|
71
|
+
});
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
export { extractDatasourcesFromModels };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
3
4
|
import { compile } from "@cubejs-backend/schema-compiler";
|
|
4
5
|
|
|
5
6
|
//#region src/lib/validate.ts
|
|
@@ -19,6 +20,44 @@ function collectYamlFiles(dir, rootDir) {
|
|
|
19
20
|
walk(dir);
|
|
20
21
|
return results;
|
|
21
22
|
}
|
|
23
|
+
function checkMissingDescriptions(files) {
|
|
24
|
+
const missing = [];
|
|
25
|
+
for (const file of files) try {
|
|
26
|
+
const parsed = YAML.parse(file.content);
|
|
27
|
+
if (!parsed) continue;
|
|
28
|
+
const cubes = parsed.cubes || [];
|
|
29
|
+
for (const cube of cubes) {
|
|
30
|
+
if (!cube.name) continue;
|
|
31
|
+
if (!cube.description) missing.push({
|
|
32
|
+
parent: cube.name,
|
|
33
|
+
type: "cube",
|
|
34
|
+
name: cube.name
|
|
35
|
+
});
|
|
36
|
+
const measures = cube.measures || [];
|
|
37
|
+
for (const measure of measures) if (measure.name && !measure.description) missing.push({
|
|
38
|
+
parent: cube.name,
|
|
39
|
+
type: "measure",
|
|
40
|
+
name: measure.name
|
|
41
|
+
});
|
|
42
|
+
const dimensions = cube.dimensions || [];
|
|
43
|
+
for (const dimension of dimensions) if (dimension.name && !dimension.description) missing.push({
|
|
44
|
+
parent: cube.name,
|
|
45
|
+
type: "dimension",
|
|
46
|
+
name: dimension.name
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const views = parsed.views || [];
|
|
50
|
+
for (const view of views) {
|
|
51
|
+
if (!view.name) continue;
|
|
52
|
+
if (!view.description) missing.push({
|
|
53
|
+
parent: view.name,
|
|
54
|
+
type: "view",
|
|
55
|
+
name: view.name
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
return missing;
|
|
60
|
+
}
|
|
22
61
|
function createModelRepository(projectPath) {
|
|
23
62
|
const modelsDir = path.join(projectPath, "models");
|
|
24
63
|
const viewsDir = path.join(projectPath, "views");
|
|
@@ -32,11 +71,13 @@ function createModelRepository(projectPath) {
|
|
|
32
71
|
}
|
|
33
72
|
async function validate(projectPath) {
|
|
34
73
|
const repo = createModelRepository(projectPath);
|
|
35
|
-
|
|
74
|
+
const files = await repo.dataSchemaFiles();
|
|
75
|
+
if (files.length === 0) return {
|
|
36
76
|
valid: true,
|
|
37
77
|
errors: [],
|
|
38
78
|
cubes: [],
|
|
39
|
-
views: []
|
|
79
|
+
views: [],
|
|
80
|
+
missingDescriptions: []
|
|
40
81
|
};
|
|
41
82
|
try {
|
|
42
83
|
const { cubeEvaluator } = await compile(repo, {});
|
|
@@ -48,7 +89,8 @@ async function validate(projectPath) {
|
|
|
48
89
|
valid: true,
|
|
49
90
|
errors: [],
|
|
50
91
|
cubes,
|
|
51
|
-
views
|
|
92
|
+
views,
|
|
93
|
+
missingDescriptions: checkMissingDescriptions(files)
|
|
52
94
|
};
|
|
53
95
|
} catch (err) {
|
|
54
96
|
const raw = err.messages ?? err.message ?? String(err);
|
|
@@ -56,7 +98,8 @@ async function validate(projectPath) {
|
|
|
56
98
|
valid: false,
|
|
57
99
|
errors: Array.isArray(raw) ? raw : [raw],
|
|
58
100
|
cubes: [],
|
|
59
|
-
views: []
|
|
101
|
+
views: [],
|
|
102
|
+
missingDescriptions: []
|
|
60
103
|
};
|
|
61
104
|
}
|
|
62
105
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Bonnard CLI Documentation
|
|
2
|
+
|
|
3
|
+
This directory contains the documentation served by `bon docs`.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
docs/
|
|
9
|
+
├── _index.md # Index shown by `bon docs` (llms.txt style)
|
|
10
|
+
├── topics/ # Individual topic files
|
|
11
|
+
│ ├── cubes.md
|
|
12
|
+
│ ├── cubes.measures.md
|
|
13
|
+
│ ├── cubes.measures.types.md
|
|
14
|
+
│ └── ...
|
|
15
|
+
├── schemas/ # JSON schemas for validation
|
|
16
|
+
│ ├── cube.schema.json
|
|
17
|
+
│ └── view.schema.json
|
|
18
|
+
└── README.md # This file
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Topic Naming Convention
|
|
22
|
+
|
|
23
|
+
Topic IDs use dot notation that maps directly to filenames:
|
|
24
|
+
|
|
25
|
+
| Topic ID | File |
|
|
26
|
+
|----------|------|
|
|
27
|
+
| `cubes` | `topics/cubes.md` |
|
|
28
|
+
| `cubes.measures` | `topics/cubes.measures.md` |
|
|
29
|
+
| `cubes.measures.types` | `topics/cubes.measures.types.md` |
|
|
30
|
+
|
|
31
|
+
## Topic File Format
|
|
32
|
+
|
|
33
|
+
Each topic file should follow this structure:
|
|
34
|
+
|
|
35
|
+
```markdown
|
|
36
|
+
# topic.name
|
|
37
|
+
|
|
38
|
+
> Brief one-line description.
|
|
39
|
+
|
|
40
|
+
## Overview
|
|
41
|
+
|
|
42
|
+
Short explanation (2-3 sentences).
|
|
43
|
+
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
# Minimal working example
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Reference
|
|
51
|
+
|
|
52
|
+
| Property | Type | Description |
|
|
53
|
+
|----------|------|-------------|
|
|
54
|
+
| name | string | ... |
|
|
55
|
+
|
|
56
|
+
## See Also
|
|
57
|
+
|
|
58
|
+
- related.topic
|
|
59
|
+
- another.topic
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Guidelines
|
|
64
|
+
|
|
65
|
+
- Keep topics concise (~20-40 lines)
|
|
66
|
+
- Lead with examples, not theory
|
|
67
|
+
- Use tables for property references
|
|
68
|
+
- Include "See Also" for discoverability
|
|
69
|
+
|
|
70
|
+
## Commands
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
bon docs # Show index
|
|
74
|
+
bon docs <topic> # Show specific topic
|
|
75
|
+
bon docs <topic> --recursive # Show topic + children
|
|
76
|
+
bon docs --search <query> # Search topics
|
|
77
|
+
bon docs schema <type> # Show JSON schema
|
|
78
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Bonnard Documentation
|
|
2
|
+
|
|
3
|
+
> Build semantic layers with Cube models and views.
|
|
4
|
+
|
|
5
|
+
## Cubes
|
|
6
|
+
|
|
7
|
+
### Core
|
|
8
|
+
- [cubes](cubes) - Define data models with measures and dimensions
|
|
9
|
+
- [cubes.sql](cubes.sql) - Base SQL table or query
|
|
10
|
+
- [cubes.extends](cubes.extends) - Reuse members from other cubes
|
|
11
|
+
- [cubes.public](cubes.public) - Control API visibility
|
|
12
|
+
- [cubes.refresh-key](cubes.refresh-key) - Cache invalidation strategies
|
|
13
|
+
- [cubes.data-source](cubes.data-source) - Connect cubes to warehouses
|
|
14
|
+
|
|
15
|
+
### Measures
|
|
16
|
+
- [cubes.measures](cubes.measures) - Quantitative metrics (count, sum, avg)
|
|
17
|
+
- [cubes.measures.types](cubes.measures.types) - All 12 measure types
|
|
18
|
+
- [cubes.measures.filters](cubes.measures.filters) - Measure-level filters
|
|
19
|
+
- [cubes.measures.calculated](cubes.measures.calculated) - Derived metrics from other measures
|
|
20
|
+
- [cubes.measures.rolling](cubes.measures.rolling) - Rolling window aggregations
|
|
21
|
+
- [cubes.measures.drill-members](cubes.measures.drill-members) - Drill-down dimensions
|
|
22
|
+
- [cubes.measures.format](cubes.measures.format) - Output formatting (percent, currency)
|
|
23
|
+
|
|
24
|
+
### Dimensions
|
|
25
|
+
- [cubes.dimensions](cubes.dimensions) - Attributes for grouping/filtering
|
|
26
|
+
- [cubes.dimensions.types](cubes.dimensions.types) - All 6 dimension types
|
|
27
|
+
- [cubes.dimensions.primary-key](cubes.dimensions.primary-key) - Unique identifiers for joins
|
|
28
|
+
- [cubes.dimensions.time](cubes.dimensions.time) - Time-based analysis
|
|
29
|
+
- [cubes.dimensions.sub-query](cubes.dimensions.sub-query) - Bring measures into dimensions
|
|
30
|
+
- [cubes.dimensions.format](cubes.dimensions.format) - Display formatting (link, image, etc.)
|
|
31
|
+
|
|
32
|
+
### Relationships & Filters
|
|
33
|
+
- [cubes.joins](cubes.joins) - Relationships between cubes
|
|
34
|
+
- [cubes.hierarchies](cubes.hierarchies) - Drill-down paths for analysis
|
|
35
|
+
- [cubes.segments](cubes.segments) - Reusable predefined filters
|
|
36
|
+
|
|
37
|
+
## Views
|
|
38
|
+
|
|
39
|
+
- [views](views) - Compose cubes into focused interfaces
|
|
40
|
+
- [views.cubes](views.cubes) - Include cubes with join paths
|
|
41
|
+
- [views.includes](views.includes) - Select members to expose
|
|
42
|
+
- [views.folders](views.folders) - Organize members into groups
|
|
43
|
+
|
|
44
|
+
## Pre-Aggregations
|
|
45
|
+
|
|
46
|
+
- [pre-aggregations](pre-aggregations) - Materialize query results for performance
|
|
47
|
+
- [pre-aggregations.rollup](pre-aggregations.rollup) - Summarized data tables
|
|
48
|
+
|
|
49
|
+
## Syntax
|
|
50
|
+
|
|
51
|
+
- [syntax](syntax) - YAML syntax and conventions
|
|
52
|
+
- [syntax.references](syntax.references) - Reference columns, members, and cubes
|
|
53
|
+
- [syntax.context-variables](syntax.context-variables) - CUBE, FILTER_PARAMS, COMPILE_CONTEXT
|
|
54
|
+
|
|
55
|
+
## Workflow
|
|
56
|
+
|
|
57
|
+
- [workflow](workflow) - End-to-end development workflow
|
|
58
|
+
- [workflow.validate](workflow.validate) - Validate models locally
|
|
59
|
+
- [workflow.deploy](workflow.deploy) - Deploy to Bonnard
|
|
60
|
+
- [workflow.query](workflow.query) - Query the deployed semantic layer
|
|
61
|
+
- [workflow.mcp](workflow.mcp) - Connect AI agents via MCP
|
|
62
|
+
|
|
63
|
+
## Quick Reference
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bon docs <topic> # View topic
|
|
67
|
+
bon docs <topic> --recursive # View topic + children
|
|
68
|
+
bon docs --search <query> # Search all topics
|
|
69
|
+
bon docs schema <type> # JSON schema for validation
|
|
70
|
+
```
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# cubes.data-source
|
|
2
|
+
|
|
3
|
+
> Connect cubes to specific data warehouses.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `data_source` property specifies which configured data source a cube should use. This enables multi-database architectures where different cubes query different warehouses.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
cubes:
|
|
13
|
+
- name: orders
|
|
14
|
+
sql_table: public.orders
|
|
15
|
+
data_source: default
|
|
16
|
+
|
|
17
|
+
- name: analytics_events
|
|
18
|
+
sql_table: events
|
|
19
|
+
data_source: clickhouse_analytics
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Syntax
|
|
23
|
+
|
|
24
|
+
### Single Data Source
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
cubes:
|
|
28
|
+
- name: users
|
|
29
|
+
sql_table: users
|
|
30
|
+
data_source: postgres_main
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Default Behavior
|
|
34
|
+
|
|
35
|
+
If `data_source` is omitted, the cube uses the `default` data source:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
cubes:
|
|
39
|
+
- name: orders
|
|
40
|
+
sql_table: orders
|
|
41
|
+
# Uses "default" data source
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Multi-Database Architecture
|
|
45
|
+
|
|
46
|
+
Different cubes can query different databases:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
cubes:
|
|
50
|
+
# Transactional data from Postgres
|
|
51
|
+
- name: orders
|
|
52
|
+
sql_table: public.orders
|
|
53
|
+
data_source: postgres
|
|
54
|
+
|
|
55
|
+
# Analytics events from ClickHouse
|
|
56
|
+
- name: events
|
|
57
|
+
sql_table: analytics.events
|
|
58
|
+
data_source: clickhouse
|
|
59
|
+
|
|
60
|
+
# ML features from Snowflake
|
|
61
|
+
- name: predictions
|
|
62
|
+
sql_table: ml.predictions
|
|
63
|
+
data_source: snowflake
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Data Source Configuration
|
|
67
|
+
|
|
68
|
+
Data sources are configured in your Bonnard project:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
# .bon/datasources.yaml
|
|
72
|
+
datasources:
|
|
73
|
+
- name: default
|
|
74
|
+
type: postgres
|
|
75
|
+
host: localhost
|
|
76
|
+
database: mydb
|
|
77
|
+
|
|
78
|
+
- name: analytics
|
|
79
|
+
type: snowflake
|
|
80
|
+
account: myaccount
|
|
81
|
+
database: ANALYTICS
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Cross-Database Joins
|
|
85
|
+
|
|
86
|
+
Cubes from different data sources cannot be directly joined. Use views or pre-aggregations to combine data from multiple sources.
|
|
87
|
+
|
|
88
|
+
## See Also
|
|
89
|
+
|
|
90
|
+
- cubes
|
|
91
|
+
- cubes.sql
|
|
92
|
+
- workflow.deploy
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# cubes.dimensions.format
|
|
2
|
+
|
|
3
|
+
> Control how dimension values are displayed.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `format` property hints to BI tools how to render dimension values. Dimensions support more format options than measures.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
dimensions:
|
|
13
|
+
- name: avatar_url
|
|
14
|
+
type: string
|
|
15
|
+
sql: avatar_url
|
|
16
|
+
format: imageUrl
|
|
17
|
+
|
|
18
|
+
- name: profile_link
|
|
19
|
+
type: string
|
|
20
|
+
sql: "CONCAT('https://example.com/users/', id)"
|
|
21
|
+
format: link
|
|
22
|
+
|
|
23
|
+
- name: price
|
|
24
|
+
type: number
|
|
25
|
+
sql: unit_price
|
|
26
|
+
format: currency
|
|
27
|
+
|
|
28
|
+
- name: external_id
|
|
29
|
+
type: number
|
|
30
|
+
sql: external_system_id
|
|
31
|
+
format: id
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Supported Formats
|
|
35
|
+
|
|
36
|
+
### imageUrl
|
|
37
|
+
|
|
38
|
+
Displays the value as an image in table views:
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- name: product_image
|
|
42
|
+
type: string
|
|
43
|
+
sql: image_url
|
|
44
|
+
format: imageUrl
|
|
45
|
+
|
|
46
|
+
- name: avatar
|
|
47
|
+
type: string
|
|
48
|
+
sql: profile_picture_url
|
|
49
|
+
format: imageUrl
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### link
|
|
53
|
+
|
|
54
|
+
Displays the value as a clickable hyperlink:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- name: website
|
|
58
|
+
type: string
|
|
59
|
+
sql: website_url
|
|
60
|
+
format: link
|
|
61
|
+
|
|
62
|
+
- name: order_link
|
|
63
|
+
type: string
|
|
64
|
+
sql: "CONCAT('https://admin.example.com/orders/', id)"
|
|
65
|
+
format: link
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### id
|
|
69
|
+
|
|
70
|
+
Prevents number formatting (no commas in large numbers):
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
- name: external_id
|
|
74
|
+
type: number
|
|
75
|
+
sql: external_system_id
|
|
76
|
+
format: id
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Output: `1234567890` instead of `1,234,567,890`
|
|
80
|
+
|
|
81
|
+
### currency
|
|
82
|
+
|
|
83
|
+
Displays as monetary value:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
- name: unit_price
|
|
87
|
+
type: number
|
|
88
|
+
sql: price
|
|
89
|
+
format: currency
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### percent
|
|
93
|
+
|
|
94
|
+
Displays as percentage:
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
- name: discount_rate
|
|
98
|
+
type: number
|
|
99
|
+
sql: discount
|
|
100
|
+
format: percent
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Custom Time Format
|
|
104
|
+
|
|
105
|
+
Use POSIX strftime format strings for time dimensions:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
- name: created_at
|
|
109
|
+
type: time
|
|
110
|
+
sql: created_at
|
|
111
|
+
format: "%Y-%m-%d %H:%M:%S"
|
|
112
|
+
|
|
113
|
+
- name: birth_date
|
|
114
|
+
type: time
|
|
115
|
+
sql: birth_date
|
|
116
|
+
format: "%B %d, %Y" # "January 15, 2024"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Time Format Specifiers
|
|
120
|
+
|
|
121
|
+
| Specifier | Description | Example |
|
|
122
|
+
|-----------|-------------|---------|
|
|
123
|
+
| `%Y` | 4-digit year | 2024 |
|
|
124
|
+
| `%y` | 2-digit year | 24 |
|
|
125
|
+
| `%m` | Month (01-12) | 03 |
|
|
126
|
+
| `%B` | Full month name | March |
|
|
127
|
+
| `%b` | Abbreviated month | Mar |
|
|
128
|
+
| `%d` | Day of month (01-31) | 15 |
|
|
129
|
+
| `%H` | Hour (00-23) | 14 |
|
|
130
|
+
| `%I` | Hour (01-12) | 02 |
|
|
131
|
+
| `%M` | Minute (00-59) | 30 |
|
|
132
|
+
| `%S` | Second (00-59) | 45 |
|
|
133
|
+
| `%p` | AM/PM | PM |
|
|
134
|
+
|
|
135
|
+
## Common Patterns
|
|
136
|
+
|
|
137
|
+
### User Profiles
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
dimensions:
|
|
141
|
+
- name: avatar
|
|
142
|
+
type: string
|
|
143
|
+
sql: avatar_url
|
|
144
|
+
format: imageUrl
|
|
145
|
+
|
|
146
|
+
- name: profile_url
|
|
147
|
+
type: string
|
|
148
|
+
sql: "CONCAT('/users/', username)"
|
|
149
|
+
format: link
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Product Catalog
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
dimensions:
|
|
156
|
+
- name: image
|
|
157
|
+
type: string
|
|
158
|
+
sql: image_url
|
|
159
|
+
format: imageUrl
|
|
160
|
+
|
|
161
|
+
- name: sku
|
|
162
|
+
type: string
|
|
163
|
+
sql: sku
|
|
164
|
+
format: id
|
|
165
|
+
|
|
166
|
+
- name: price
|
|
167
|
+
type: number
|
|
168
|
+
sql: price
|
|
169
|
+
format: currency
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Timestamps
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
dimensions:
|
|
176
|
+
- name: created_at
|
|
177
|
+
type: time
|
|
178
|
+
sql: created_at
|
|
179
|
+
format: "%Y-%m-%d"
|
|
180
|
+
|
|
181
|
+
- name: last_login
|
|
182
|
+
type: time
|
|
183
|
+
sql: last_login_at
|
|
184
|
+
format: "%b %d, %Y at %I:%M %p"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## BI Tool Support
|
|
188
|
+
|
|
189
|
+
Format support varies by visualization tool. Most tools recognize common formats, but rendering details may differ.
|
|
190
|
+
|
|
191
|
+
## See Also
|
|
192
|
+
|
|
193
|
+
- cubes.dimensions
|
|
194
|
+
- cubes.dimensions.types
|
|
195
|
+
- cubes.measures.format
|