@xpert-ai/plugin-live-dashboard 0.3.0 → 0.4.0
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/.xpertai-plugin/plugin.json +10 -2
- package/README.md +70 -0
- package/assets/branding/cover-v1.prompt.txt +27 -0
- package/assets/branding/cover-v1.webp +0 -0
- package/assets/previews/customer-brief.png +0 -0
- package/assets/previews/profit-atlas.png +0 -0
- package/assets/previews/trend-workspace.png +0 -0
- package/dist/index.js +14 -1
- package/dist/lib/agent.schema.d.ts +43 -37
- package/dist/lib/agent.schema.js +2 -1
- package/dist/lib/app-config.d.ts +62 -0
- package/dist/lib/app-config.js +38 -0
- package/dist/lib/artifact-type.provider.d.ts +15 -0
- package/dist/lib/artifact-type.provider.js +78 -0
- package/dist/lib/assistant-template.d.ts +2 -0
- package/dist/lib/assistant-template.js +7 -0
- package/dist/lib/content.provider.js +13 -5
- package/dist/lib/dashboard-navigation.d.ts +58 -14
- package/dist/lib/dashboard-navigation.js +11 -10
- package/dist/lib/dashboard-view.schema.d.ts +197 -46
- package/dist/lib/dashboard-view.schema.js +8 -1
- package/dist/lib/dashboard.service.d.ts +39 -2
- package/dist/lib/dashboard.service.js +52 -28
- package/dist/lib/document-libraries.d.ts +1 -1
- package/dist/lib/document-libraries.js +3 -2
- package/dist/lib/document-network.d.ts +1 -1
- package/dist/lib/document-network.js +2 -6
- package/dist/lib/editing-feedback.d.ts +5 -0
- package/dist/lib/editing-feedback.js +19 -0
- package/dist/lib/editing-patch.d.ts +22 -0
- package/dist/lib/editing-patch.js +46 -0
- package/dist/lib/editing-session.d.ts +167 -0
- package/dist/lib/editing-session.js +53 -0
- package/dist/lib/editing.d.ts +92 -60
- package/dist/lib/editing.js +36 -24
- package/dist/lib/html-guidance.d.ts +1 -1
- package/dist/lib/html-guidance.js +1 -1
- package/dist/lib/middleware.js +48 -20
- package/dist/lib/monitor.d.ts +70 -0
- package/dist/lib/monitor.js +101 -0
- package/dist/lib/plugin.js +5 -1
- package/dist/lib/project.d.ts +106 -0
- package/dist/lib/project.js +17 -0
- package/dist/lib/projects.schema.d.ts +49 -0
- package/dist/lib/projects.schema.js +7 -0
- package/dist/lib/projects.service.d.ts +23 -0
- package/dist/lib/projects.service.js +65 -0
- package/dist/lib/prompt-workflows.d.ts +3 -2
- package/dist/lib/prompt-workflows.js +17 -3
- package/dist/lib/provider-options.controller.d.ts +10 -0
- package/dist/lib/provider-options.controller.js +49 -0
- package/dist/lib/resource-card.js +1 -1
- package/dist/lib/runtime-context.d.ts +3 -0
- package/dist/lib/runtime-context.js +13 -0
- package/dist/lib/save-evidence.d.ts +1 -1
- package/dist/lib/templates.d.ts +2 -0
- package/dist/lib/templates.js +55 -0
- package/dist/lib/view.provider.d.ts +97 -7
- package/dist/lib/view.provider.js +39 -10
- package/dist/libraries/TREND-WORKSPACE-ICONS-LICENSE.txt +43 -0
- package/dist/libraries/catalog.json +1 -1
- package/dist/remote/app.css +1 -1
- package/dist/remote/app.js +33 -33
- package/dist/remote/build-manifest.json +118 -32
- package/dist/sandbox-actions/action.json +1 -1
- package/dist/sandbox-actions/bundle/host.js +2 -2
- package/dist/sandbox-actions/bundle/resource-cache.mjs +2 -1
- package/dist/sandbox-actions/bundle/validate.mjs +1 -1
- package/docs/docs.json +7 -0
- package/docs/frontend-libraries.mdx +12 -4
- package/docs/html-mode.md +3 -3
- package/docs/quick-start-acceptance.mdx +50 -0
- package/docs/quick-start.mdx +64 -0
- package/docs/superstore.mdx +38 -0
- package/docs/team-sharing.mdx +6 -2
- package/docs/trend-workspace-acceptance.mdx +43 -0
- package/docs/trend-workspace.mdx +30 -0
- package/docs/verification.mdx +4 -2
- package/docs/visual-styles-acceptance.mdx +40 -0
- package/docs/visual-styles.mdx +46 -0
- package/package.json +25 -18
- package/scripts/import-prompts.mjs +108 -0
- package/skills/live-dashboard/SKILL.md +27 -3
- package/skills/live-dashboard/examples/README.md +25 -0
- package/skills/live-dashboard/examples/catalog.json +79 -0
- package/skills/live-dashboard/examples/production-quality/README.md +25 -0
- package/skills/live-dashboard/examples/production-quality/dashboard.html +129 -0
- package/skills/live-dashboard/examples/production-quality/definition.json +67 -0
- package/skills/live-dashboard/examples/production-quality/demo.csv +7 -0
- package/skills/live-dashboard/examples/production-quality/demo.json +44 -0
- package/skills/live-dashboard/examples/production-quality/expected.json +26 -0
- package/skills/live-dashboard/examples/production-quality/preview.png +0 -0
- package/skills/live-dashboard/examples/production-quality/query.json +53 -0
- package/skills/live-dashboard/examples/regional-sales/README.md +25 -0
- package/skills/live-dashboard/examples/regional-sales/dashboard-create.json +35 -0
- package/skills/live-dashboard/examples/regional-sales/definition.json +35 -0
- package/skills/live-dashboard/examples/regional-sales/demo.csv +5 -0
- package/skills/live-dashboard/examples/regional-sales/demo.json +18 -0
- package/skills/live-dashboard/examples/regional-sales/expected.json +19 -0
- package/skills/live-dashboard/examples/regional-sales/preview.png +0 -0
- package/skills/live-dashboard/examples/regional-sales/query.json +45 -0
- package/skills/live-dashboard/examples/sales-overview/README.md +25 -0
- package/skills/live-dashboard/examples/sales-overview/dashboard.html +129 -0
- package/skills/live-dashboard/examples/sales-overview/definition.json +57 -0
- package/skills/live-dashboard/examples/sales-overview/demo.csv +7 -0
- package/skills/live-dashboard/examples/sales-overview/demo.json +38 -0
- package/skills/live-dashboard/examples/sales-overview/expected.json +22 -0
- package/skills/live-dashboard/examples/sales-overview/preview.png +0 -0
- package/skills/live-dashboard/examples/sales-overview/query.json +52 -0
- package/skills/live-dashboard/examples/superstore/README.md +64 -0
- package/skills/live-dashboard/examples/superstore/THIRD_PARTY_LICENSE.txt +207 -0
- package/skills/live-dashboard/examples/superstore/build_data.py +155 -0
- package/skills/live-dashboard/examples/superstore/build_pages.py +129 -0
- package/skills/live-dashboard/examples/superstore/checks.json +104 -0
- package/skills/live-dashboard/examples/superstore/customers-mobile-dark.png +0 -0
- package/skills/live-dashboard/examples/superstore/customers-select.png +0 -0
- package/skills/live-dashboard/examples/superstore/customers.html +316 -0
- package/skills/live-dashboard/examples/superstore/customers.png +0 -0
- package/skills/live-dashboard/examples/superstore/customers.queries.json +512 -0
- package/skills/live-dashboard/examples/superstore/expected.json +4069 -0
- package/skills/live-dashboard/examples/superstore/import.json +200 -0
- package/skills/live-dashboard/examples/superstore/orders.csv +10195 -0
- package/skills/live-dashboard/examples/superstore/overview-mobile-dark.png +0 -0
- package/skills/live-dashboard/examples/superstore/overview-select.png +0 -0
- package/skills/live-dashboard/examples/superstore/overview.html +310 -0
- package/skills/live-dashboard/examples/superstore/overview.png +0 -0
- package/skills/live-dashboard/examples/superstore/overview.queries.json +636 -0
- package/skills/live-dashboard/examples/superstore/page.js +109 -0
- package/skills/live-dashboard/examples/superstore/profitability-mobile-dark.png +0 -0
- package/skills/live-dashboard/examples/superstore/profitability-select.png +0 -0
- package/skills/live-dashboard/examples/superstore/profitability.html +307 -0
- package/skills/live-dashboard/examples/superstore/profitability.png +0 -0
- package/skills/live-dashboard/examples/superstore/profitability.queries.json +334 -0
- package/skills/live-dashboard/examples/superstore/provenance.json +24 -0
- package/skills/live-dashboard/examples/superstore/semantic-model.json +220 -0
- package/skills/live-dashboard/examples/superstore/test_data.py +62 -0
- package/skills/live-dashboard/references/html-runtime.md +1 -1
- package/skills/live-dashboard/themes/README.md +50 -0
- package/skills/live-dashboard/themes/customer-brief/README.md +47 -0
- package/skills/live-dashboard/themes/customer-brief/charts.js +17 -0
- package/skills/live-dashboard/themes/customer-brief/layout.html +22 -0
- package/skills/live-dashboard/themes/customer-brief/theme.css +108 -0
- package/skills/live-dashboard/themes/profit-atlas/README.md +43 -0
- package/skills/live-dashboard/themes/profit-atlas/charts.js +30 -0
- package/skills/live-dashboard/themes/profit-atlas/layout.html +19 -0
- package/skills/live-dashboard/themes/profit-atlas/theme.css +75 -0
- package/skills/live-dashboard/themes/trend-workspace/ICON_LICENSE.txt +43 -0
- package/skills/live-dashboard/themes/trend-workspace/README.md +144 -0
- package/skills/live-dashboard/themes/trend-workspace/charts.js +60 -0
- package/skills/live-dashboard/themes/trend-workspace/icons.js +11 -0
- package/skills/live-dashboard/themes/trend-workspace/layout.html +45 -0
- package/skills/live-dashboard/themes/trend-workspace/select.js +127 -0
- package/skills/live-dashboard/themes/trend-workspace/theme.css +137 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
{
|
|
2
|
+
"controls": [
|
|
3
|
+
{
|
|
4
|
+
"controlId": "year",
|
|
5
|
+
"label": "年份",
|
|
6
|
+
"type": "string",
|
|
7
|
+
"defaultValue": "",
|
|
8
|
+
"allowedValues": [
|
|
9
|
+
"",
|
|
10
|
+
"2021",
|
|
11
|
+
"2022",
|
|
12
|
+
"2023",
|
|
13
|
+
"2024"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"controlId": "region",
|
|
18
|
+
"label": "区域",
|
|
19
|
+
"type": "string",
|
|
20
|
+
"defaultValue": "",
|
|
21
|
+
"allowedValues": [
|
|
22
|
+
"",
|
|
23
|
+
"Central",
|
|
24
|
+
"East",
|
|
25
|
+
"South",
|
|
26
|
+
"West"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"controlId": "category",
|
|
31
|
+
"label": "品类",
|
|
32
|
+
"type": "string",
|
|
33
|
+
"defaultValue": "",
|
|
34
|
+
"allowedValues": [
|
|
35
|
+
"",
|
|
36
|
+
"Furniture",
|
|
37
|
+
"Office Supplies",
|
|
38
|
+
"Technology"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"bindings": [
|
|
43
|
+
{
|
|
44
|
+
"id": "summary",
|
|
45
|
+
"providerId": "data-xpert.ontology",
|
|
46
|
+
"operation": "action",
|
|
47
|
+
"contractVersion": 1,
|
|
48
|
+
"payload": {
|
|
49
|
+
"resourceId": "<discovered-resource-id>",
|
|
50
|
+
"actionTypeCode": "sql.query_cube_slice",
|
|
51
|
+
"target": {
|
|
52
|
+
"entityTypeCode": "semantic_cube",
|
|
53
|
+
"entityRef": "<published-model-id>::SuperstoreOrders"
|
|
54
|
+
},
|
|
55
|
+
"params": {
|
|
56
|
+
"dimensions": [],
|
|
57
|
+
"measures": [
|
|
58
|
+
"Sales",
|
|
59
|
+
"Profit",
|
|
60
|
+
"Orders",
|
|
61
|
+
"Customers"
|
|
62
|
+
],
|
|
63
|
+
"filters": [
|
|
64
|
+
{
|
|
65
|
+
"field": "OrderYear",
|
|
66
|
+
"op": "contains",
|
|
67
|
+
"value": ""
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"field": "Region",
|
|
71
|
+
"op": "contains",
|
|
72
|
+
"value": ""
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"field": "Category",
|
|
76
|
+
"op": "contains",
|
|
77
|
+
"value": ""
|
|
78
|
+
}
|
|
79
|
+
],
|
|
80
|
+
"limit": 100
|
|
81
|
+
},
|
|
82
|
+
"parameterPaths": {
|
|
83
|
+
"year": [
|
|
84
|
+
"filters",
|
|
85
|
+
"0",
|
|
86
|
+
"value"
|
|
87
|
+
],
|
|
88
|
+
"region": [
|
|
89
|
+
"filters",
|
|
90
|
+
"1",
|
|
91
|
+
"value"
|
|
92
|
+
],
|
|
93
|
+
"category": [
|
|
94
|
+
"filters",
|
|
95
|
+
"2",
|
|
96
|
+
"value"
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"parameters": [
|
|
101
|
+
{
|
|
102
|
+
"name": "year",
|
|
103
|
+
"controlId": "year",
|
|
104
|
+
"type": "string",
|
|
105
|
+
"required": true,
|
|
106
|
+
"defaultValue": "",
|
|
107
|
+
"allowedValues": [
|
|
108
|
+
"",
|
|
109
|
+
"2021",
|
|
110
|
+
"2022",
|
|
111
|
+
"2023",
|
|
112
|
+
"2024"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "region",
|
|
117
|
+
"controlId": "region",
|
|
118
|
+
"type": "string",
|
|
119
|
+
"required": true,
|
|
120
|
+
"defaultValue": "",
|
|
121
|
+
"allowedValues": [
|
|
122
|
+
"",
|
|
123
|
+
"Central",
|
|
124
|
+
"East",
|
|
125
|
+
"South",
|
|
126
|
+
"West"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "category",
|
|
131
|
+
"controlId": "category",
|
|
132
|
+
"type": "string",
|
|
133
|
+
"required": true,
|
|
134
|
+
"defaultValue": "",
|
|
135
|
+
"allowedValues": [
|
|
136
|
+
"",
|
|
137
|
+
"Furniture",
|
|
138
|
+
"Office Supplies",
|
|
139
|
+
"Technology"
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
],
|
|
143
|
+
"output": {
|
|
144
|
+
"datasetId": "summary",
|
|
145
|
+
"fields": [
|
|
146
|
+
{
|
|
147
|
+
"id": "Sales",
|
|
148
|
+
"sourceField": "Sales",
|
|
149
|
+
"label": "Sales",
|
|
150
|
+
"logicalType": "decimal",
|
|
151
|
+
"role": "measure",
|
|
152
|
+
"nullable": true
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"id": "Profit",
|
|
156
|
+
"sourceField": "Profit",
|
|
157
|
+
"label": "Profit",
|
|
158
|
+
"logicalType": "decimal",
|
|
159
|
+
"role": "measure",
|
|
160
|
+
"nullable": true
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"id": "Orders",
|
|
164
|
+
"sourceField": "Orders",
|
|
165
|
+
"label": "Orders",
|
|
166
|
+
"logicalType": "int64",
|
|
167
|
+
"role": "measure",
|
|
168
|
+
"nullable": false
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"id": "Customers",
|
|
172
|
+
"sourceField": "Customers",
|
|
173
|
+
"label": "Customers",
|
|
174
|
+
"logicalType": "int64",
|
|
175
|
+
"role": "measure",
|
|
176
|
+
"nullable": false
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
"grain": []
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "subcategories",
|
|
184
|
+
"providerId": "data-xpert.ontology",
|
|
185
|
+
"operation": "action",
|
|
186
|
+
"contractVersion": 1,
|
|
187
|
+
"payload": {
|
|
188
|
+
"resourceId": "<discovered-resource-id>",
|
|
189
|
+
"actionTypeCode": "sql.query_cube_slice",
|
|
190
|
+
"target": {
|
|
191
|
+
"entityTypeCode": "semantic_cube",
|
|
192
|
+
"entityRef": "<published-model-id>::SuperstoreOrders"
|
|
193
|
+
},
|
|
194
|
+
"params": {
|
|
195
|
+
"dimensions": [
|
|
196
|
+
"SubCategory"
|
|
197
|
+
],
|
|
198
|
+
"measures": [
|
|
199
|
+
"Sales",
|
|
200
|
+
"Profit",
|
|
201
|
+
"Orders",
|
|
202
|
+
"Customers"
|
|
203
|
+
],
|
|
204
|
+
"filters": [
|
|
205
|
+
{
|
|
206
|
+
"field": "OrderYear",
|
|
207
|
+
"op": "contains",
|
|
208
|
+
"value": ""
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"field": "Region",
|
|
212
|
+
"op": "contains",
|
|
213
|
+
"value": ""
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"field": "Category",
|
|
217
|
+
"op": "contains",
|
|
218
|
+
"value": ""
|
|
219
|
+
}
|
|
220
|
+
],
|
|
221
|
+
"limit": 100
|
|
222
|
+
},
|
|
223
|
+
"parameterPaths": {
|
|
224
|
+
"year": [
|
|
225
|
+
"filters",
|
|
226
|
+
"0",
|
|
227
|
+
"value"
|
|
228
|
+
],
|
|
229
|
+
"region": [
|
|
230
|
+
"filters",
|
|
231
|
+
"1",
|
|
232
|
+
"value"
|
|
233
|
+
],
|
|
234
|
+
"category": [
|
|
235
|
+
"filters",
|
|
236
|
+
"2",
|
|
237
|
+
"value"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"parameters": [
|
|
242
|
+
{
|
|
243
|
+
"name": "year",
|
|
244
|
+
"controlId": "year",
|
|
245
|
+
"type": "string",
|
|
246
|
+
"required": true,
|
|
247
|
+
"defaultValue": "",
|
|
248
|
+
"allowedValues": [
|
|
249
|
+
"",
|
|
250
|
+
"2021",
|
|
251
|
+
"2022",
|
|
252
|
+
"2023",
|
|
253
|
+
"2024"
|
|
254
|
+
]
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"name": "region",
|
|
258
|
+
"controlId": "region",
|
|
259
|
+
"type": "string",
|
|
260
|
+
"required": true,
|
|
261
|
+
"defaultValue": "",
|
|
262
|
+
"allowedValues": [
|
|
263
|
+
"",
|
|
264
|
+
"Central",
|
|
265
|
+
"East",
|
|
266
|
+
"South",
|
|
267
|
+
"West"
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"name": "category",
|
|
272
|
+
"controlId": "category",
|
|
273
|
+
"type": "string",
|
|
274
|
+
"required": true,
|
|
275
|
+
"defaultValue": "",
|
|
276
|
+
"allowedValues": [
|
|
277
|
+
"",
|
|
278
|
+
"Furniture",
|
|
279
|
+
"Office Supplies",
|
|
280
|
+
"Technology"
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
"output": {
|
|
285
|
+
"datasetId": "subcategories",
|
|
286
|
+
"fields": [
|
|
287
|
+
{
|
|
288
|
+
"id": "SubCategory",
|
|
289
|
+
"sourceField": "SubCategory",
|
|
290
|
+
"label": "SubCategory",
|
|
291
|
+
"logicalType": "string",
|
|
292
|
+
"role": "dimension",
|
|
293
|
+
"nullable": false
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"id": "Sales",
|
|
297
|
+
"sourceField": "Sales",
|
|
298
|
+
"label": "Sales",
|
|
299
|
+
"logicalType": "decimal",
|
|
300
|
+
"role": "measure",
|
|
301
|
+
"nullable": true
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"id": "Profit",
|
|
305
|
+
"sourceField": "Profit",
|
|
306
|
+
"label": "Profit",
|
|
307
|
+
"logicalType": "decimal",
|
|
308
|
+
"role": "measure",
|
|
309
|
+
"nullable": true
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
"id": "Orders",
|
|
313
|
+
"sourceField": "Orders",
|
|
314
|
+
"label": "Orders",
|
|
315
|
+
"logicalType": "int64",
|
|
316
|
+
"role": "measure",
|
|
317
|
+
"nullable": false
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"id": "Customers",
|
|
321
|
+
"sourceField": "Customers",
|
|
322
|
+
"label": "Customers",
|
|
323
|
+
"logicalType": "int64",
|
|
324
|
+
"role": "measure",
|
|
325
|
+
"nullable": false
|
|
326
|
+
}
|
|
327
|
+
],
|
|
328
|
+
"grain": [
|
|
329
|
+
"SubCategory"
|
|
330
|
+
]
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
]
|
|
334
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"dataClassification": "fictional teaching data, not actual customer operations",
|
|
4
|
+
"publisher": "Tableau / Salesforce",
|
|
5
|
+
"sourcePage": "https://tableau.github.io/extensions-api/docs/dataapps/trex_dataapp_examples/",
|
|
6
|
+
"repositoryPath": "Samples/DataApp/Tableau Superstore Data App/Data/Datasources/Sample - Superstore.xls",
|
|
7
|
+
"blobUrl": "https://api.github.com/repos/tableau/extensions-api/git/blobs/7f294f45f39c3e82b4dc738ffa5f8692c3c000d8",
|
|
8
|
+
"sourceSha256": "add4e193479cae8e9f22bfa734dd8bb706355dce38353967668f4e2b6643d19c",
|
|
9
|
+
"csvSha256": "7939076bc3b0c5841a9514da2dc2fbce91df97a05f36a586952ca95d64fcb37b",
|
|
10
|
+
"license": "Apache-2.0",
|
|
11
|
+
"licenseUrl": "https://github.com/tableau/extensions-api/blob/main/LICENSE.txt",
|
|
12
|
+
"transformations": [
|
|
13
|
+
"Normalize Excel date serials to ISO dates; decimal values to six places",
|
|
14
|
+
"Deduplicate 800 Returns rows to 296 unique Order IDs before enrichment",
|
|
15
|
+
"Preserve 10194 order-line rows; enrich with one manager per region",
|
|
16
|
+
"Derive order_year, order_month, returned, nullable returned_order_id and returned_sales",
|
|
17
|
+
"Postal codes remain text; blank returned_order_id is SQL NULL on CSV import"
|
|
18
|
+
],
|
|
19
|
+
"visualReference": {
|
|
20
|
+
"author": "Luke Stanke / phData",
|
|
21
|
+
"url": "https://www.phdata.io/blog/building-an-executive-dashboard-in-tableau/",
|
|
22
|
+
"usage": "Information hierarchy reference only; original page implementation; no copied screenshot or brand assets"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cubes": [
|
|
3
|
+
{
|
|
4
|
+
"name": "SuperstoreOrders",
|
|
5
|
+
"caption": "Superstore 订单分析(演示)",
|
|
6
|
+
"fact": {
|
|
7
|
+
"type": "table",
|
|
8
|
+
"table": {
|
|
9
|
+
"name": "superstore_orders_v1",
|
|
10
|
+
"schema": "public"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"defaultMeasure": "Sales",
|
|
14
|
+
"measures": [
|
|
15
|
+
{
|
|
16
|
+
"name": "Sales",
|
|
17
|
+
"column": "sales",
|
|
18
|
+
"aggregator": "sum",
|
|
19
|
+
"caption": "销售额(USD)",
|
|
20
|
+
"datatype": "Numeric",
|
|
21
|
+
"visible": true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "Profit",
|
|
25
|
+
"column": "profit",
|
|
26
|
+
"aggregator": "sum",
|
|
27
|
+
"caption": "利润(USD)",
|
|
28
|
+
"datatype": "Numeric",
|
|
29
|
+
"visible": true
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "Quantity",
|
|
33
|
+
"column": "quantity",
|
|
34
|
+
"aggregator": "sum",
|
|
35
|
+
"caption": "商品件数",
|
|
36
|
+
"datatype": "Numeric",
|
|
37
|
+
"visible": true
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "Orders",
|
|
41
|
+
"column": "order_id",
|
|
42
|
+
"aggregator": "distinct-count",
|
|
43
|
+
"caption": "订单数",
|
|
44
|
+
"datatype": "Numeric",
|
|
45
|
+
"visible": true
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "Customers",
|
|
49
|
+
"column": "customer_id",
|
|
50
|
+
"aggregator": "distinct-count",
|
|
51
|
+
"caption": "客户数",
|
|
52
|
+
"datatype": "Numeric",
|
|
53
|
+
"visible": true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "ReturnedOrders",
|
|
57
|
+
"column": "returned_order_id",
|
|
58
|
+
"aggregator": "distinct-count",
|
|
59
|
+
"caption": "退货订单数",
|
|
60
|
+
"datatype": "Numeric",
|
|
61
|
+
"visible": true
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "ReturnedSales",
|
|
65
|
+
"column": "returned_sales",
|
|
66
|
+
"aggregator": "sum",
|
|
67
|
+
"caption": "退货关联销售额(USD,非退款)",
|
|
68
|
+
"datatype": "Numeric",
|
|
69
|
+
"visible": true
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"dimensions": [
|
|
73
|
+
{
|
|
74
|
+
"name": "OrderYear",
|
|
75
|
+
"caption": "年份",
|
|
76
|
+
"hierarchies": [
|
|
77
|
+
{
|
|
78
|
+
"name": "OrderYear",
|
|
79
|
+
"hasAll": true,
|
|
80
|
+
"levels": [
|
|
81
|
+
{
|
|
82
|
+
"name": "OrderYear",
|
|
83
|
+
"column": "order_year",
|
|
84
|
+
"type": "String",
|
|
85
|
+
"uniqueMembers": true
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "OrderMonth",
|
|
93
|
+
"caption": "订单月份",
|
|
94
|
+
"hierarchies": [
|
|
95
|
+
{
|
|
96
|
+
"name": "OrderMonth",
|
|
97
|
+
"hasAll": true,
|
|
98
|
+
"levels": [
|
|
99
|
+
{
|
|
100
|
+
"name": "OrderMonth",
|
|
101
|
+
"column": "order_month",
|
|
102
|
+
"type": "String",
|
|
103
|
+
"uniqueMembers": true
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "Region",
|
|
111
|
+
"caption": "区域",
|
|
112
|
+
"hierarchies": [
|
|
113
|
+
{
|
|
114
|
+
"name": "Region",
|
|
115
|
+
"hasAll": true,
|
|
116
|
+
"levels": [
|
|
117
|
+
{
|
|
118
|
+
"name": "Region",
|
|
119
|
+
"column": "region",
|
|
120
|
+
"type": "String",
|
|
121
|
+
"uniqueMembers": true
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "Category",
|
|
129
|
+
"caption": "品类",
|
|
130
|
+
"hierarchies": [
|
|
131
|
+
{
|
|
132
|
+
"name": "Category",
|
|
133
|
+
"hasAll": true,
|
|
134
|
+
"levels": [
|
|
135
|
+
{
|
|
136
|
+
"name": "Category",
|
|
137
|
+
"column": "category",
|
|
138
|
+
"type": "String",
|
|
139
|
+
"uniqueMembers": true
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"name": "SubCategory",
|
|
147
|
+
"caption": "子品类",
|
|
148
|
+
"hierarchies": [
|
|
149
|
+
{
|
|
150
|
+
"name": "SubCategory",
|
|
151
|
+
"hasAll": true,
|
|
152
|
+
"levels": [
|
|
153
|
+
{
|
|
154
|
+
"name": "SubCategory",
|
|
155
|
+
"column": "sub_category",
|
|
156
|
+
"type": "String",
|
|
157
|
+
"uniqueMembers": true
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "Segment",
|
|
165
|
+
"caption": "客户分群",
|
|
166
|
+
"hierarchies": [
|
|
167
|
+
{
|
|
168
|
+
"name": "Segment",
|
|
169
|
+
"hasAll": true,
|
|
170
|
+
"levels": [
|
|
171
|
+
{
|
|
172
|
+
"name": "Segment",
|
|
173
|
+
"column": "segment",
|
|
174
|
+
"type": "String",
|
|
175
|
+
"uniqueMembers": true
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"name": "Returned",
|
|
183
|
+
"caption": "退货标记",
|
|
184
|
+
"hierarchies": [
|
|
185
|
+
{
|
|
186
|
+
"name": "Returned",
|
|
187
|
+
"hasAll": true,
|
|
188
|
+
"levels": [
|
|
189
|
+
{
|
|
190
|
+
"name": "Returned",
|
|
191
|
+
"column": "returned",
|
|
192
|
+
"type": "String",
|
|
193
|
+
"uniqueMembers": true
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "RegionalManager",
|
|
201
|
+
"caption": "区域经理",
|
|
202
|
+
"hierarchies": [
|
|
203
|
+
{
|
|
204
|
+
"name": "RegionalManager",
|
|
205
|
+
"hasAll": true,
|
|
206
|
+
"levels": [
|
|
207
|
+
{
|
|
208
|
+
"name": "RegionalManager",
|
|
209
|
+
"column": "regional_manager",
|
|
210
|
+
"type": "String",
|
|
211
|
+
"uniqueMembers": true
|
|
212
|
+
}
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Independent checks of packaged CSV and aggregate baselines; no xlrd required."""
|
|
2
|
+
import csv
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import tempfile
|
|
6
|
+
import unittest
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from build_data import read_workbook
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SuperstoreDataTest(unittest.TestCase):
|
|
15
|
+
@classmethod
|
|
16
|
+
def setUpClass(cls):
|
|
17
|
+
with (ROOT / 'orders.csv').open() as source:
|
|
18
|
+
cls.rows = list(csv.DictReader(source))
|
|
19
|
+
cls.expected = json.loads((ROOT / 'expected.json').read_text())
|
|
20
|
+
|
|
21
|
+
def test_provenance_and_unique_order_lines(self):
|
|
22
|
+
provenance = json.loads((ROOT / 'provenance.json').read_text())
|
|
23
|
+
self.assertEqual(hashlib.sha256((ROOT / 'orders.csv').read_bytes()).hexdigest(), provenance['csvSha256'])
|
|
24
|
+
self.assertEqual(len(self.rows), 10194)
|
|
25
|
+
self.assertEqual(len({row['row_id'] for row in self.rows}), 10194)
|
|
26
|
+
self.assertEqual(min(row['order_date'] for row in self.rows), '2021-01-03')
|
|
27
|
+
self.assertEqual(max(row['order_date'] for row in self.rows), '2024-12-30')
|
|
28
|
+
|
|
29
|
+
def test_returns_do_not_multiply_facts(self):
|
|
30
|
+
returned = {row['order_id'] for row in self.rows if row['returned'] == 'Yes'}
|
|
31
|
+
self.assertEqual(len(returned), 296)
|
|
32
|
+
for row in self.rows:
|
|
33
|
+
self.assertEqual(row['returned_order_id'], row['order_id'] if row['returned'] == 'Yes' else '')
|
|
34
|
+
self.assertEqual(Decimal(row['returned_sales']), Decimal(row['sales']) if row['returned'] == 'Yes' else 0)
|
|
35
|
+
|
|
36
|
+
def test_all_filter_baselines_from_csv(self):
|
|
37
|
+
for name, case in self.expected['cases'].items():
|
|
38
|
+
with self.subTest(name=name):
|
|
39
|
+
rows = [r for r in self.rows if all(r[k] == v for k, v in case['filters'].items())]
|
|
40
|
+
self.assertEqual(len(rows), case['rows'])
|
|
41
|
+
for field, measure in [('sales', 'Sales'), ('profit', 'Profit'), ('returned_sales', 'ReturnedSales')]:
|
|
42
|
+
self.assertEqual(sum((Decimal(r[field]) for r in rows), Decimal(0)), Decimal(case['totals'][measure]))
|
|
43
|
+
for field, measure in [('order_id', 'Orders'), ('customer_id', 'Customers'), ('returned_order_id', 'ReturnedOrders')]:
|
|
44
|
+
self.assertEqual(len({r[field] for r in rows if r[field]}), case['totals'][measure])
|
|
45
|
+
|
|
46
|
+
def test_counts_must_not_be_added_across_groups(self):
|
|
47
|
+
case = self.expected['cases']['all']
|
|
48
|
+
self.assertGreater(sum(row['Customers'] for row in case['groups']['OrderMonth']), case['totals']['Customers'])
|
|
49
|
+
self.assertGreater(sum(row['Orders'] for row in case['groups']['SubCategory']), case['totals']['Orders'])
|
|
50
|
+
tables = next(row for row in case['groups']['SubCategory'] if row['SubCategory'] == 'Tables')
|
|
51
|
+
self.assertEqual(Decimal(tables['Profit']), Decimal('-17753.206100'))
|
|
52
|
+
|
|
53
|
+
def test_changed_workbook_rejected_before_parsing(self):
|
|
54
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
55
|
+
workbook = Path(directory) / 'wrong.xls'
|
|
56
|
+
workbook.write_bytes(b'not the pinned workbook')
|
|
57
|
+
with self.assertRaisesRegex(ValueError, 'SHA-256'):
|
|
58
|
+
read_workbook(workbook)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
unittest.main()
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
When the user requests a custom HTML/JavaScript page, free layout, or a specific visual style, use HTML mode. Before writing JavaScript, read [html-lifecycle.md](html-lifecycle.md) for the runnable lifecycle pattern. Keep its SDK initialization, subscriptions, typed filter fan-out and refresh behavior while changing the layout freely. Build the smallest complete page first; avoid extra aggregate Bindings when complete rows already support every panel:
|
|
4
4
|
1. Discover and preview the real data exactly as for a standard dashboard. Reuse the returned previewId and binding.id; the latter is the identifier passed to the page SDK. No hard-coded business rows or stale snapshots in HTML.
|
|
5
5
|
Prefer ONE complete Dataset per page when its rows already support the requested KPIs, trends, breakdown and details. Compute display aggregates from those complete rows in frontend JS. The standard chart renderer's separate-aggregate-binding requirement does not apply to custom HTML. If multiple datasets are truly necessary, every bindingId passed to query, subscribe or bindSelect MUST have its previewId included in dashboard_create_html. An upstream preview that is omitted from the saved previewIds is NOT available to the page and returns DOCUMENT_BINDING_DENIED. Never query an auxiliary totals binding without saving it, and propagate shared filters to every binding used in a dependent total.
|
|
6
|
-
2. Write a complete single-file HTML/CSS/JavaScript document using sandbox_write_file({file_path,content}); use sandbox_edit_file or sandbox_multi_edit_file for existing files. Use inline styles/scripts, SVG, Canvas and DOM for any layout. Online scripts, stylesheets and
|
|
6
|
+
2. Write a complete single-file HTML/CSS/JavaScript document using sandbox_write_file({file_path,content}); use sandbox_edit_file or sandbox_multi_edit_file for existing files. Use inline styles/scripts, SVG, Canvas and DOM for any layout. Network access is temporarily unrestricted for functional acceptance, as reported by dashboard_libraries.online. Online scripts, stylesheets, fonts, images and fetch/XHR are available subject to browser CORS and mixed-content rules. Tailwind CDN can be loaded with <script src="https://cdn.tailwindcss.com/3.4.17"></script>. Use complete pinned URLs where available. Host DOM/storage access, forms and eval remain unavailable. Business data must use saved Bindings; do not embed credentials in HTML. No credentials, API URLs or backend queries in page code. Call dashboard_libraries to discover packaged dependencies. ECharts and Three.js are supported without CDN: pass libraries:["echarts"] or ["three"] (or both) to dashboard_create_html; the host injects window.echarts/window.THREE before your page code. Use the documented bundled versions. Do not paste library source, use React/JSX or assume other globals. Use packaged ECharts/Three.js when requested in libraries; use ordinary script/link tags for online resources, without listing them in libraries. CDN resources must be reachable during preflight and viewing; they are not frozen into the Artifact snapshot. ECharts can use SVG or Canvas: initialize a sized container once, setOption on each Dataset update and resize with ResizeObserver. When filters can remove a series/category, replace the old series: use chart.setOption(option, {notMerge:true}) or replaceMerge:["series"]. The default merge keeps stale series even when the new Dataset has fewer regions. Verify all → one region → another region → all against both totals and plotted series. Three.js core supports WebGLRenderer with WebGL2; catch unavailable GPU contexts, show a meaningful fallback, dispose resources, and do not request external textures/models/addons/workers. Libraries only render data: all queries still use the page SDK. Dependency bytes, versions and SHA-256 are frozen with the Artifact; preflight runs the identical snapshot.
|
|
7
7
|
3. The injected SDK is window.xpertLiveArtifact. await live.ready() returns {bindingIds,defaults,theme,locale}. live.query(bindingId, {controlId:value}) returns the canonical Dataset {fields,rows,grain,completeness,freshness,provenance}. Field values use output.fields[].id; decimal/int64 stay exact strings. Use Number only for safe plotting; show exact monetary values. Treat received text as untrusted: use textContent, not innerHTML interpolation.
|
|
8
8
|
4. Register live.subscribe(bindingId, dataset => render(dataset)) BEFORE the first query. Call live.query on initial load and filter changes; subscription callbacks redraw both normal results and toolbar refresh results. live.refresh(bindingId) repeats the latest controls. Catch errors and show an error state. Null means all only when the declared parameter allows it. Every control ID/type/default/allowedValues must match the preview and the controls argument to save.
|
|
9
9
|
For a dimension selector controlling ONE binding (such as region), use the SDK helper before the initial query: live.bindSelect(document.getElementById('regionFilter'), {bindingId, controlId:'region', fieldId:'region', allLabel:'全部区域'}). Replace IDs with the exact preview IDs. It populates unique values from received Dataset rows, preserves options across filtering, and calls the declared query on change. Do not add another change listener or manually rewrite its options/values: its option values are JSON encoded to preserve types. Keep a visible associated label. Handle the bubbling xpert-query-error event to display an error. Its optional allValue defaults to null; pass the actual declared all sentinel if different. Do not use it for fixed query parameters such as forecast days, page size or granularity: these values are not Dataset rows.
|