@databrainhq/mcp-server 0.2.17 → 0.3.0-rc.10
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/.claude-plugin/marketplace.json +21 -0
- package/.claude-plugin/plugin.json +13 -0
- package/.mcp.json +11 -0
- package/README.md +171 -7
- package/dist/apps/server.d.ts +323 -0
- package/dist/apps/server.js +11 -0
- package/dist/apps/server.js.map +1 -0
- package/dist/apps/stdio.d.ts +2 -0
- package/dist/apps/stdio.js +16 -0
- package/dist/apps/stdio.js.map +1 -0
- package/dist/chunk-5IGP6A6T.js +496 -0
- package/dist/chunk-5IGP6A6T.js.map +1 -0
- package/dist/chunk-CUPL7CAN.js +75 -0
- package/dist/chunk-CUPL7CAN.js.map +1 -0
- package/dist/chunk-ERTAKB2H.js +11093 -0
- package/dist/chunk-ERTAKB2H.js.map +1 -0
- package/dist/chunk-N7X762VU.js +34 -0
- package/dist/chunk-N7X762VU.js.map +1 -0
- package/dist/chunk-OCBTNAAO.js +1946 -0
- package/dist/chunk-OCBTNAAO.js.map +1 -0
- package/dist/chunk-Z3ZHN4UW.js +130 -0
- package/dist/chunk-Z3ZHN4UW.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +495 -0
- package/dist/cli.js.map +1 -0
- package/dist/custom-chart/runtime-shim.js +146 -0
- package/dist/custom-chart/template/AGENTS.md +374 -0
- package/dist/custom-chart/template/README.md +374 -0
- package/dist/custom-chart/template/chart.css +28 -0
- package/dist/custom-chart/template/chart.ts +192 -0
- package/dist/custom-chart/template/custom-chart.d.ts +78 -0
- package/dist/custom-chart/template/icon.svg +1 -0
- package/dist/custom-chart/template/manifest.json +23 -0
- package/dist/custom-chart/template/package.json +14 -0
- package/dist/custom-chart/template/sample-data.json +18 -0
- package/dist/custom-chart/template/tsconfig.json +12 -0
- package/dist/index.js +65 -9787
- package/dist/index.js.map +1 -1
- package/dist/main-KZRHWE3K.js +857 -0
- package/dist/main-KZRHWE3K.js.map +1 -0
- package/dist/test-only-K7QTSVO4.js +38 -0
- package/dist/test-only-K7QTSVO4.js.map +1 -0
- package/package.json +17 -5
- package/skills/author-dashboard/SKILL.md +30 -0
- package/skills/embed-dashboard/SKILL.md +23 -0
- package/skills/style-dashboard/SKILL.md +58 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Databrain custom chart runtime shim (DAT-925).
|
|
3
|
+
*
|
|
4
|
+
* The CLI (`databrain custom-chart build`) prepends this file, byte for byte,
|
|
5
|
+
* to the author's bundled IIFE. It is plain ES2020 with no imports so it runs
|
|
6
|
+
* inside the sandbox frame exactly as written. The plugin keeps a copy at
|
|
7
|
+
* `packages/@databrainhq/plugin/src/customChart/runtimeShim.js` and tests it;
|
|
8
|
+
* `contract/runtime-shim.js` in the fleet folder is the same bytes.
|
|
9
|
+
*
|
|
10
|
+
* Contract with the author bundle (see the authoring guide):
|
|
11
|
+
* - The bundle assigns `globalThis.__dbnCustomChart = { render, resize? }`.
|
|
12
|
+
* - The CLI may prepend `globalThis.__dbnCustomChartSlots = [...slot ids]`
|
|
13
|
+
* so every manifest slot is present on `ctx.slots`, unbound ones as `[]`.
|
|
14
|
+
* - `render(ctx)` runs on the first data message and on every data change.
|
|
15
|
+
* - `resize(ctx)` runs on window resize when the author defines it;
|
|
16
|
+
* otherwise `render(ctx)` runs again (one call per animation frame).
|
|
17
|
+
* - Inside the frame, `dbnChart.click(rowIndex, columnId)`,
|
|
18
|
+
* `dbnChart.filter(columnId, values)` and `dbnChart.resize(height)` map
|
|
19
|
+
* onto the sandbox protocol. There is no other host access.
|
|
20
|
+
* - A throw (or a rejected promise) inside render/resize is reported to the
|
|
21
|
+
* host once as `error/runtime`; the chart stops receiving data after it.
|
|
22
|
+
*/
|
|
23
|
+
(() => {
|
|
24
|
+
const sandbox = globalThis.dbnSandbox;
|
|
25
|
+
if (!sandbox || typeof sandbox.onData !== 'function') return;
|
|
26
|
+
|
|
27
|
+
const DEFAULT_THEME = Object.freeze({
|
|
28
|
+
fontFamily: 'system-ui, sans-serif',
|
|
29
|
+
fontSize: 12,
|
|
30
|
+
textColor: 'CanvasText',
|
|
31
|
+
textSecondaryColor: 'GrayText',
|
|
32
|
+
background: 'Canvas',
|
|
33
|
+
surfaceColor: 'Canvas',
|
|
34
|
+
borderColor: 'GrayText',
|
|
35
|
+
borderRadius: 8,
|
|
36
|
+
primaryColor: 'Highlight',
|
|
37
|
+
palette: [],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
Object.defineProperty(globalThis, 'dbnChart', {
|
|
41
|
+
value: Object.freeze({
|
|
42
|
+
click(rowIndex, columnId) {
|
|
43
|
+
sandbox.click(rowIndex, columnId);
|
|
44
|
+
},
|
|
45
|
+
filter(columnId, values) {
|
|
46
|
+
sandbox.filter(columnId, Array.isArray(values) ? values : []);
|
|
47
|
+
},
|
|
48
|
+
resize(height) {
|
|
49
|
+
sandbox.resize(height);
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
writable: false,
|
|
53
|
+
configurable: false,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
let failed = false;
|
|
57
|
+
let latest = null;
|
|
58
|
+
let frame = 0;
|
|
59
|
+
|
|
60
|
+
const report = (error) => {
|
|
61
|
+
if (failed) return;
|
|
62
|
+
failed = true;
|
|
63
|
+
latest = null;
|
|
64
|
+
try {
|
|
65
|
+
console.error('[dbn custom chart] render failed:', error);
|
|
66
|
+
} catch {}
|
|
67
|
+
const message =
|
|
68
|
+
error && typeof error.message === 'string'
|
|
69
|
+
? error.message
|
|
70
|
+
: String(error);
|
|
71
|
+
// The sandbox bootstrap listens for window "error" and reports it to the
|
|
72
|
+
// host as error/runtime. One event, then the chart is inert.
|
|
73
|
+
globalThis.dispatchEvent(new ErrorEvent('error', { error, message }));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const chart = () => {
|
|
77
|
+
const value = globalThis.__dbnCustomChart;
|
|
78
|
+
if (!value || typeof value.render !== 'function') {
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
'The chart bundle did not assign globalThis.__dbnCustomChart = { render }.'
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const slotsOf = (columns) => {
|
|
87
|
+
const slots = {};
|
|
88
|
+
const declared = globalThis.__dbnCustomChartSlots;
|
|
89
|
+
if (Array.isArray(declared)) {
|
|
90
|
+
for (const id of declared) if (typeof id === 'string') slots[id] = [];
|
|
91
|
+
}
|
|
92
|
+
columns.forEach((column, index) => {
|
|
93
|
+
const id = column?.slotId;
|
|
94
|
+
if (typeof id !== 'string') return;
|
|
95
|
+
if (!Array.isArray(slots[id])) slots[id] = [];
|
|
96
|
+
slots[id].push(index);
|
|
97
|
+
});
|
|
98
|
+
return slots;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const context = (data) => ({
|
|
102
|
+
container: document.getElementById('chart'),
|
|
103
|
+
data: { columns: data.columns, rows: data.rows },
|
|
104
|
+
slots: slotsOf(data.columns),
|
|
105
|
+
options: data.options,
|
|
106
|
+
theme: data.theme || DEFAULT_THEME,
|
|
107
|
+
language: typeof data.language === 'string' ? data.language : 'en',
|
|
108
|
+
dimensions: {
|
|
109
|
+
width: globalThis.innerWidth,
|
|
110
|
+
height: globalThis.innerHeight,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const run = (method, data) => {
|
|
115
|
+
if (failed) return;
|
|
116
|
+
let result;
|
|
117
|
+
try {
|
|
118
|
+
const value = chart();
|
|
119
|
+
const fn =
|
|
120
|
+
method === 'resize' && typeof value.resize === 'function'
|
|
121
|
+
? value.resize
|
|
122
|
+
: value.render;
|
|
123
|
+
result = fn.call(value, context(data));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
report(error);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (result && typeof result.then === 'function') {
|
|
129
|
+
result.then(undefined, report);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
sandbox.onData((data) => {
|
|
134
|
+
latest = data;
|
|
135
|
+
run('render', data);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
globalThis.addEventListener('resize', () => {
|
|
139
|
+
if (!latest || failed) return;
|
|
140
|
+
if (frame) return;
|
|
141
|
+
frame = globalThis.requestAnimationFrame(() => {
|
|
142
|
+
frame = 0;
|
|
143
|
+
if (latest) run('resize', latest);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
})();
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# Databrain custom chart: author guide
|
|
2
|
+
|
|
3
|
+
This folder is one custom chart type for Databrain. When it is uploaded and
|
|
4
|
+
published, every builder in your company can pick it in the metric editor as
|
|
5
|
+
chart type `custom:<key>` and put it on dashboards. The same guide is shipped
|
|
6
|
+
as `AGENTS.md` (for coding agents) and `README.md` (for people); the two files
|
|
7
|
+
are identical, and the `custom-chart` skill in `@databrainhq/agent-skills`
|
|
8
|
+
carries a byte identical copy as `references/authoring-guide.md`.
|
|
9
|
+
|
|
10
|
+
Read this file to the end before you change `chart.ts`.
|
|
11
|
+
|
|
12
|
+
## 1. What a custom chart is
|
|
13
|
+
|
|
14
|
+
A custom chart is a small package: a manifest that declares the data the chart
|
|
15
|
+
needs and the settings it exposes, a TypeScript module that draws into a DOM
|
|
16
|
+
element, an optional stylesheet and an icon. The CLI compiles the module into
|
|
17
|
+
one self contained `dist/index.js`. Databrain stores that file, serves it to
|
|
18
|
+
dashboards, resolves the metric's data onto the slots you declared, and runs
|
|
19
|
+
the bundle inside a sandboxed frame. The chart never sees a token, a query or
|
|
20
|
+
a network. It only sees rows, options, a theme and a container.
|
|
21
|
+
|
|
22
|
+
The lifecycle:
|
|
23
|
+
|
|
24
|
+
1. `databrain custom-chart init my-chart` writes this folder.
|
|
25
|
+
2. You edit `manifest.json` and `chart.ts`.
|
|
26
|
+
3. `databrain custom-chart validate` checks the manifest and the icon.
|
|
27
|
+
4. `databrain custom-chart build` writes `dist/`.
|
|
28
|
+
5. `databrain custom-chart upload` creates the chart in your company as a
|
|
29
|
+
private chart, version 1. A second upload creates version 2 in preview
|
|
30
|
+
while live dashboards keep the published version.
|
|
31
|
+
6. Publish (with `--publish`, the `publish_custom_chart` tool, or Settings >
|
|
32
|
+
Developers > Custom charts) makes a version live for every builder.
|
|
33
|
+
|
|
34
|
+
## 2. Project structure
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
my-chart/
|
|
38
|
+
manifest.json the contract: id, name, slots, options
|
|
39
|
+
chart.ts export function render(ctx), optional resize(ctx)
|
|
40
|
+
chart.css optional, uploaded next to the bundle; the host injects it into the frame
|
|
41
|
+
custom-chart.d.ts the CustomChartContext types (do not edit)
|
|
42
|
+
icon.svg required, at most 16 KiB, no script or external references
|
|
43
|
+
sample-data.json optional, used by `dev` and by the in app preview
|
|
44
|
+
package.json scripts: validate, build, dev, upload
|
|
45
|
+
tsconfig.json editor settings so chart.ts type checks
|
|
46
|
+
AGENTS.md this guide
|
|
47
|
+
README.md this guide
|
|
48
|
+
dist/ written by build, never edit by hand
|
|
49
|
+
index.js the shim plus your compiled module (upload this)
|
|
50
|
+
manifest.json the validated manifest
|
|
51
|
+
icon.svg the sanitized icon
|
|
52
|
+
index.css the raw chart.css, uploaded as bundleCss
|
|
53
|
+
bundle.zip the four files above, for drag and drop upload in the app
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Only `chart.ts` may import other files. You can add more `.ts` files next to
|
|
57
|
+
it and import them with a relative path. You can also `npm install` a browser
|
|
58
|
+
library and import it; the build bundles it. There is no `external`: whatever
|
|
59
|
+
you import ends up inside `index.js`, and the whole file must stay under 2 MiB.
|
|
60
|
+
|
|
61
|
+
## 3. The manifest
|
|
62
|
+
|
|
63
|
+
`manifest.json` is checked with the same rules in the CLI, in the MCP tools, on
|
|
64
|
+
the server and in the dashboard runtime. Unknown keys are refused at every
|
|
65
|
+
level. Every error carries a code shaped `manifest.<path>.<reason>`, a JSON
|
|
66
|
+
path and a sentence that says what to change.
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"schemaVersion": 2,
|
|
71
|
+
"id": "revenue-bars",
|
|
72
|
+
"name": "Revenue bars",
|
|
73
|
+
"version": "1.2.0",
|
|
74
|
+
"description": "Bars with a target line.",
|
|
75
|
+
"slots": [
|
|
76
|
+
{ "id": "category", "label": "Category", "role": "dimension", "types": ["string"], "required": true,
|
|
77
|
+
"description": "One bar per value." },
|
|
78
|
+
{ "id": "value", "label": "Value", "role": "measure", "types": ["number"], "required": true },
|
|
79
|
+
{ "id": "target", "label": "Target", "role": "measure", "types": ["number"], "required": false }
|
|
80
|
+
],
|
|
81
|
+
"options": [
|
|
82
|
+
{ "id": "show_labels", "label": "Show labels", "type": "boolean", "default": true, "group": "Labels" },
|
|
83
|
+
{ "id": "bar_radius", "label": "Corner radius", "type": "number", "default": 4, "min": 0, "max": 20, "step": 1 },
|
|
84
|
+
{ "id": "subtitle", "label": "Subtitle", "type": "string", "default": "", "maxLength": 120 },
|
|
85
|
+
{ "id": "mode", "label": "Mode", "type": "select", "default": "grouped",
|
|
86
|
+
"choices": [ { "value": "grouped", "label": "Grouped" }, { "value": "stacked", "label": "Stacked" } ] },
|
|
87
|
+
{ "id": "bar_color", "label": "Bar colour", "type": "color", "default": "#3366cc" }
|
|
88
|
+
],
|
|
89
|
+
"translations": {
|
|
90
|
+
"fr": {
|
|
91
|
+
"name": "Barres de revenus",
|
|
92
|
+
"slots": { "category": { "label": "Categorie" } },
|
|
93
|
+
"options": { "mode": { "label": "Mode", "choices": { "grouped": "Groupe", "stacked": "Empile" } } }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 3.1 Top level keys
|
|
100
|
+
|
|
101
|
+
| Key | Required | Rule |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `schemaVersion` | yes | `1` or `2`. Use `2`. Version 1 is the older sandbox manifest and does not allow `version`, `description`, `translations`, the `color` type, `step`, `group` or slot descriptions. |
|
|
104
|
+
| `id` | yes | The chart key. `^[a-z][a-z0-9_-]{0,63}$`. The chart type becomes `custom:<id>`. Changing it later creates a different chart. |
|
|
105
|
+
| `name` | yes | 1 to 256 characters. Shown in the chart picker. |
|
|
106
|
+
| `version` | no | Semver such as `1.2.0`. A label for you; Databrain numbers versions itself (1, 2, 3). |
|
|
107
|
+
| `description` | no | Up to 500 characters. |
|
|
108
|
+
| `slots` | yes | 1 to 16 slots with unique ids. See 3.2. |
|
|
109
|
+
| `options` | yes | 0 to 32 options with unique ids. Use `[]` for none. See 3.3. |
|
|
110
|
+
| `translations` | no | Per language overrides. See 3.4. |
|
|
111
|
+
|
|
112
|
+
The whole file must be at most 32 KiB as JSON.
|
|
113
|
+
|
|
114
|
+
### 3.2 Slots
|
|
115
|
+
|
|
116
|
+
A slot is one place where a builder binds a column of the metric. In the
|
|
117
|
+
metric editor each slot becomes a select: dimension slots list the metric's
|
|
118
|
+
dimension columns, measure slots list its measure columns.
|
|
119
|
+
|
|
120
|
+
| Field | Required | Rule |
|
|
121
|
+
| --- | --- | --- |
|
|
122
|
+
| `id` | yes | `^[a-z][a-z0-9_-]{0,63}$`, unique across slots. This is the key you read in `ctx.slots`. |
|
|
123
|
+
| `label` | yes | 1 to 256 characters. |
|
|
124
|
+
| `role` | yes | `"dimension"` (a category, a date, a flag) or `"measure"` (a number to aggregate). |
|
|
125
|
+
| `types` | yes | 1 to 3 unique values from `"string"`, `"number"`, `"boolean"`. A measure slot must be exactly `["number"]`. Dates arrive as ISO strings, so a date slot is `["string"]`. |
|
|
126
|
+
| `required` | yes | `true` when the chart cannot draw without it. The editor marks required slots and refuses to save without them. |
|
|
127
|
+
| `description` | no | Up to 256 characters, shown as help text. |
|
|
128
|
+
|
|
129
|
+
One slot binds one column. If a chart needs several series, declare several
|
|
130
|
+
slots (`value_1`, `value_2`) or one measure slot and one dimension slot that
|
|
131
|
+
splits the series. There is no "multiple" slot.
|
|
132
|
+
|
|
133
|
+
### 3.3 Options
|
|
134
|
+
|
|
135
|
+
Options are the settings a builder changes in the chart settings panel. Their
|
|
136
|
+
values reach the chart as `ctx.options[id]`, already merged with the defaults,
|
|
137
|
+
so you never need to check for a missing option.
|
|
138
|
+
|
|
139
|
+
Every option has `id` (`^[a-z][a-z0-9_-]{0,63}$`, unique), `label` (1 to 256
|
|
140
|
+
characters), `type`, `default` and an optional `group` (up to 64 characters;
|
|
141
|
+
options with the same group render as one section).
|
|
142
|
+
|
|
143
|
+
| `type` | Extra fields | Default rule | Rendered as |
|
|
144
|
+
| --- | --- | --- | --- |
|
|
145
|
+
| `boolean` | none | `true` or `false` | a switch |
|
|
146
|
+
| `number` | `min`, `max` (required, finite, `min <= max`), `step` (optional, positive, at most `max - min`) | a finite number inside `[min, max]` | a number input |
|
|
147
|
+
| `string` | `maxLength` (1 to 1024) | a string no longer than `maxLength` | a text input |
|
|
148
|
+
| `select` | `choices`: 1 to 32 of `{ "value", "label" }`, values unique and shaped like ids | one of the choice values | a select |
|
|
149
|
+
| `color` | none | `#rrggbb` (six hex digits) | the colour picker |
|
|
150
|
+
|
|
151
|
+
The id `theme` and any id that starts with `theme.` are reserved and refused
|
|
152
|
+
with `manifest.option.reserved`. The host theme arrives on `ctx.theme`, not as
|
|
153
|
+
an option.
|
|
154
|
+
|
|
155
|
+
### 3.4 Translations
|
|
156
|
+
|
|
157
|
+
`translations` is an object keyed by language code (`^[a-z]{2}(-[A-Z]{2})?$`,
|
|
158
|
+
for example `fr` or `pt-BR`). Each entry may override `name`, `description`,
|
|
159
|
+
`slots.<slotId>.label`, `slots.<slotId>.description`, `options.<optionId>.label`,
|
|
160
|
+
`options.<optionId>.group` and `options.<optionId>.choices.<value>`. Every
|
|
161
|
+
referenced slot, option and choice must exist in the manifest, and every
|
|
162
|
+
string is 1 to 256 characters. The editor uses the builder's language;
|
|
163
|
+
`ctx.language` tells the chart which language the viewer has.
|
|
164
|
+
|
|
165
|
+
### 3.5 Error codes you will meet
|
|
166
|
+
|
|
167
|
+
| Code | Meaning |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `manifest.schemaVersion.unsupported` | `schemaVersion` is not 1 or 2. |
|
|
170
|
+
| `manifest.unknownKey` | A key that is not in the table above (or a v2 key on a v1 manifest). |
|
|
171
|
+
| `manifest.id.invalid` | The id does not match the pattern. Lowercase, no spaces. |
|
|
172
|
+
| `manifest.slots.empty`, `manifest.slots.tooMany` | Declare 1 to 16 slots. |
|
|
173
|
+
| `manifest.slot.types` | A measure slot must be exactly `["number"]`, or a type is not string, number or boolean. |
|
|
174
|
+
| `manifest.slot.duplicate` | Two slots share an id. |
|
|
175
|
+
| `manifest.option.type` | Unknown option type, or `color` on schemaVersion 1. |
|
|
176
|
+
| `manifest.option.reserved` | The option id is `theme` or starts with `theme.`. |
|
|
177
|
+
| `manifest.option.default` | The default is outside the range, longer than `maxLength` or not a choice. |
|
|
178
|
+
| `manifest.option.step` | `step` is not positive or is larger than `max - min`. |
|
|
179
|
+
| `manifest.option.color` | The colour default is not `#rrggbb`. |
|
|
180
|
+
| `manifest.option.duplicate` | Two options share an id. |
|
|
181
|
+
| `manifest.translations.language` | A key is not a language code. |
|
|
182
|
+
| `manifest.translations.slot`, `.option`, `.choice` | A translation names something the manifest does not declare. |
|
|
183
|
+
| `manifest.size` | The manifest is over 32 KiB as JSON. Reported once every other rule passes. |
|
|
184
|
+
| `bundle.icon.required`, `bundle.icon.size`, `bundle.icon.notSvg`, `bundle.icon.unsafe` | The icon is missing, over 16 KiB, not one `<svg>` element, or breaks a rule from section 7 (the message names the rule). |
|
|
185
|
+
| `bundle.js.size` | `dist/index.js` is over 2 MiB. |
|
|
186
|
+
| `bundle.css.size` | `chart.css` is over 256 KiB. |
|
|
187
|
+
| `bundle.build.failed` | esbuild could not compile `chart.ts`; the message is the compiler's. An import that does not resolve inside the project (a node builtin, a path outside the folder) is this too. |
|
|
188
|
+
| `bundle.build.noRender` | `chart.ts` is not an ES module with a named `render` export. `module.exports` and `export default` are not read. |
|
|
189
|
+
|
|
190
|
+
## 4. The data your chart receives
|
|
191
|
+
|
|
192
|
+
Databrain calls `render(ctx)` with one `CustomChartContext` (see
|
|
193
|
+
`custom-chart.d.ts`):
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import type { CustomChartContext } from '@databrainhq/custom-chart-types';
|
|
197
|
+
|
|
198
|
+
export function render({ container, data, slots, options, theme, language, dimensions }: CustomChartContext): void {
|
|
199
|
+
// draw into container
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function resize(ctx: CustomChartContext): void {
|
|
203
|
+
// optional; without it render runs again on every size change
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| Field | What it is |
|
|
208
|
+
| --- | --- |
|
|
209
|
+
| `container` | The `HTMLElement` to draw into. It fills the tile. Clear it before drawing again (`container.replaceChildren()`); render runs again on every data update. |
|
|
210
|
+
| `data.columns` | One entry per bound column, in manifest slot order: `{ id, label, type, filterable, slotId }`. `id` is what you pass to `dbnChart.click` and `dbnChart.filter`. `filterable` is true on dimension columns. |
|
|
211
|
+
| `data.rows` | An array of rows, each an array of cells aligned with `data.columns`. Cells are `string`, `number`, `boolean` or `null`. Row order is the metric's order. Rows are already tenant scoped. At most 5000 rows and 64 columns reach the chart. |
|
|
212
|
+
| `slots` | `slotId -> number[]`, the indexes into `data.columns` bound to that slot. An optional slot that is not bound gives `[]`. Read columns through slots, never by position. |
|
|
213
|
+
| `options` | The option values, defaults merged with what the builder saved. |
|
|
214
|
+
| `theme` | The flat host theme (section 5). |
|
|
215
|
+
| `language` | The viewer's language code, for example `en` or `fr`. |
|
|
216
|
+
| `dimensions` | `{ width, height }` of the container in CSS pixels at the time of the call. |
|
|
217
|
+
|
|
218
|
+
A typical start of `render`:
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
const categoryIndex = ctx.slots.category[0];
|
|
222
|
+
const valueIndex = ctx.slots.value[0];
|
|
223
|
+
const targetIndex = ctx.slots.target[0]; // undefined when the optional slot is empty
|
|
224
|
+
for (const row of ctx.data.rows) {
|
|
225
|
+
const label = String(row[categoryIndex] ?? '');
|
|
226
|
+
const value = typeof row[valueIndex] === 'number' ? row[valueIndex] : 0;
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Numbers arrive as numbers, not strings. Dates arrive as ISO strings; parse
|
|
231
|
+
them with `new Date(value)` if you need a time axis. Booleans arrive as
|
|
232
|
+
booleans. Null means no value: draw a gap, not a zero, unless the chart says
|
|
233
|
+
otherwise.
|
|
234
|
+
|
|
235
|
+
`sample-data.json` uses the same shape (`columns`, `rows`, optional
|
|
236
|
+
`options`) and is what `databrain custom-chart dev` and the in app preview
|
|
237
|
+
feed to the chart. Keep it small and realistic.
|
|
238
|
+
|
|
239
|
+
## 5. Theme
|
|
240
|
+
|
|
241
|
+
`ctx.theme` is derived from the dashboard's resolved theme so a custom chart
|
|
242
|
+
looks like the charts around it. Every key is always present; when a host
|
|
243
|
+
sends no theme at all, the shim supplies a neutral default with an empty
|
|
244
|
+
palette, so fall back to `primaryColor` when `palette` is empty.
|
|
245
|
+
|
|
246
|
+
| Key | Type | Use it for |
|
|
247
|
+
| --- | --- | --- |
|
|
248
|
+
| `fontFamily` | string | Text. |
|
|
249
|
+
| `fontSize` | number (px) | The base text size. Axis labels are usually `fontSize * 0.9`. |
|
|
250
|
+
| `textColor` | string | Titles, values, primary text. |
|
|
251
|
+
| `textSecondaryColor` | string | Axis labels, subtitles, hints. |
|
|
252
|
+
| `background` | string | The tile background. Set it on the container if you paint one. |
|
|
253
|
+
| `surfaceColor` | string | Cards, tooltips, legends. |
|
|
254
|
+
| `borderColor` | string | Axis lines, gridlines, borders. |
|
|
255
|
+
| `borderRadius` | number (px) | Corners. |
|
|
256
|
+
| `primaryColor` | string | The accent colour. |
|
|
257
|
+
| `palette` | string[] | Series colours in order. At least one entry, at most 32. |
|
|
258
|
+
|
|
259
|
+
Use the theme first and your own colours second. A `color` option is the
|
|
260
|
+
right way to let a builder override one colour; a hard coded `#ff0000` in
|
|
261
|
+
`chart.ts` is not. Do not read `prefers-color-scheme`: the host decides.
|
|
262
|
+
|
|
263
|
+
`chart.css` is uploaded next to the bundle (`bundleCss`) and the host injects
|
|
264
|
+
it into the frame as a style element, so class names cannot clash with the
|
|
265
|
+
host page. It cannot load fonts, images or any `url(...)`; the sandbox blocks
|
|
266
|
+
network access.
|
|
267
|
+
|
|
268
|
+
## 6. Interactions
|
|
269
|
+
|
|
270
|
+
Inside the chart a global `dbnChart` is available (typed in
|
|
271
|
+
`custom-chart.d.ts`). Every call is an event to the host; the host decides
|
|
272
|
+
what happens, and nothing in the chart can navigate, fetch or write.
|
|
273
|
+
|
|
274
|
+
| Call | Effect on the dashboard |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| `dbnChart.click(rowIndex, columnId)` | The tile's data point click. `rowIndex` indexes `data.rows`; the host reads the row's slot values and passes them to the embedding app's `onDataPointClick`. |
|
|
277
|
+
| `dbnChart.filter(columnId, values)` | Cross filter the dashboard on that column when it is `filterable` and the values are in the current rows. At most 100 values. An empty list clears the filter. Ignored otherwise. |
|
|
278
|
+
| `dbnChart.resize(height)` | Ask the host for a different tile height, 80 to 1200 pixels. Width always belongs to the host. |
|
|
279
|
+
|
|
280
|
+
Bind them to real user actions (click, keyboard), never call them in a loop.
|
|
281
|
+
The host caps events at 60 per second and drops the rest.
|
|
282
|
+
|
|
283
|
+
## 7. Icon
|
|
284
|
+
|
|
285
|
+
`icon.svg` shows in the chart picker and in Settings. Rules, checked at build
|
|
286
|
+
and again on upload:
|
|
287
|
+
|
|
288
|
+
- One `<svg>` element, at most 16 KiB.
|
|
289
|
+
- No `<script>`, no `<foreignObject>`, no `<image>`, no `on*=` attributes.
|
|
290
|
+
- No `url(...)` in styles or attributes except `url(#id)`, no `href` that does not start with `#`.
|
|
291
|
+
- No comments, no DOCTYPE, no CDATA, no animation elements, and only the standard SVG
|
|
292
|
+
drawing attributes (an editor's `inkscape:` or `sodipodi:` attributes are refused).
|
|
293
|
+
The build strips the XML declaration and comments for you; the upload does not.
|
|
294
|
+
- Use `currentColor` for strokes and fills so the icon follows the UI theme.
|
|
295
|
+
|
|
296
|
+
The template icon is a 24 by 24 outline on a 2 pixel stroke. Keep that size.
|
|
297
|
+
|
|
298
|
+
## 8. Build, upload, publish
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
databrain custom-chart validate # manifest, icon, css. No compile.
|
|
302
|
+
databrain custom-chart build # esbuild, writes dist/
|
|
303
|
+
databrain custom-chart dev # local page on http://127.0.0.1:4174
|
|
304
|
+
databrain custom-chart upload # build, server validate, plan, confirm, upload
|
|
305
|
+
databrain custom-chart upload --publish # same, then publish that version
|
|
306
|
+
databrain custom-chart upload --yes # no confirmation prompt (CI)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`build` compiles `chart.ts` with esbuild (`format: iife`, `target: es2020`,
|
|
310
|
+
minified, everything bundled), prepends the Databrain runtime shim and the
|
|
311
|
+
list of slot ids, checks the manifest and the icon, enforces 2 MiB and writes
|
|
312
|
+
`dist/`. The shim is the only code between your module and the sandbox: it
|
|
313
|
+
receives the data message, builds the context, calls your `render` and
|
|
314
|
+
`resize` (once per animation frame on resize), and maps `dbnChart` to the
|
|
315
|
+
sandbox protocol. If `render` throws or returns a rejected promise, the shim
|
|
316
|
+
reports it to the host once and the chart stops receiving data. Do not edit
|
|
317
|
+
`dist/index.js`.
|
|
318
|
+
|
|
319
|
+
`upload` needs `DATABRAIN_SERVICE_TOKEN` (Settings > Service Tokens) and, on
|
|
320
|
+
self hosted instances, `DATABRAIN_API_URL`. It builds, asks the server to
|
|
321
|
+
validate, prints the plan (`will create chart` or `will create version n`),
|
|
322
|
+
waits for `y`, then uploads. The first upload creates the chart with status
|
|
323
|
+
Private and version 1. Every later upload with different content creates the
|
|
324
|
+
next version in preview; the same content is recognised by its hash and
|
|
325
|
+
creates nothing. Live dashboards keep rendering the published version until
|
|
326
|
+
someone publishes the preview.
|
|
327
|
+
|
|
328
|
+
Publishing is the one step that changes what customers see. Do it with
|
|
329
|
+
`--publish`, with the `publish_custom_chart` MCP tool (dry run first, then
|
|
330
|
+
`confirm: "APPLY_TO_PRODUCTION"`), or in Settings > Developers > Custom charts.
|
|
331
|
+
|
|
332
|
+
From an agent, the same steps are the MCP tools `custom_chart_scaffold`,
|
|
333
|
+
`custom_chart_validate`, `custom_chart_upload` (dry run, then apply with the
|
|
334
|
+
confirmation string) and `publish_custom_chart`. The tools never write to disk
|
|
335
|
+
and never publish without the confirmation.
|
|
336
|
+
|
|
337
|
+
## 9. Writing a good chart
|
|
338
|
+
|
|
339
|
+
- Draw with plain DOM or SVG when you can. A dependency is fine when it earns
|
|
340
|
+
its bytes; check the size line that `build` prints.
|
|
341
|
+
- Read columns through `ctx.slots`, never by position, and handle an empty
|
|
342
|
+
optional slot (`[]`).
|
|
343
|
+
- Clear the container at the top of `render`. The host calls it again on
|
|
344
|
+
every data update and, without `resize`, on every size change.
|
|
345
|
+
- Use `ctx.dimensions` for the drawing size and `ctx.theme` for every colour
|
|
346
|
+
and font you do not expose as an option.
|
|
347
|
+
- Format numbers with `Intl.NumberFormat(ctx.language)`.
|
|
348
|
+
- Show a short message when the required data is missing instead of throwing.
|
|
349
|
+
A thrown error is reported to the host once, the tile shows a generic
|
|
350
|
+
failure and the chart receives no more data.
|
|
351
|
+
- Keep `render` under a few milliseconds for 5000 rows. Avoid layout reads in
|
|
352
|
+
a loop.
|
|
353
|
+
- Do not touch `window.parent`, `document.cookie`, `fetch`, `localStorage` or
|
|
354
|
+
timers that outlive a render. The sandbox blocks them and a reviewer will
|
|
355
|
+
reject the chart.
|
|
356
|
+
|
|
357
|
+
## 10. Troubleshooting
|
|
358
|
+
|
|
359
|
+
| Symptom | Cause and fix |
|
|
360
|
+
| --- | --- |
|
|
361
|
+
| `manifest.unknownKey` at `/version` | The manifest says `schemaVersion: 1`. Set it to `2`. |
|
|
362
|
+
| `manifest.slot.types` at `/slots/N/types` | A measure slot lists something other than `["number"]`. Change the role to `dimension` or the types to `["number"]`. |
|
|
363
|
+
| `manifest.option.reserved` | Rename the option; `theme` is the host theme. |
|
|
364
|
+
| `bundle.build.failed` with a TypeScript message | esbuild reports the file and line. It does not type check; run `npx tsc -p tsconfig.json` in the project for types. |
|
|
365
|
+
| `bundle.js.size` | A dependency is too big. Import only what you use, or draw it yourself. |
|
|
366
|
+
| `bundle.icon.unsafe` | Export the icon again without links, images, styles with `url(...)` or scripts. Inline the paths. |
|
|
367
|
+
| The chart is blank in `dev` | The events panel shows `error` with code `runtime` and the browser console shows `[dbn custom chart] render failed:` with the error. Fix it and reload. |
|
|
368
|
+
| The chart draws once and never updates | `render` does not clear the container, or it caches the first rows. Clear and redraw from `ctx` each time. |
|
|
369
|
+
| Labels are unreadable on a dark dashboard | Colours are hard coded. Use `ctx.theme.textColor` and `ctx.theme.palette`. |
|
|
370
|
+
| Clicks do nothing on the dashboard | `dbnChart.click` was given a row index that is not in `data.rows`, or the embedding app has no `onDataPointClick`. |
|
|
371
|
+
| `upload` says `DATABRAIN_SERVICE_TOKEN is not set` | Export the token in the shell that runs the command. Never write it into a file in the project. |
|
|
372
|
+
| `upload` says the server refused the bundle | The server runs the same rules; the printed problems name the path to fix. Rebuild and upload again. |
|
|
373
|
+
| Dashboards still show the old chart after upload | Uploads land in preview. Publish the version. |
|
|
374
|
+
| `This tile uses a custom chart (custom:key) that is not published.` | The dashboard references a key that is private or missing in this company. Publish it, or pick another chart type. |
|