@bonnard/cli 0.2.10 → 0.2.12
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/README.md +166 -51
- package/dist/bin/api-B7cdKn9j.mjs +3 -0
- package/dist/bin/api-DqgY-30K.mjs +75 -0
- package/dist/bin/bon.mjs +243 -282
- package/dist/bin/{cubes-9rklhdAJ.mjs → cubes-BvtwNBUG.mjs} +1 -1
- package/dist/bin/local-BkK5XL7T.mjs +3 -0
- package/dist/bin/local-ByvuW3eV.mjs +149 -0
- package/dist/bin/project-Dj085D_B.mjs +27 -0
- package/dist/bin/{push-Bv9AFGc2.mjs → push-BOkUmRL8.mjs} +2 -1
- package/dist/bin/{validate-Bc8zGNw7.mjs → validate-C4W_Vto2.mjs} +1 -1
- package/dist/docs/topics/dashboards.examples.md +59 -76
- package/dist/docs/topics/dashboards.inputs.md +17 -23
- package/dist/docs/topics/dashboards.md +5 -7
- package/dist/docs/topics/dashboards.queries.md +17 -24
- package/dist/docs/topics/querying.sdk.md +3 -4
- package/package.json +1 -1
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as getLocalDatasource, c as resolveEnvVarsInCredentials, i as ensureBonDir, l as saveLocalDatasources, n as addLocalDatasource, o as loadLocalDatasources, r as datasourceExists, s as removeLocalDatasource, t as isDatasourcesTrackedByGit } from "./local-ByvuW3eV.mjs";
|
|
2
|
+
|
|
3
|
+
export { loadLocalDatasources };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { execFileSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
//#region src/lib/local/datasources.ts
|
|
7
|
+
/**
|
|
8
|
+
* Local datasource storage (.bon/datasources.yaml)
|
|
9
|
+
*
|
|
10
|
+
* Single file containing both config and credentials.
|
|
11
|
+
* Credentials may contain:
|
|
12
|
+
* - Plain values: "my_password"
|
|
13
|
+
* - dbt env var syntax: "{{ env_var('MY_PASSWORD') }}"
|
|
14
|
+
*
|
|
15
|
+
* Env vars are resolved at deploy time, not import time.
|
|
16
|
+
*/
|
|
17
|
+
const BON_DIR$1 = ".bon";
|
|
18
|
+
const DATASOURCES_FILE$1 = "datasources.yaml";
|
|
19
|
+
function getBonDir(cwd = process.cwd()) {
|
|
20
|
+
return path.join(cwd, BON_DIR$1);
|
|
21
|
+
}
|
|
22
|
+
function getDatasourcesPath$1(cwd = process.cwd()) {
|
|
23
|
+
return path.join(getBonDir(cwd), DATASOURCES_FILE$1);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Ensure .bon directory exists
|
|
27
|
+
*/
|
|
28
|
+
function ensureBonDir(cwd = process.cwd()) {
|
|
29
|
+
const bonDir = getBonDir(cwd);
|
|
30
|
+
if (!fs.existsSync(bonDir)) fs.mkdirSync(bonDir, { recursive: true });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load all local datasources
|
|
34
|
+
*/
|
|
35
|
+
function loadLocalDatasources(cwd = process.cwd()) {
|
|
36
|
+
const filePath = getDatasourcesPath$1(cwd);
|
|
37
|
+
if (!fs.existsSync(filePath)) return [];
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
40
|
+
return YAML.parse(content)?.datasources ?? [];
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Save all local datasources (with secure permissions since it contains credentials)
|
|
47
|
+
*/
|
|
48
|
+
function saveLocalDatasources(datasources, cwd = process.cwd()) {
|
|
49
|
+
ensureBonDir(cwd);
|
|
50
|
+
const filePath = getDatasourcesPath$1(cwd);
|
|
51
|
+
const file = { datasources };
|
|
52
|
+
const content = `# Bonnard datasources configuration
|
|
53
|
+
# This file contains credentials - add to .gitignore
|
|
54
|
+
# Env vars like {{ env_var('PASSWORD') }} are resolved at deploy time
|
|
55
|
+
|
|
56
|
+
` + YAML.stringify(file, { indent: 2 });
|
|
57
|
+
fs.writeFileSync(filePath, content, { mode: 384 });
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Add a single datasource (updates existing or appends new)
|
|
61
|
+
*/
|
|
62
|
+
function addLocalDatasource(datasource, cwd = process.cwd()) {
|
|
63
|
+
const existing = loadLocalDatasources(cwd);
|
|
64
|
+
const index = existing.findIndex((ds) => ds.name === datasource.name);
|
|
65
|
+
if (index >= 0) existing[index] = datasource;
|
|
66
|
+
else existing.push(datasource);
|
|
67
|
+
saveLocalDatasources(existing, cwd);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Remove a datasource by name
|
|
71
|
+
*/
|
|
72
|
+
function removeLocalDatasource(name, cwd = process.cwd()) {
|
|
73
|
+
const existing = loadLocalDatasources(cwd);
|
|
74
|
+
const filtered = existing.filter((ds) => ds.name !== name);
|
|
75
|
+
if (filtered.length === existing.length) return false;
|
|
76
|
+
saveLocalDatasources(filtered, cwd);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get a single datasource by name
|
|
81
|
+
*/
|
|
82
|
+
function getLocalDatasource(name, cwd = process.cwd()) {
|
|
83
|
+
return loadLocalDatasources(cwd).find((ds) => ds.name === name) ?? null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if a datasource name already exists locally
|
|
87
|
+
*/
|
|
88
|
+
function datasourceExists(name, cwd = process.cwd()) {
|
|
89
|
+
return getLocalDatasource(name, cwd) !== null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve {{ env_var('VAR_NAME') }} patterns in credentials
|
|
93
|
+
* Used at deploy time to resolve env vars before uploading
|
|
94
|
+
*/
|
|
95
|
+
function resolveEnvVarsInCredentials(credentials) {
|
|
96
|
+
const resolved = {};
|
|
97
|
+
const missing = [];
|
|
98
|
+
const envVarPattern = /\{\{\s*env_var\(['"]([\w_]+)['"]\)\s*\}\}/;
|
|
99
|
+
for (const [key, value] of Object.entries(credentials)) {
|
|
100
|
+
const match = value.match(envVarPattern);
|
|
101
|
+
if (match) {
|
|
102
|
+
const varName = match[1];
|
|
103
|
+
const envValue = process.env[varName];
|
|
104
|
+
if (envValue !== void 0) resolved[key] = envValue;
|
|
105
|
+
else {
|
|
106
|
+
missing.push(varName);
|
|
107
|
+
resolved[key] = value;
|
|
108
|
+
}
|
|
109
|
+
} else resolved[key] = value;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
resolved,
|
|
113
|
+
missing
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/lib/local/credentials.ts
|
|
119
|
+
/**
|
|
120
|
+
* Credential utilities (git tracking check)
|
|
121
|
+
*/
|
|
122
|
+
const BON_DIR = ".bon";
|
|
123
|
+
const DATASOURCES_FILE = "datasources.yaml";
|
|
124
|
+
function getDatasourcesPath(cwd = process.cwd()) {
|
|
125
|
+
return path.join(cwd, BON_DIR, DATASOURCES_FILE);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Check if datasources file is tracked by git (it shouldn't be - contains credentials)
|
|
129
|
+
*/
|
|
130
|
+
function isDatasourcesTrackedByGit(cwd = process.cwd()) {
|
|
131
|
+
const filePath = getDatasourcesPath(cwd);
|
|
132
|
+
if (!fs.existsSync(filePath)) return false;
|
|
133
|
+
try {
|
|
134
|
+
execFileSync("git", [
|
|
135
|
+
"ls-files",
|
|
136
|
+
"--error-unmatch",
|
|
137
|
+
filePath
|
|
138
|
+
], {
|
|
139
|
+
cwd,
|
|
140
|
+
stdio: "pipe"
|
|
141
|
+
});
|
|
142
|
+
return true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
export { getLocalDatasource as a, resolveEnvVarsInCredentials as c, ensureBonDir as i, saveLocalDatasources as l, addLocalDatasource as n, loadLocalDatasources as o, datasourceExists as r, removeLocalDatasource as s, isDatasourcesTrackedByGit as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/project.ts
|
|
5
|
+
/**
|
|
6
|
+
* The subdirectory name used for Bonnard cube/view files.
|
|
7
|
+
* Keeps Bonnard files namespaced to avoid conflicts with existing
|
|
8
|
+
* project directories (e.g. dbt's models/).
|
|
9
|
+
*/
|
|
10
|
+
const BONNARD_DIR = "bonnard";
|
|
11
|
+
/**
|
|
12
|
+
* Resolve Bonnard project paths relative to the working directory.
|
|
13
|
+
* All cube/view operations should use these paths.
|
|
14
|
+
*/
|
|
15
|
+
function getProjectPaths(cwd) {
|
|
16
|
+
const bonnardRoot = path.join(cwd, BONNARD_DIR);
|
|
17
|
+
return {
|
|
18
|
+
root: bonnardRoot,
|
|
19
|
+
cubes: path.join(bonnardRoot, "cubes"),
|
|
20
|
+
views: path.join(bonnardRoot, "views"),
|
|
21
|
+
config: path.join(cwd, "bon.yaml"),
|
|
22
|
+
localState: path.join(cwd, ".bon")
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { getProjectPaths as n, BONNARD_DIR as t };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as post } from "./api-DqgY-30K.mjs";
|
|
2
|
+
import { a as getLocalDatasource, c as resolveEnvVarsInCredentials } from "./local-ByvuW3eV.mjs";
|
|
2
3
|
import pc from "picocolors";
|
|
3
4
|
import "@inquirer/prompts";
|
|
4
5
|
|
|
@@ -15,50 +15,45 @@ description: Monthly revenue trends and breakdowns
|
|
|
15
15
|
# Revenue Overview
|
|
16
16
|
|
|
17
17
|
` ``query total_revenue
|
|
18
|
-
|
|
19
|
-
measures: [total_revenue]
|
|
18
|
+
measures: [orders.total_revenue]
|
|
20
19
|
` ``
|
|
21
20
|
|
|
22
21
|
` ``query order_count
|
|
23
|
-
|
|
24
|
-
measures: [count]
|
|
22
|
+
measures: [orders.count]
|
|
25
23
|
` ``
|
|
26
24
|
|
|
27
25
|
` ``query avg_order
|
|
28
|
-
|
|
29
|
-
measures: [avg_order_value]
|
|
26
|
+
measures: [orders.avg_order_value]
|
|
30
27
|
` ``
|
|
31
28
|
|
|
32
29
|
<Grid cols="3">
|
|
33
|
-
<BigValue data={total_revenue} value="total_revenue" title="Total Revenue" />
|
|
34
|
-
<BigValue data={order_count} value="count" title="Orders" />
|
|
35
|
-
<BigValue data={avg_order} value="avg_order_value" title="Avg Order" />
|
|
30
|
+
<BigValue data={total_revenue} value="orders.total_revenue" title="Total Revenue" />
|
|
31
|
+
<BigValue data={order_count} value="orders.count" title="Orders" />
|
|
32
|
+
<BigValue data={avg_order} value="orders.avg_order_value" title="Avg Order" />
|
|
36
33
|
</Grid>
|
|
37
34
|
|
|
38
35
|
## Monthly Trend
|
|
39
36
|
|
|
40
37
|
` ``query monthly_revenue
|
|
41
|
-
|
|
42
|
-
measures: [total_revenue]
|
|
38
|
+
measures: [orders.total_revenue]
|
|
43
39
|
timeDimension:
|
|
44
|
-
dimension: created_at
|
|
40
|
+
dimension: orders.created_at
|
|
45
41
|
granularity: month
|
|
46
42
|
dateRange: [2025-01-01, 2025-12-31]
|
|
47
43
|
` ``
|
|
48
44
|
|
|
49
|
-
<LineChart data={monthly_revenue} x="created_at" y="total_revenue" title="Monthly Revenue" />
|
|
45
|
+
<LineChart data={monthly_revenue} x="orders.created_at" y="orders.total_revenue" title="Monthly Revenue" />
|
|
50
46
|
|
|
51
47
|
## By Category
|
|
52
48
|
|
|
53
49
|
` ``query by_category
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
dimensions: [category]
|
|
50
|
+
measures: [orders.total_revenue, orders.count]
|
|
51
|
+
dimensions: [orders.category]
|
|
57
52
|
orderBy:
|
|
58
|
-
total_revenue: desc
|
|
53
|
+
orders.total_revenue: desc
|
|
59
54
|
` ``
|
|
60
55
|
|
|
61
|
-
<BarChart data={by_category} x="category" y="total_revenue" title="Revenue by Category" />
|
|
56
|
+
<BarChart data={by_category} x="orders.category" y="orders.total_revenue" title="Revenue by Category" />
|
|
62
57
|
<DataTable data={by_category} />
|
|
63
58
|
```
|
|
64
59
|
|
|
@@ -75,43 +70,40 @@ description: Order status breakdown and city analysis
|
|
|
75
70
|
# Sales Pipeline
|
|
76
71
|
|
|
77
72
|
` ``query by_status
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
dimensions: [status]
|
|
73
|
+
measures: [orders.count]
|
|
74
|
+
dimensions: [orders.status]
|
|
81
75
|
` ``
|
|
82
76
|
|
|
83
|
-
<PieChart data={by_status} name="status" value="count" title="Order Status" />
|
|
77
|
+
<PieChart data={by_status} name="orders.status" value="orders.count" title="Order Status" />
|
|
84
78
|
|
|
85
79
|
## Top Cities
|
|
86
80
|
|
|
87
81
|
` ``query top_cities
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
dimensions: [city]
|
|
82
|
+
measures: [orders.total_revenue, orders.count]
|
|
83
|
+
dimensions: [orders.city]
|
|
91
84
|
orderBy:
|
|
92
|
-
total_revenue: desc
|
|
85
|
+
orders.total_revenue: desc
|
|
93
86
|
limit: 10
|
|
94
87
|
` ``
|
|
95
88
|
|
|
96
|
-
<BarChart data={top_cities} x="city" y="total_revenue" horizontal />
|
|
89
|
+
<BarChart data={top_cities} x="orders.city" y="orders.total_revenue" horizontal />
|
|
97
90
|
<DataTable data={top_cities} />
|
|
98
91
|
|
|
99
92
|
## Completed Orders Over Time
|
|
100
93
|
|
|
101
94
|
` ``query completed_trend
|
|
102
|
-
|
|
103
|
-
measures: [total_revenue]
|
|
95
|
+
measures: [orders.total_revenue]
|
|
104
96
|
timeDimension:
|
|
105
|
-
dimension: created_at
|
|
97
|
+
dimension: orders.created_at
|
|
106
98
|
granularity: week
|
|
107
99
|
dateRange: [2025-01-01, 2025-06-30]
|
|
108
100
|
filters:
|
|
109
|
-
- dimension: status
|
|
101
|
+
- dimension: orders.status
|
|
110
102
|
operator: equals
|
|
111
103
|
values: [completed]
|
|
112
104
|
` ``
|
|
113
105
|
|
|
114
|
-
<AreaChart data={completed_trend} x="created_at" y="total_revenue" title="Completed Order Revenue" />
|
|
106
|
+
<AreaChart data={completed_trend} x="orders.created_at" y="orders.total_revenue" title="Completed Order Revenue" />
|
|
115
107
|
```
|
|
116
108
|
|
|
117
109
|
## Multi-Series Dashboard
|
|
@@ -127,39 +119,37 @@ description: Multi-series charts showing revenue breakdown by sales channel
|
|
|
127
119
|
# Revenue by Channel
|
|
128
120
|
|
|
129
121
|
` ``query revenue_by_channel
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
dimensions: [channel]
|
|
122
|
+
measures: [orders.total_revenue]
|
|
123
|
+
dimensions: [orders.channel]
|
|
133
124
|
timeDimension:
|
|
134
|
-
dimension: created_at
|
|
125
|
+
dimension: orders.created_at
|
|
135
126
|
granularity: month
|
|
136
127
|
dateRange: [2025-01-01, 2025-12-31]
|
|
137
128
|
` ``
|
|
138
129
|
|
|
139
130
|
## Stacked Bar (default)
|
|
140
131
|
|
|
141
|
-
<BarChart data={revenue_by_channel} x="created_at" y="total_revenue" series="channel" title="Revenue by Channel" />
|
|
132
|
+
<BarChart data={revenue_by_channel} x="orders.created_at" y="orders.total_revenue" series="orders.channel" title="Revenue by Channel" />
|
|
142
133
|
|
|
143
134
|
## Grouped Bar
|
|
144
135
|
|
|
145
|
-
<BarChart data={revenue_by_channel} x="created_at" y="total_revenue" series="channel" type="grouped" title="Revenue by Channel (Grouped)" />
|
|
136
|
+
<BarChart data={revenue_by_channel} x="orders.created_at" y="orders.total_revenue" series="orders.channel" type="grouped" title="Revenue by Channel (Grouped)" />
|
|
146
137
|
|
|
147
138
|
## Multi-Line
|
|
148
139
|
|
|
149
140
|
` ``query trend
|
|
150
|
-
|
|
151
|
-
measures: [total_revenue, count]
|
|
141
|
+
measures: [orders.total_revenue, orders.count]
|
|
152
142
|
timeDimension:
|
|
153
|
-
dimension: created_at
|
|
143
|
+
dimension: orders.created_at
|
|
154
144
|
granularity: month
|
|
155
145
|
dateRange: [2025-01-01, 2025-12-31]
|
|
156
146
|
` ``
|
|
157
147
|
|
|
158
|
-
<LineChart data={trend} x="created_at" y="total_revenue,count" title="Revenue vs Orders" />
|
|
148
|
+
<LineChart data={trend} x="orders.created_at" y="orders.total_revenue,orders.count" title="Revenue vs Orders" />
|
|
159
149
|
|
|
160
150
|
## Stacked Area by Channel
|
|
161
151
|
|
|
162
|
-
<AreaChart data={revenue_by_channel} x="created_at" y="total_revenue" series="channel" type="stacked" title="Revenue by Channel" />
|
|
152
|
+
<AreaChart data={revenue_by_channel} x="orders.created_at" y="orders.total_revenue" series="orders.channel" type="stacked" title="Revenue by Channel" />
|
|
163
153
|
```
|
|
164
154
|
|
|
165
155
|
## Formatted Dashboard
|
|
@@ -175,40 +165,37 @@ description: Formatted revenue metrics and trends
|
|
|
175
165
|
# Sales Performance
|
|
176
166
|
|
|
177
167
|
` ``query totals
|
|
178
|
-
|
|
179
|
-
measures: [total_revenue, count, avg_order_value]
|
|
168
|
+
measures: [orders.total_revenue, orders.count, orders.avg_order_value]
|
|
180
169
|
` ``
|
|
181
170
|
|
|
182
171
|
<Grid cols="3">
|
|
183
|
-
<BigValue data={totals} value="total_revenue" title="Revenue" fmt="eur2" />
|
|
184
|
-
<BigValue data={totals} value="count" title="Orders" fmt="num0" />
|
|
185
|
-
<BigValue data={totals} value="avg_order_value" title="Avg Order" fmt="eur2" />
|
|
172
|
+
<BigValue data={totals} value="orders.total_revenue" title="Revenue" fmt="eur2" />
|
|
173
|
+
<BigValue data={totals} value="orders.count" title="Orders" fmt="num0" />
|
|
174
|
+
<BigValue data={totals} value="orders.avg_order_value" title="Avg Order" fmt="eur2" />
|
|
186
175
|
</Grid>
|
|
187
176
|
|
|
188
177
|
## Revenue Trend
|
|
189
178
|
|
|
190
179
|
` ``query monthly
|
|
191
|
-
|
|
192
|
-
measures: [total_revenue]
|
|
180
|
+
measures: [orders.total_revenue]
|
|
193
181
|
timeDimension:
|
|
194
|
-
dimension: created_at
|
|
182
|
+
dimension: orders.created_at
|
|
195
183
|
granularity: month
|
|
196
184
|
dateRange: [2025-01-01, 2025-12-31]
|
|
197
185
|
` ``
|
|
198
186
|
|
|
199
|
-
<LineChart data={monthly} x="created_at" y="total_revenue" title="Monthly Revenue" yFmt="eur" />
|
|
187
|
+
<LineChart data={monthly} x="orders.created_at" y="orders.total_revenue" title="Monthly Revenue" yFmt="eur" />
|
|
200
188
|
|
|
201
189
|
## Detail Table
|
|
202
190
|
|
|
203
191
|
` ``query details
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
dimensions: [category]
|
|
192
|
+
measures: [orders.total_revenue, orders.count]
|
|
193
|
+
dimensions: [orders.category]
|
|
207
194
|
orderBy:
|
|
208
|
-
total_revenue: desc
|
|
195
|
+
orders.total_revenue: desc
|
|
209
196
|
` ``
|
|
210
197
|
|
|
211
|
-
<DataTable data={details} fmt="total_revenue:eur2,count:num0" />
|
|
198
|
+
<DataTable data={details} fmt="orders.total_revenue:eur2,orders.count:num0" />
|
|
212
199
|
```
|
|
213
200
|
|
|
214
201
|
## Interactive Dashboard
|
|
@@ -224,47 +211,43 @@ description: Sales dashboard with date and channel filters
|
|
|
224
211
|
# Interactive Sales
|
|
225
212
|
|
|
226
213
|
<DateRange name="period" default="last-6-months" label="Time Period" />
|
|
227
|
-
<Dropdown name="channel" dimension="channel" data={channels} queries="trend,by_city" label="Channel" />
|
|
214
|
+
<Dropdown name="channel" dimension="orders.channel" data={channels} queries="trend,by_city" label="Channel" />
|
|
228
215
|
|
|
229
216
|
` ``query channels
|
|
230
|
-
|
|
231
|
-
dimensions: [channel]
|
|
217
|
+
dimensions: [orders.channel]
|
|
232
218
|
` ``
|
|
233
219
|
|
|
234
220
|
` ``query kpis
|
|
235
|
-
|
|
236
|
-
measures: [total_revenue, count]
|
|
221
|
+
measures: [orders.total_revenue, orders.count]
|
|
237
222
|
` ``
|
|
238
223
|
|
|
239
224
|
<Grid cols="2">
|
|
240
|
-
<BigValue data={kpis} value="total_revenue" title="Revenue" fmt="eur2" />
|
|
241
|
-
<BigValue data={kpis} value="count" title="Orders" fmt="num0" />
|
|
225
|
+
<BigValue data={kpis} value="orders.total_revenue" title="Revenue" fmt="eur2" />
|
|
226
|
+
<BigValue data={kpis} value="orders.count" title="Orders" fmt="num0" />
|
|
242
227
|
</Grid>
|
|
243
228
|
|
|
244
229
|
## Revenue Trend
|
|
245
230
|
|
|
246
231
|
` ``query trend
|
|
247
|
-
|
|
248
|
-
measures: [total_revenue]
|
|
232
|
+
measures: [orders.total_revenue]
|
|
249
233
|
timeDimension:
|
|
250
|
-
dimension: created_at
|
|
234
|
+
dimension: orders.created_at
|
|
251
235
|
granularity: month
|
|
252
236
|
` ``
|
|
253
237
|
|
|
254
|
-
<LineChart data={trend} x="created_at" y="total_revenue" title="Monthly Revenue" yFmt="eur" />
|
|
238
|
+
<LineChart data={trend} x="orders.created_at" y="orders.total_revenue" title="Monthly Revenue" yFmt="eur" />
|
|
255
239
|
|
|
256
240
|
## By City
|
|
257
241
|
|
|
258
242
|
` ``query by_city
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
dimensions: [city]
|
|
243
|
+
measures: [orders.total_revenue]
|
|
244
|
+
dimensions: [orders.city]
|
|
262
245
|
orderBy:
|
|
263
|
-
total_revenue: desc
|
|
246
|
+
orders.total_revenue: desc
|
|
264
247
|
limit: 10
|
|
265
248
|
` ``
|
|
266
249
|
|
|
267
|
-
<BarChart data={by_city} x="city" y="total_revenue" title="Top Cities" yFmt="eur" />
|
|
250
|
+
<BarChart data={by_city} x="orders.city" y="orders.total_revenue" title="Top Cities" yFmt="eur" />
|
|
268
251
|
```
|
|
269
252
|
|
|
270
253
|
The `<DateRange>` automatically applies to all queries with a `timeDimension` (here: `trend`). The `<Dropdown>` filters `trend` and `by_city` by channel. The `channels` query populates the dropdown and is never filtered by it.
|
|
@@ -273,12 +256,12 @@ The `<DateRange>` automatically applies to all queries with a `timeDimension` (h
|
|
|
273
256
|
|
|
274
257
|
- **Start with KPIs**: Use `BigValue` in a `Grid` at the top for key metrics
|
|
275
258
|
- **One query per chart**: Each component gets its own query — keep them focused
|
|
276
|
-
- **Use views**: Prefer view names over cube names when available
|
|
259
|
+
- **Use views**: Prefer view names over cube names when available
|
|
277
260
|
- **Name queries descriptively**: `monthly_revenue` is better than `q1`
|
|
278
261
|
- **Limit large datasets**: Add `limit` to dimension queries to avoid oversized charts
|
|
279
262
|
- **Time series**: Always use `timeDimension` with `granularity` for time-based charts
|
|
280
|
-
- **Multi-series**: Use `series="column"` to split data by a dimension. For bars, default is stacked; use `type="grouped"` for side-by-side
|
|
281
|
-
- **Multiple y columns**: Use comma-separated values like `y="revenue,cases"` to show multiple measures on one chart
|
|
263
|
+
- **Multi-series**: Use `series="cube.column"` to split data by a dimension. For bars, default is stacked; use `type="grouped"` for side-by-side
|
|
264
|
+
- **Multiple y columns**: Use comma-separated values like `y="orders.revenue,orders.cases"` to show multiple measures on one chart
|
|
282
265
|
|
|
283
266
|
## See Also
|
|
284
267
|
|
|
@@ -54,7 +54,7 @@ Targeting lets you control which queries a filter affects — useful when some c
|
|
|
54
54
|
Renders a dropdown selector populated from a query's dimension values. Adds a filter on the specified dimension to targeted queries.
|
|
55
55
|
|
|
56
56
|
```markdown
|
|
57
|
-
<Dropdown name="channel" dimension="channel" data={channels} queries="main,trend" label="Channel" />
|
|
57
|
+
<Dropdown name="channel" dimension="orders.channel" data={channels} queries="main,trend" label="Channel" />
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
### Props
|
|
@@ -86,14 +86,13 @@ title: Revenue Trends
|
|
|
86
86
|
<DateRange name="period" default="last-6-months" label="Time Period" />
|
|
87
87
|
|
|
88
88
|
` ``query monthly_revenue
|
|
89
|
-
|
|
90
|
-
measures: [total_revenue]
|
|
89
|
+
measures: [orders.total_revenue]
|
|
91
90
|
timeDimension:
|
|
92
|
-
dimension: created_at
|
|
91
|
+
dimension: orders.created_at
|
|
93
92
|
granularity: month
|
|
94
93
|
` ``
|
|
95
94
|
|
|
96
|
-
<LineChart data={monthly_revenue} x="created_at" y="total_revenue" />
|
|
95
|
+
<LineChart data={monthly_revenue} x="orders.created_at" y="orders.total_revenue" />
|
|
97
96
|
```
|
|
98
97
|
|
|
99
98
|
The DateRange automatically applies to `monthly_revenue` because it has a `timeDimension`. No hardcoded `dateRange` needed in the query.
|
|
@@ -106,19 +105,17 @@ title: Sales by Channel
|
|
|
106
105
|
---
|
|
107
106
|
|
|
108
107
|
` ``query channels
|
|
109
|
-
|
|
110
|
-
dimensions: [channel]
|
|
108
|
+
dimensions: [orders.channel]
|
|
111
109
|
` ``
|
|
112
110
|
|
|
113
|
-
<Dropdown name="ch" dimension="channel" data={channels} queries="main" label="Channel" />
|
|
111
|
+
<Dropdown name="ch" dimension="orders.channel" data={channels} queries="main" label="Channel" />
|
|
114
112
|
|
|
115
113
|
` ``query main
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
dimensions: [city]
|
|
114
|
+
measures: [orders.total_revenue]
|
|
115
|
+
dimensions: [orders.city]
|
|
119
116
|
` ``
|
|
120
117
|
|
|
121
|
-
<BarChart data={main} x="city" y="total_revenue" />
|
|
118
|
+
<BarChart data={main} x="orders.city" y="orders.total_revenue" />
|
|
122
119
|
```
|
|
123
120
|
|
|
124
121
|
### Combined inputs
|
|
@@ -129,30 +126,27 @@ title: Sales Dashboard
|
|
|
129
126
|
---
|
|
130
127
|
|
|
131
128
|
<DateRange name="period" default="last-6-months" label="Time Period" />
|
|
132
|
-
<Dropdown name="channel" dimension="channel" data={channels} queries="trend,by_city" label="Channel" />
|
|
129
|
+
<Dropdown name="channel" dimension="orders.channel" data={channels} queries="trend,by_city" label="Channel" />
|
|
133
130
|
|
|
134
131
|
` ``query channels
|
|
135
|
-
|
|
136
|
-
dimensions: [channel]
|
|
132
|
+
dimensions: [orders.channel]
|
|
137
133
|
` ``
|
|
138
134
|
|
|
139
135
|
` ``query trend
|
|
140
|
-
|
|
141
|
-
measures: [total_revenue]
|
|
136
|
+
measures: [orders.total_revenue]
|
|
142
137
|
timeDimension:
|
|
143
|
-
dimension: created_at
|
|
138
|
+
dimension: orders.created_at
|
|
144
139
|
granularity: month
|
|
145
140
|
` ``
|
|
146
141
|
|
|
147
|
-
<LineChart data={trend} x="created_at" y="total_revenue" />
|
|
142
|
+
<LineChart data={trend} x="orders.created_at" y="orders.total_revenue" />
|
|
148
143
|
|
|
149
144
|
` ``query by_city
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
dimensions: [city]
|
|
145
|
+
measures: [orders.total_revenue]
|
|
146
|
+
dimensions: [orders.city]
|
|
153
147
|
` ``
|
|
154
148
|
|
|
155
|
-
<BarChart data={by_city} x="city" y="total_revenue" />
|
|
149
|
+
<BarChart data={by_city} x="orders.city" y="orders.total_revenue" />
|
|
156
150
|
```
|
|
157
151
|
|
|
158
152
|
Both inputs work together: the DateRange scopes the time window on `trend` (which has a timeDimension), and the Dropdown filters both `trend` and `by_city` by channel.
|
|
@@ -27,19 +27,17 @@ description: Key metrics for the orders pipeline
|
|
|
27
27
|
# Order Summary
|
|
28
28
|
|
|
29
29
|
` ``query order_count
|
|
30
|
-
|
|
31
|
-
measures: [count]
|
|
30
|
+
measures: [orders.count]
|
|
32
31
|
` ``
|
|
33
32
|
|
|
34
|
-
<BigValue data={order_count} value="count" title="Total Orders" />
|
|
33
|
+
<BigValue data={order_count} value="orders.count" title="Total Orders" />
|
|
35
34
|
|
|
36
35
|
` ``query by_status
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
dimensions: [status]
|
|
36
|
+
measures: [orders.count]
|
|
37
|
+
dimensions: [orders.status]
|
|
40
38
|
` ``
|
|
41
39
|
|
|
42
|
-
<BarChart data={by_status} x="status" y="count" />
|
|
40
|
+
<BarChart data={by_status} x="orders.status" y="orders.count" />
|
|
43
41
|
```
|
|
44
42
|
|
|
45
43
|
## Frontmatter
|