@malloy-publisher/skills 0.1.39 → 0.1.41
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/package.json +1 -1
- package/skills/eval-answer/SKILL.md +1 -1
- package/skills/eval-diagnose/SKILL.md +1 -1
- package/skills/eval-import/SKILL.md +1 -1
- package/skills/eval-improve/SKILL.md +1 -1
- package/skills/eval-judge/SKILL.md +1 -1
- package/skills/eval-loop/SKILL.md +1 -1
- package/skills/eval-loop/templates/eval-run-package/README.md +2 -2
- package/skills/eval-loop/templates/eval-run-package/notebooks/eval_run.malloy +58 -50
- package/skills/eval-report/SKILL.md +1 -1
- package/skills/malloy-analysis/SKILL.md +1 -1
- package/skills/malloy-analysis-report/SKILL.md +88 -60
- package/skills/malloy-charts/SKILL.md +1 -1
- package/skills/malloy-dashboards/SKILL.md +1 -1
- package/skills/malloy-define/SKILL.md +2 -2
- package/skills/malloy-discover/SKILL.md +1 -1
- package/skills/malloy-document/SKILL.md +1 -1
- package/skills/malloy-getting-started/SKILL.md +1 -1
- package/skills/malloy-gotchas-modeling/SKILL.md +1 -1
- package/skills/malloy-html-data-apps/SKILL.md +1 -1
- package/skills/malloy-lookml-review/SKILL.md +1 -1
- package/skills/malloy-materialization/SKILL.md +1 -1
- package/skills/malloy-model/SKILL.md +22 -14
- package/skills/malloy-model/reference/analysis-to-model.md +1 -1
- package/skills/malloy-model-as-you-go/SKILL.md +1 -1
- package/skills/malloy-modeling/SKILL.md +1 -1
- package/skills/malloy-notebooks/SKILL.md +1 -1
- package/skills/malloy-phrase-detection/SKILL.md +1 -1
- package/skills/malloy-powerbi-review/SKILL.md +1 -1
- package/skills/malloy-publish/SKILL.md +2 -2
- package/skills/malloy-queries/SKILL.md +2 -2
- package/skills/malloy-reports/SKILL.md +89 -24
- package/skills/malloy-reports/reference/linear.md +32 -106
- package/skills/malloy-review/SKILL.md +1 -1
- package/skills/malloy-review/reference/rubric-structure.md +1 -1
- package/skills/malloy-run-agent/SKILL.md +1 -1
- package/skills/malloy-source-unreachable/SKILL.md +1 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-materialization
|
|
3
3
|
description: Add, confirm and debug Malloy Persistence materializations in a package - persist an expensive source so queries read a pre-built table, in the source's own warehouse or in a storage destination. Read this whenever the user wants to materialize a source, add a persist annotation, speed up a slow source, build one stored rollup over another, tune what to persist, or asks why a persist source isn't building or isn't being read.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-model
|
|
3
3
|
description: Build Malloy semantic models with base source and joined source files. Use when creating or modifying .malloy files, user asks to "create a malloy model", "add dimensions", "add measures", "create a source", or any Malloy model authoring task.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -206,31 +206,39 @@ gets unwieldy.
|
|
|
206
206
|
|
|
207
207
|
## Parameterizing sources with `given:` (preferred)
|
|
208
208
|
|
|
209
|
-
Native Malloy **`given:` parameters** are
|
|
209
|
+
Native Malloy **`given:` parameters** are runtime values a model reads in its own logic; callers supply them at query time. `#(filter)` is deprecated: never add one. Enable givens with `##! experimental.givens` at the top of the model.
|
|
210
|
+
|
|
211
|
+
**A given belongs in the model only when the data's meaning or access depends on it:**
|
|
212
|
+
|
|
213
|
+
- a row-level gate: `#(authorize)`, `#(access_filter)`, a `#(secure)` given, `$GROUPS`;
|
|
214
|
+
- a value every query must supply (the no-default row in the table below);
|
|
215
|
+
- a parameter a measure, dimension or view is defined with;
|
|
216
|
+
- scoping the author means to apply to every consumer of the source: chat, MCP and every report.
|
|
217
|
+
|
|
218
|
+
**A filter a reader picks on a dashboard or notebook is not modeling.** Manufacturer, a date range, region, a "major recalls only" toggle: don't add it to a source. Leave the source unfiltered, and let the report declare the given and bind it on its tiles (`skill:malloy-reports`). A model-level `where:` applies to every read of the source, chat and every other report included, and a report cannot take it over, because binding is per declaration, not per name. The report builder shows such a filter but cannot remove or reconfigure it.
|
|
210
219
|
|
|
211
220
|
```malloy
|
|
212
221
|
##! experimental.givens
|
|
213
222
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
subject_filter :: filter<string> is f''
|
|
223
|
+
# A value every query must supply: the table is partitioned by day.
|
|
224
|
+
given: EVENT_DATE :: date
|
|
217
225
|
|
|
218
|
-
source:
|
|
219
|
-
where:
|
|
220
|
-
measure:
|
|
226
|
+
source: events is duckdb.table('data/events.parquet') extend {
|
|
227
|
+
where: event_date = $EVENT_DATE
|
|
228
|
+
measure: event_count is count()
|
|
221
229
|
}
|
|
222
230
|
```
|
|
223
231
|
|
|
224
|
-
A given is **declared bare** but **referenced with a `$` sigil** in expressions (`$
|
|
232
|
+
A given is **declared bare** but **referenced with a `$` sigil** in expressions (`$EVENT_DATE`), as above.
|
|
225
233
|
|
|
226
|
-
- **
|
|
234
|
+
- **An optional given in the model gets a neutral, match-all default** (a `filter<>` given defaulting to `f''`), so an unsupplied value returns unfiltered rows. Because it bakes an always-on `where:` into the source, a non-neutral default (a date floor, say) applies to *every* read of the source. Defaults must be Malloy literals.
|
|
227
235
|
- **Givens don't auto-inject a `where:`.** Unlike `#(filter)`, you write the filter expression that references the given yourself (e.g. `where: dimension ~ $given_name`).
|
|
228
236
|
- **Every `#(filter)` use has a `given:` form.** Never add a `#(filter)` annotation to a model, not even for `required`, `implicit`, or a date/number range:
|
|
229
237
|
|
|
230
238
|
| You want | Write this | Not this |
|
|
231
239
|
|---|---|---|
|
|
232
|
-
| An optional value or list filter | `given: REGION :: filter<string> is f''`
|
|
233
|
-
| A date or number range | `given: MIN_SALE :: filter<number> is f''` (or `filter<date>`)
|
|
240
|
+
| An optional value or list filter a reader picks | In the **report**, not the model: `given: REGION :: filter<string> is f''` there, bound on its tiles with `+ { where: region ~ $REGION }` (`skill:malloy-reports`). In the model only for one of the cases above. | `#(filter) dimension=region type=in` |
|
|
241
|
+
| A date or number range a reader picks | In the **report** the same way: `given: MIN_SALE :: filter<number> is f''` (or `filter<date>`), bound with `+ { where: sale_price ~ $MIN_SALE }`. The caller sends a filter expression such as `>= 50`. | two `#(filter)` lines, `greater_than` and `less_than` |
|
|
234
242
|
| A value every query must supply (the primary key is only unique within it, or the table is too big to scan whole) | a given with **no default**, e.g. `given: EVENT_DATE :: date` and `where: event_date = $EVENT_DATE`. A query that omits it fails with "Given 'EVENT_DATE' has no value and no default". | `#(filter) ... required` |
|
|
235
243
|
| A row filter the system applies, not the caller | `#(access_filter) org_id in $ORG_IDS`, with `ORG_IDS` set by a trusted tier (see the trust caveat below) | `#(filter) ... implicit` |
|
|
236
244
|
|
|
@@ -260,7 +268,7 @@ Publisher formats the value from the dimension's type (`'value'`, bare `true`/`f
|
|
|
260
268
|
|
|
261
269
|
`#(filter)` is not a security boundary. A caller skips it with `bypass_filters=true` (REST) or `bypassFilters: true` (POST body), or by writing their own query text. Use givens with `#(authorize)` / `#(access_filter)` for access control.
|
|
262
270
|
|
|
263
|
-
**To migrate** a source, replace each annotation using the table above, then delete it. `type=greater_than` and `type=less_than` are exclusive, so a plain comparison you convert one to is `>` or `<`, not `>=` or `<=`. `docs/givens.md` § "Coming from `#(filter)`" has a worked conversion.
|
|
271
|
+
**To migrate** a source, replace each annotation using the table above, then delete it. An optional filter (`type=in`, `like`, `equal`, a range) moves to the reports that offer it, declared and bound there, and the source is left unfiltered; a `required` one stays in the model as a no-default given. `type=greater_than` and `type=less_than` are exclusive, so a plain comparison you convert one to is `>` or `<`, not `>=` or `<=`. `docs/givens.md` § "Coming from `#(filter)`" has a worked conversion.
|
|
264
272
|
|
|
265
273
|
## Access Control: `#(authorize)` and `#(access_filter)`
|
|
266
274
|
|
|
@@ -318,7 +326,7 @@ source: orders_admin is orders_base extend {}
|
|
|
318
326
|
- **Both only gate from the `source:` line.** The same annotation on a `dimension:`/`measure:`/`join_*:`/`view:` line, or on a top-level `query:`, is refused at load naming the position rather than silently protecting nothing.
|
|
319
327
|
- **Every given the gate references must be declared on the entry model's own surface, and must carry no default.** In a package curated with `index.malloy`, "on the surface" means `index.malloy` imports it: import the declaring file whole, or name the given in a selective import (`import { orders, GROUPS } from "orders.malloy"`). A given the model cannot resolve is refused at load. So is a referenced given declared *with* a default: a caller who supplies nothing would get that default and be admitted or excluded by a value the gate's own line never shows, so it is refused rather than reasoned about case by case.
|
|
320
328
|
- **A scalar/array mismatch between the operator and the given's declared type is a load-time refusal**, not a request-time warehouse error: `org_id in $GROUPS` requires `GROUPS` to be array-typed, `region = $REGION` requires `REGION` scalar. Negation is likewise refused outright, so there is no empty-given inversion surprise to warn about.
|
|
321
|
-
- **Entry point only: not joined, but inherited through `extend`.** The gate applies to the source a query enters through. A gate on a source reached only via `join_*` **never fires**, at any depth, so anything ungated that joins a locked base hands the base's rows to every caller. A source that `extend`s a locked base and declares no gate of its own **does** carry the base's gate; declaring its own annotation replaces it. A source derived from a locked base via a query (`source: z is locked -> { … }`) instead **always carries the base's gate in addition to its own**: the derivation recurses into the base unconditionally, so an own gate does not replace it, and the two combine as separate AND'd entries. Pair a locked base with curated extension sources, using access modifiers (`include { public: …, private: * }`), so an extension re-exposes only a curated column surface, and keep sensitive sources out of ungated joins.
|
|
329
|
+
- **Entry point only: not joined, but inherited through `extend`.** The gate applies to the source a query enters through. A gate on a source reached only via `join_*` **never fires**, at any depth, so anything ungated that joins a locked base hands the base's rows to every caller. A source that `extend`s a locked base and declares no gate of its own **does** carry the base's gate; declaring its own annotation replaces it. A source derived from a locked base via a query (`source: z is locked -> { … }`) instead **always carries the base's gate in addition to its own**: the derivation recurses into the base unconditionally, so an own gate does not replace it, and the two combine as separate AND'd entries. Pair a locked base with curated extension sources, using access modifiers (`include { public: …, private: * }`), so an extension re-exposes only a curated column surface, and keep sensitive sources out of ungated joins. A join the *caller* writes in query text is the caller entering the joined source, so that source's gate does apply to it: a lock is decided, a row filter is grafted into the join. That holds for a query source the model declares over an inline `table(...) extend { … }` base as much as for a table source: the join anchors on the declared source, and the derivation carries the base's gate. The author-join side has a contract of its own: when a gated entry source author-joins a tenant-scoped source, carry the tenant key in the `on` (`on list_id = values.list_id and org_id = values.org_id`), because the joined source's gate does not fire through that join and the ON is what keeps the rows to the caller's tenant. Where a join cannot be keyed that way, the entry source's own gate may reference the joined column (`#(access_filter) values.org_id = $ORG_ID`): that is enforced, at the cost of turning that `join_one` into an inner join, so a row with no match drops rather than surviving with nulls.
|
|
322
330
|
- **A derivation that drops a column the gate reads fails CLOSED.** `extend { except: org_id }`, or an `accept:` that omits it, leaves the grafted filter unable to compile, so the request is denied rather than served ungated. The one hole to know: dropping the gated column and then `rename:`-ing a *different* column onto that exact name grafts successfully and binds the gate to the wrong column. Narrow, but real, so don't recycle a gated column's name. If a derivation's own projection needs to drop the column a row-level term would read, use a source-level term (`'literal' in/= $GIVEN`) instead, since it never depends on any projected column.
|
|
323
331
|
- **The quoted-string and file-level forms are refused at load and no longer exist.** A quoted expression on the `source:` line, in either quote, a file-level `##(…) "<expr>"` applying to every source in the file, and the earlier `internal dimension: authorized is <expr>` form are all retired; the load names the rewrite. Every `.malloy` file in a package compiles at load and any failure aborts the package, so a retired-form gate anywhere in the package is refused. Only a declaring file *outside* the package escapes that: it loads and denies every request instead, with no compile-time hint. See your deployment's reference documentation.
|
|
324
332
|
- **A gated source can be persisted, but the gating column freezes.** `storage=` and `#@ preaggregate` refuse a gated source outright; a colocated `#@ persist` is admitted when the gate is provably the entry point's own row filter. The gate still runs live on every query, so rows come back filtered - but the column it filters ON is frozen at build time, so a row whose access decision changes keeps being served under the old decision until the next rebuild. Pair `#@ persist` on a gated source with a freshness window (`fallback="live"`), which is the only control that bounds that - and read `skill:malloy-materialization` for where that window binds, because on a standalone Publisher it does not.
|
|
@@ -11,7 +11,7 @@ When you've been doing analysis, writing queries, building views, creating repor
|
|
|
11
11
|
|
|
12
12
|
## Step 1: IDENTIFY, what belongs in a model?
|
|
13
13
|
|
|
14
|
-
Scan your analysis `.malloy` files and reports (any `.malloy` with an `## artifact` tag, wherever it sits, or an existing `.malloynb`). A report (a `tiles=[…]` layout
|
|
14
|
+
Scan your analysis `.malloy` files and reports (any `.malloy` with an `## artifact` tag, wherever it sits, or an existing `.malloynb`). A report (a `tiles=[…]` layout, of either layout) is not a model file to restructure: lift its reusable definitions into a model and leave the report to import them. For each dimension, measure, join, or calculation, ask: **is this a reusable building block?**
|
|
15
15
|
|
|
16
16
|
A measure or dimension belongs in a model if it meets ANY of these criteria:
|
|
17
17
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-model-as-you-go
|
|
3
3
|
description: 'After answering a data question, write down what the answer assumed so the next reader can trust the number. A field with a #(doc) in the model when you can edit it, an extend in the notebook when you can only author reports, or a stated assumption plus a Malloy snippet when you can only chat. Use after every answered question that rested on a judgment call, and whenever a question is asked against tables that have no model yet.'
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-modeling
|
|
3
3
|
description: Build semantic models with Malloy for the Malloy Publisher. Read this skill whenever the user asks about modeling data or specifically mentions Malloy.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-notebooks
|
|
3
3
|
description: "Renamed to malloy-reports. A notebook is now a report with layout=linear: a .malloy file with an ## artifact tag, written at the package root. Load malloy-reports instead when the user asks for a notebook, a data story, or a report that reads top to bottom."
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-phrase-detection
|
|
3
3
|
description: How to phrase search_text on a get_context call so retrieval returns the fields you need instead of a truncated catalog. Covers target-type classification and decomposition patterns.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!-- Copyright (c) Credible Data Inc. SPDX-License-Identifier: MIT -->
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-powerbi-review
|
|
3
3
|
description: Move a Power BI semantic model (TMDL, PBIP, or .pbix) onto Malloy - read their model, transpile its DAX, and prove the numbers match. Use when Power BI artifacts are present, when a user wants to migrate or switch off Power BI, and whenever a specific DAX measure has to become Malloy - CALCULATE, ALL/ALLSELECTED/ALLEXCEPT, RANKX and top-N, time intelligence, USERELATIONSHIP, calculation groups, what-if parameters, or an RLS role. Carries a worked, executed transpile per construct and states what each port costs. Works with or without a database connection.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-publish
|
|
3
3
|
description: Package Malloy models for serving by Malloy Publisher. Use when user asks to "publish", "package", "deploy", or wants to share models with others.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -138,7 +138,7 @@ A flat layout at the package root is the simplest default, for models and report
|
|
|
138
138
|
|
|
139
139
|
Publishable contents:
|
|
140
140
|
- `.malloy` files - Semantic model definitions (base sources + joined sources)
|
|
141
|
-
- Reports: `.malloy` files with an `## artifact { layout=linear … }` or `## artifact { layout=grid … }` tag, anywhere in the package (outside `public/`), written as tiles
|
|
141
|
+
- Reports: `.malloy` files with an `## artifact { layout=linear … }` or `## artifact { layout=grid … }` tag, anywhere in the package (outside `public/`), written as tiles (see `skill:malloy-reports`). A linear tag with no tiles, the retired `run:`-cell form, is no report. The tag alone makes a file a report and sets its layout; the folder means nothing. An existing `.malloynb` is still served as a linear report; never write a new one.
|
|
142
142
|
- Data files (CSV/Parquet/XLSX) - Embedded data published with package
|
|
143
143
|
|
|
144
144
|
## Version Management
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-queries
|
|
3
3
|
description: Read before writing or debugging a Malloy query. Dates, aggregates vs dimensions, joins, filters, strings, window functions, chart annotations, and the compile errors a SQL habit produces.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -96,7 +96,7 @@ run: source -> {
|
|
|
96
96
|
|
|
97
97
|
## Field Paths and Joins
|
|
98
98
|
|
|
99
|
-
**Use the joins the model declares.** When you answer a question over a published model, every query is rooted on one source, and you reach a joined source's fields by a dotted path within the query body (for example `stores.region`). Do not write `join_one` or `join_many` in an answer query, and never invent a join key. (A join written in a query does compile, and model files and notebooks use them; the rule is that an answer uses the joins the model declares.) If the field you need is on a source the model does not join, say so and ask the user to add the join to the model.
|
|
99
|
+
**Use the joins the model declares.** When you answer a question over a published model, every query is rooted on one source, and you reach a joined source's fields by a dotted path within the query body (for example `stores.region`). Do not write `join_one` or `join_many` in an answer query, and never invent a join key. (A join written in a query does compile, and model files and notebooks use them; the rule is that an answer uses the joins the model declares.) If the field you need is on a source the model does not join, say so and ask the user to add the join to the model. If you do write a join in query text, it is gated as if you had entered that source yourself: a locked source refuses the query with a 403 that names your alias, and a row-filtered source filters the rows your join sees, so a denial on a join you wrote means the joined source refused you, not that the model is broken.
|
|
100
100
|
|
|
101
101
|
The `->` operator separates a **source** from a **view** (query transformation). It does NOT navigate between joined sources.
|
|
102
102
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-reports
|
|
3
3
|
description: "Build or modify a Malloy Publisher report: a .malloy file with an ## artifact tag, written at the package root unless asked otherwise, laid out as a grid (a dashboard, scanned at a glance) or linear (a notebook or data story, read top to bottom), with filter controls rendered from givens and # drill click-through. Use when the user asks for a dashboard, a notebook, a report, a data story, a filterable view, or drill-through between views, and no code is wanted. Legacy .malloynb files are read, not authored."
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!--
|
|
@@ -16,7 +16,7 @@ SPDX-License-Identifier: MIT
|
|
|
16
16
|
What other tools call a dashboard and a notebook are both reports here. The **layout** is the only difference:
|
|
17
17
|
|
|
18
18
|
- **`layout=grid`**: tiles across a grid behind one filter row, scanned at a glance (a dashboard). Read `reference/grid.md` before you lay one out.
|
|
19
|
-
- **`layout=linear`**: tiles and prose top to bottom, read in order (a notebook, a data story). Read `reference/linear.md` before you write one
|
|
19
|
+
- **`layout=linear`**: tiles and prose top to bottom, read in order (a notebook, a data story). Read `reference/linear.md` before you write one.
|
|
20
20
|
|
|
21
21
|
Everything else (filters, text tiles, drill, the builder, the lint, the write path) is the same for both, and switching layout later is a one-property edit.
|
|
22
22
|
|
|
@@ -46,7 +46,7 @@ Tool names below are bare (`get_context`); match each against the tools you actu
|
|
|
46
46
|
|
|
47
47
|
1. **READ THE MODEL FIRST.** Get the real source, view, dimension, and given names from the package: `get_context` if you have it, otherwise the REST model endpoint for `index.malloy` (it answers 404 for a file off the surface) or the `.malloy` files. Never guess a name. A guessed field in a query fails the whole package load, not just that one report; a guessed tile or suggest source is quieter, and only shows up in the package warnings. If the package root holds an `index.malloy` (every scaffolded package does), a tile reads only the sources that file exports.
|
|
48
48
|
2. **PICK THE LAYOUT AND THE TILES.** Which views, in what order, and on a grid how wide each sits. A query tile is a `view:` you declare in the report, named as `"source -> view"`.
|
|
49
|
-
3. **DECLARE THE GIVENS** the report will filter by, in the report file itself, with their control tags (see "Filter controls").
|
|
49
|
+
3. **DECLARE THE GIVENS** the report will filter by, in the report file itself, with their control tags (see "Filter controls"). A report holds its own filters: the builder edits the report file and nothing else, so a filter it can add, change or remove is a declaration in that file. A given the model already declares and reads is imported by name, never declared again; if it is only a reader's filter, move it into the report instead (see "Givens that stay in the model").
|
|
50
50
|
4. **COMPOSE THE FILE** at its path (the package root unless asked otherwise), following the template below, but do not save it yet. Name the sources you need in the import. Each tile binds its controls with a refinement, `view: t is v + { where: category ~ $CATEGORY }`, one clause per given it answers to; a `filter<...>` binds with `~`, a plain `date` or `number` given is a value and binds with `>=`, `<=` or `=`. Only the givens some tile references become controls. Then import every source or query any referenced given names in a `suggest`; **a `suggest` naming a `query=` needs that query's own source imported as well**, because an import is not transitive.
|
|
51
51
|
5. **COMPILE IT** with your compile tool (`POST …/models/<path>/compile` over REST) at the path the file will have, with **`"scope": "file"`**: a report opens with an `import`, which the default `append` scope refuses, and an existing file compiled at `append` collides with itself. **`"scope": "file"` does not check tiles, drills or suggest queries.** To see those before you save, compile the same text at `"scope": "package"` with `modelPath` set to the report's path: it runs the lint step 6 reads and returns its findings with code `dashboard-lint` (or `render-tag`). Editing an untagged model several reports import also wants `"scope": "package"`, which recompiles every file as saved.
|
|
52
52
|
6. **SAVE IT, RELOAD, AND READ THE MANIFEST AND THE WARNINGS.** Save by writing the file, or with `PUT …/models/<path>` over REST (omit `expectedHash` to create; it creates at any new path and replaces only a file the package already serves as a report). Then your reload tool, or `GET …/packages/<pkg>?reload=true`. A 424 means the package did not load and your edit is not live. **The `warnings` key is absent when there are none.** Then read `GET …/packages/<pkg>/reports/<path>`: its `givens` are exactly the controls that will render, and its `layout` is the layout Publisher read. See "Read the lint".
|
|
@@ -97,7 +97,7 @@ source: overview is order_items extend {
|
|
|
97
97
|
The same file with `layout=linear` and no `dashboard { … }` or `colspan` is a linear report.
|
|
98
98
|
|
|
99
99
|
- **The tag is model-level**, one line (`## artifact { … }`) or a `##|` … `|##` block with the same text inside, the form to use once the list is long: one tile per line, `}` on its own line, `|##` closing at the opener's column. A one-line `##` tag must stay on one line: wrapping it is a compile error, or worse, compiles clean and quietly stops being a report.
|
|
100
|
-
- **The header** above the tag holds only `##!` flags, `//` comments and the unnamed `"` notes that are the report's description.
|
|
100
|
+
- **The header** above the tag holds only `##!` flags, `//` comments and the unnamed `"` notes that are the report's description. Keep statements and `#` tags below `## artifact`: nothing refuses one above it, but the header is where readers and the builder look.
|
|
101
101
|
- **Quote every query entry.** `"overview -> kpis"` is a string; unquoted, the tag does not parse (`notebook-artifact-unparsed`). A bare quoted name (`"top_products"`) names a model-level `query:` in the file.
|
|
102
102
|
- **A query tile is a thin `view:` on an extension the report declares** (`source: overview is order_items extend { … }`): the modelled view keeps its chart tag, the wrapper carries the tile's `# label`, its width on a grid, and its binding.
|
|
103
103
|
- **A text tile is a `##|(markdown) name` block** listed as `name { kind=text }`. The name is one bare word on the opener line, the body starts on the next line (a heading goes inside the block), and `|##` closes it at the opener's column. A bare `intro` without `{ kind=text }` is the query-tile form for a `query:`, so the block is not shown. On a grid the entry also takes `colspan` and `break`, and nothing else.
|
|
@@ -142,6 +142,71 @@ A given stays in the model, declared once there, when a model source, view or me
|
|
|
142
142
|
- **Import a name and also declare it: a compile error.** Drop the local declaration.
|
|
143
143
|
- **Declare a name the model already reads, without importing it: no error at all.** The control renders and moves, and the model's own `where:` or gate never sees the value.
|
|
144
144
|
|
|
145
|
+
**A model that already scopes a source by an optional filter given** (`where: Manufacturer ~ $manufacturer_filter` in the model) needs a decision:
|
|
146
|
+
|
|
147
|
+
- **It is a reader's filter and you may change the model: move it.** Delete the given and its `where:` clause from the model, declare it in the report with its control tags, and bind it on each tile that should answer to it. A reader's filter is anything a person picks on the page; a given stays in the model only for the reasons `malloy-model` lists (a gate, a value every query must supply, a parameter a field is defined with, scoping meant for every consumer).
|
|
148
|
+
- **The model can't change, or the given is semantic or governance: import it** as above. Say so plainly: the filter shows in the reader and in the builder, and can be set in both, but the builder cannot remove or reconfigure it. It is the model's.
|
|
149
|
+
|
|
150
|
+
Before, in the model:
|
|
151
|
+
|
|
152
|
+
```malloy
|
|
153
|
+
##! experimental.givens
|
|
154
|
+
given:
|
|
155
|
+
manufacturer_filter :: filter<string> is f''
|
|
156
|
+
subject_filter :: filter<string> is f''
|
|
157
|
+
major_recall_filter :: filter<boolean> is f''
|
|
158
|
+
recall_date_filter :: filter<date> is f''
|
|
159
|
+
|
|
160
|
+
source: recalls is duckdb.table('data/auto_recalls.csv') extend {
|
|
161
|
+
where:
|
|
162
|
+
Manufacturer ~ $manufacturer_filter,
|
|
163
|
+
Subject ~ $subject_filter,
|
|
164
|
+
`Major Recall` ~ $major_recall_filter,
|
|
165
|
+
`Report Received Date` ~ $recall_date_filter
|
|
166
|
+
dimension: `Major Recall` is ifnull(`Potentially Affected`, 0) > 100000
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
After: the model declares no givens and `recalls` has no `where:`; the report declares the four and binds each tile:
|
|
171
|
+
|
|
172
|
+
```malloy
|
|
173
|
+
##! experimental.givens
|
|
174
|
+
##| artifact { layout=grid title="Recalls"
|
|
175
|
+
tiles=["recalls_tiles -> recalls_by_year", "recalls_tiles -> recalls_by_manufacturer"]
|
|
176
|
+
} dashboard { columns=12 }
|
|
177
|
+
|##
|
|
178
|
+
import { recalls } from "auto_recalls.malloy"
|
|
179
|
+
|
|
180
|
+
# label="Manufacturer" control=multiselect suggest { source=recalls dimension=Manufacturer }
|
|
181
|
+
given: manufacturer_filter :: filter<string> is f''
|
|
182
|
+
# label="Subject"
|
|
183
|
+
given: subject_filter :: filter<string> is f''
|
|
184
|
+
# label="Major recalls only"
|
|
185
|
+
given: major_recall_filter :: filter<boolean> is f''
|
|
186
|
+
# label="Filed"
|
|
187
|
+
given: recall_date_filter :: filter<date> is f''
|
|
188
|
+
|
|
189
|
+
source: recalls_tiles is recalls extend {
|
|
190
|
+
# colspan=6
|
|
191
|
+
view: recalls_by_year is by_year + {
|
|
192
|
+
where:
|
|
193
|
+
Manufacturer ~ $manufacturer_filter,
|
|
194
|
+
Subject ~ $subject_filter,
|
|
195
|
+
`Major Recall` ~ $major_recall_filter,
|
|
196
|
+
`Report Received Date` ~ $recall_date_filter
|
|
197
|
+
}
|
|
198
|
+
# colspan=6
|
|
199
|
+
view: recalls_by_manufacturer is by_manufacturer + {
|
|
200
|
+
where:
|
|
201
|
+
Subject ~ $subject_filter,
|
|
202
|
+
`Major Recall` ~ $major_recall_filter,
|
|
203
|
+
`Report Received Date` ~ $recall_date_filter
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
A tile view takes a name of its own: `view: by_year is by_year + { … }` is "Cannot redefine 'by_year'". Each tile binds only the givens it should answer to: `recalls_by_manufacturer` leaves the manufacturer filter off, since it is the breakdown by manufacturer. Every given here is a `filter<…>`, so each binds with `~`; a plain `date` or `number` given binds with `>=`, `<=` or `=` (step 4).
|
|
209
|
+
|
|
145
210
|
A given has to be in the report file's own scope to be bindable: declared there or imported. Never add a `#(filter)` annotation: it is deprecated, a report renders no control for it, and every use has a `given:` form (`malloy-model` covers the migration). `malloy-model` and `docs/givens.md` cover givens themselves.
|
|
146
211
|
|
|
147
212
|
## Drill
|
|
@@ -166,24 +231,24 @@ dimension: region is regions.region
|
|
|
166
231
|
|
|
167
232
|
Package warnings after a reload are the report's test suite. Fix all of them, and read the text rather than the severity or the count. **"Dashboard lint stopped early, so this list is incomplete"** means the rest of the list is partial.
|
|
168
233
|
|
|
169
|
-
| Finding
|
|
170
|
-
|
|
|
171
|
-
| `# drill … targets "x", which is not a report in this package`
|
|
172
|
-
| `to=self, but no model in this package declares a given "X"`
|
|
173
|
-
| `given "X" suggests options from source "y", which this file does not define`
|
|
174
|
-
| `filters by given "X", which this file does not import`
|
|
175
|
-
| A tile that does not resolve to a real view
|
|
176
|
-
| `notebook-artifact-unparsed`
|
|
177
|
-
| `notebook-layout-run`
|
|
178
|
-
| `notebook-tile-layout-ignored`
|
|
179
|
-
| `report-kind-removed`
|
|
180
|
-
| `report-
|
|
181
|
-
| `report-on-surface`
|
|
182
|
-
| `index-is-not-a-report`
|
|
183
|
-
| `report-name-ambiguous`
|
|
184
|
-
| `Tile … reads <source>, which index.malloy doesn't export`
|
|
185
|
-
| A grid's `columns`, `colspan`, or `dashboard_columns=` findings
|
|
186
|
-
|
|
|
234
|
+
| Finding | Fix |
|
|
235
|
+
| ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
236
|
+
| `# drill … targets "x", which is not a report in this package` | A dead click. Write the target's path |
|
|
237
|
+
| `to=self, but no model in this package declares a given "X"` | The clicked value has nowhere to go. Declare the given, or fix `given=` |
|
|
238
|
+
| `given "X" suggests options from source "y", which this file does not define` | Import `y` into the report |
|
|
239
|
+
| `filters by given "X", which this file does not import` | No control is shown for it. Import the given |
|
|
240
|
+
| A tile that does not resolve to a real view | Fix the view name, or declare the view |
|
|
241
|
+
| `notebook-artifact-unparsed` | The tag does not parse, usually an unquoted `source -> view` entry |
|
|
242
|
+
| `notebook-layout-run` | A `run:` beside `tiles`. Make it a view, list it, delete the `run:` |
|
|
243
|
+
| `notebook-tile-layout-ignored` | `colspan` or `break` on a linear report. Remove them, or switch to `layout=grid` |
|
|
244
|
+
| `report-kind-removed` | The tag sets `kind=`, which is no longer read, so the file is no report. Write the `layout=` the finding gives |
|
|
245
|
+
| `report-cells-removed` | A linear tag with no `tiles`, the retired cell format, so the file is no report. List its tiles (`reference/linear.md`) |
|
|
246
|
+
| `report-on-surface` | A model `index.malloy` imports (or `explores` lists) is tagged, so its sources left the surface. Untag it, or move the report out |
|
|
247
|
+
| `index-is-not-a-report` | The root `index.malloy` carries a tag. Remove it; put the report in its own file |
|
|
248
|
+
| `report-name-ambiguous` | Two grid reports share a file name, so the deprecated by-name dashboards route answers 409 for it. Rename one of the files |
|
|
249
|
+
| `Tile … reads <source>, which index.malloy doesn't export` | Add the source to the `export { ... }` in `index.malloy`. Never add an `explores` to `publisher.json` |
|
|
250
|
+
| A grid's `columns`, `colspan`, or `dashboard_columns=` findings | `reference/grid.md` |
|
|
251
|
+
| Prose the reader cannot place (`notebook-markdown-attached-nowhere`, `notebook-markdown-block-unnamed`) | `reference/linear.md` |
|
|
187
252
|
|
|
188
253
|
**Read the status the reload itself returns, not the listing.** One report that fails to compile fails the whole package load (424), and a package already serving keeps its previous version, so the listing still looks healthy. `GET /api/v0/status` still knows: a package serving an older model than its files appears in `loadErrors` with `stale: true`. If the reload is 200 and your report is not listed, discovery skipped it: usually a missing or misspelled artifact tag, or a file under `public/`.
|
|
189
254
|
|
|
@@ -218,7 +283,7 @@ For a grid, write `layout=grid`, add `dashboard { columns=12 }` after the tag's
|
|
|
218
283
|
|
|
219
284
|
## Editing in the Console
|
|
220
285
|
|
|
221
|
-
A person can open any report in the Console's builder (**Edit** on the report page;
|
|
286
|
+
A person can open any report in the Console's builder (**Edit** on the report page; **+ Report** on the package page writes `<slug>.malloy` at the package root). The builder splices the file rather than regenerating it, so comments and anything it does not model survive; its layout switch rewrites `layout=` and nothing else. A file written as `run:` cells, the retired linear form, is no report and does not open (`reference/linear.md`). A save whose text declares a real `#(authorize)` or `#(access_filter)` gate outside prose is refused with a 400: gates live in a model file the report imports.
|
|
222
287
|
|
|
223
288
|
## What "done" means
|
|
224
289
|
|
|
@@ -233,6 +298,6 @@ A person can open any report in the Console's builder (**Edit** on the report pa
|
|
|
233
298
|
## Reference
|
|
234
299
|
|
|
235
300
|
- `reference/grid.md`: the grid recipe, its traps, and one query as the whole page.
|
|
236
|
-
- `reference/linear.md`: linear reports, `run
|
|
301
|
+
- `reference/linear.md`: linear reports, rewriting a retired `run:`-cell file, legacy `.malloynb`, style, and common mistakes.
|
|
237
302
|
- `docs/reports.md`: the full guide this skill condenses.
|
|
238
303
|
- `docs/givens.md`: the givens the controls are generated from.
|
|
@@ -45,94 +45,21 @@ source: category_tiles is order_items extend {
|
|
|
45
45
|
|
|
46
46
|
- **`layout=linear` is required** when the tag lists tiles; without it the file is read as a grid.
|
|
47
47
|
- `dashboard { columns }` has no effect, and a `colspan` or `break` on a tile entry is ignored with a warning (`notebook-tile-layout-ignored`).
|
|
48
|
-
- **Tiles run through the model query endpoint**,
|
|
48
|
+
- **Tiles run through the model query endpoint**, against the report's own path, like a grid's.
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Files written as `run:` cells (retired)
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
An older linear form was a file of `run:` statements with `(markdown)` notes between them, under a tag with no `tiles`. Publisher no longer reads it: such a file is not a report, and the package warnings name it (`report-cells-removed`). Never write one. To bring one back, rewrite it as tiles:
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
1. **The header is `##!` flags, `//` comments and unnamed `"` notes (the description), then `## artifact { layout=linear … }`.** Nothing else goes above the tag. A statement or a tag above `## artifact` is an error, and the report is served with that error instead of opening.
|
|
59
|
-
2. **Prose is a `(markdown)` annotation, and the number of `#` says what it belongs to.** Floating markdown is a cell of its own: `##(markdown) text` (contiguous lines with no blank line between them merge into one cell) or a `##|(markdown)` block, with the body starting on the next line and the `|##` closer at the opener's column. Attached markdown, `#(markdown) text` or a `#|(markdown)` … `|#` block, belongs to the statement below it and renders with that cell. Keep the parentheses: `##| markdown` reads its prose as ordinary tags, and `##|markdown` draws a malformed-route warning. No body line starts with `|##`, and a blank line follows the closer. A `## Heading` line is model tags, not prose: write a heading inside a `##|(markdown)` block.
|
|
60
|
-
3. **Render tags sit directly above `run:`, with nothing between.** A `#"` directly above the `run:` is its caption; `# bar_chart`, `# label="..."` and the other render tags go in the same block. Attached `#(markdown)` goes above the `#"` caption when both are present, and renders as a header above the result.
|
|
61
|
-
4. **`given:` uses `NAME :: filter<T> is f''`, bound with `~`, declared before first use.** Put `##! experimental.givens` at the top of the file.
|
|
62
|
-
5. **Trailing prose is `##(markdown)`, never `#(markdown)` or `#"`.** Both belong to the statement below them, and at the end of a file there is none. An `import` or an `export` takes no annotation either, so prose above one is `##|(markdown)`.
|
|
63
|
-
|
|
64
|
-
### How the file becomes cells
|
|
65
|
-
|
|
66
|
-
Cells come from the file's own notes and statements, in file order (imported files add none):
|
|
67
|
-
|
|
68
|
-
| In the file | Cell |
|
|
69
|
-
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
70
|
-
| `##!` lines, `## artifact`, and every `"` note above the tag | the header: no cell |
|
|
71
|
-
| `##(markdown)` lines (contiguous) or a `## | (markdown)` block, after the tag | one markdown cell |
|
|
72
|
-
| `#(markdown)` or a `# | (markdown)`block above a`run:`, `source:`, `query:`, `given:`or`type:` | no cell of its own: it renders with that statement's cell |
|
|
73
|
-
| `run:` with its contiguous `#` tag block, including `run: <named query>` | one query cell |
|
|
74
|
-
| `import`, `source:`, `query:`, `given:`, `export { … }`, `type:` | one definition cell **per statement** |
|
|
75
|
-
| a non-`"` `##` note after the tag (`## title=…`) | not a cell |
|
|
76
|
-
|
|
77
|
-
Definition cells render folded; only query cells run. Earlier spellings are still read: `##"`, `##|"`, `##(text)` and `##|(text)` notes below the tag are markdown cells, like `(markdown)`. Write `(markdown)`. A query cell runs as a _restricted query_ against the already-compiled file, so an `import` inside a `run:` is rejected (`file imports are not permitted in a restricted query`).
|
|
78
|
-
|
|
79
|
-
### A complete cell report
|
|
80
|
-
|
|
81
|
-
```malloy
|
|
82
|
-
##! experimental.givens
|
|
83
|
-
##" One category at a time: how it sells across the year and which brands carry it.
|
|
84
|
-
## artifact { layout=linear title="Category review" }
|
|
85
|
-
import { order_items, products } from "storefront.malloy"
|
|
86
|
-
|
|
87
|
-
##|(markdown)
|
|
88
|
-
## Category review
|
|
89
|
-
|
|
90
|
-
Pick a **Category** in the controls above and every chart below re-runs for it.
|
|
91
|
-
Leave the control empty to read the whole catalog.
|
|
92
|
-
|##
|
|
93
|
-
|
|
94
|
-
#(description="Narrow to one product category")
|
|
95
|
-
# label="Category" control=select suggest { source=products dimension=category }
|
|
96
|
-
given: CATEGORY :: filter<string> is f''
|
|
97
|
-
|
|
98
|
-
##(markdown) How is revenue trending for the selected category?
|
|
99
|
-
|
|
100
|
-
#(markdown) ### Revenue by month
|
|
101
|
-
#" Revenue by month for the selected category
|
|
102
|
-
# line_chart
|
|
103
|
-
# label="Revenue by month"
|
|
104
|
-
run: order_items -> sales_by_month + { where: category ~ $CATEGORY }
|
|
105
|
-
|
|
106
|
-
##(markdown) Which brands are behind those numbers?
|
|
107
|
-
|
|
108
|
-
#" The eight brands with the most revenue in the selected category
|
|
109
|
-
# bar_chart
|
|
110
|
-
# label="Top brands"
|
|
111
|
-
run: order_items -> top_brands + { where: category ~ $CATEGORY }
|
|
112
|
-
|
|
113
|
-
##(markdown) Last, the best-selling products, defined once as a named query and then run.
|
|
114
|
-
|
|
115
|
-
query: top_products_in_category is order_items -> top_products + { where: category ~ $CATEGORY }
|
|
116
|
-
|
|
117
|
-
#(markdown) ### Best sellers
|
|
118
|
-
run: top_products_in_category
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
The `##|(markdown)` block and each `##(markdown)` line are floating markdown cells. The two `#(markdown)` lines are attached: each renders as a header with the `run:` below it. The `given:` and the `query:` are definition cells, and each `run:` is a query cell.
|
|
122
|
-
|
|
123
|
-
### When the Console converts it
|
|
124
|
-
|
|
125
|
-
Saving a cell report in the builder turns each `run:` into a `view:` on a `<source>_tiles` extension appended to the file (a `query:` used by exactly one run is folded into its view), each prose note into a `##|(markdown) text_N` block, and leaves definitions, imports, givens and comments where they were. The first Save asks before it rewrites the file ("Convert this notebook?"); Cancel writes nothing, and the file's history in its repository is the way back. Write a cell report so it converts:
|
|
126
|
-
|
|
127
|
-
- Every `run:` is `<source> -> <view or query body>` with a named source. An inline `extend` before the arrow, a source that is not a name, or a refinement (`q + { … }`) of a multi-stage query is refused, and so is a run that resolves through more than ten named queries.
|
|
128
|
-
- A chart line the editor does not model (for example `# bar_chart { size=spark }`) is kept, and the Viz type picker is disabled for that tile with the reason shown.
|
|
129
|
-
- The editor opens a report **read-only**, saying why, when it cannot place cells one by one: two statements or notes on one line, text after a block closer, a comment straddling two cells, a lone carriage return (use LF or CRLF), a statement above the `## artifact` tag, a statement no cell can hold, or a tag value Malloy cannot read, such as a malformed date literal (`@2024-13-01`).
|
|
54
|
+
1. Each floating `##(markdown)` note or `##|(markdown)` block becomes a named `##|(markdown) name` block, listed as `name { kind=text }`.
|
|
55
|
+
2. Each `run: <source> -> <query>` becomes a `view:` on a `<source>_tiles` extension the report declares, listed as `"<source>_tiles -> <view>"`. The render tags above the `run:` move to the view; a `#"` caption becomes `# label="…"`. A `run:` of a named `query:` is listed by the query's name.
|
|
56
|
+
3. List the entries in `tiles=[…]` in reading order, keep `layout=linear`, and delete every `run:`.
|
|
130
57
|
|
|
131
58
|
## Multiple models and cross-model joins
|
|
132
59
|
|
|
133
|
-
A report compiles with **all** its `import`s in scope, so it can import several `.malloy` models, and a tile's source
|
|
60
|
+
A report compiles with **all** its `import`s in scope, so it can import several `.malloy` models, and a tile's source can reference, and **join**, sources from different imported files. Import each model you need at the top; no re-export wrapper is required.
|
|
134
61
|
|
|
135
|
-
**When the package root holds an `index.malloy`, tiles
|
|
62
|
+
**When the package root holds an `index.malloy`, tiles read only what it exports.** A tile over a source `index.malloy` does not export answers 404, even when the report imports that source's file. A source the report derives from an exported one (`source: mine is customers extend { ... }`) still works. A tile's own source over a raw table (`duckdb.table(...)`) is refused. To use a source, add it to the `export { ... }` in `index.malloy`.
|
|
136
63
|
|
|
137
64
|
## Style
|
|
138
65
|
|
|
@@ -141,14 +68,20 @@ A report compiles with **all** its `import`s in scope, so it can import several
|
|
|
141
68
|
**No data-specific insights in markdown.** Data refreshes will make them stale.
|
|
142
69
|
|
|
143
70
|
```malloy
|
|
144
|
-
|
|
71
|
+
##|(markdown) spike_note
|
|
72
|
+
WRONG: will become stale when data refreshes. Revenue spiked 23% in March.
|
|
73
|
+
|##
|
|
145
74
|
|
|
146
|
-
|
|
75
|
+
##|(markdown) trend_q
|
|
76
|
+
RIGHT: frames the question and lets the query answer it. How is revenue trending?
|
|
77
|
+
|##
|
|
147
78
|
```
|
|
148
79
|
|
|
80
|
+
Each is a text tile, listed in `tiles=[…]` by name, for example `trend_q { kind=text }`.
|
|
81
|
+
|
|
149
82
|
To add `limit`, `where`, or other options to an existing view, refine it with `+`: `my_view + { limit: 15 }`.
|
|
150
83
|
|
|
151
|
-
One query per tile
|
|
84
|
+
One query per tile, and remember a chart renders only the **first** aggregate in its result.
|
|
152
85
|
|
|
153
86
|
## Existing `.malloynb` files
|
|
154
87
|
|
|
@@ -156,25 +89,18 @@ A `.malloynb` (cells delimited by `>>>markdown` and `>>>malloy`) is a deprecated
|
|
|
156
89
|
|
|
157
90
|
## Common mistakes
|
|
158
91
|
|
|
159
|
-
| Mistake | Fix
|
|
160
|
-
| -------------------------------------------------------------------------- |
|
|
161
|
-
| A tile list without `layout=linear` | Read as a grid. Write `layout=linear`.
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
| `##
|
|
165
|
-
| `##
|
|
166
|
-
|
|
|
167
|
-
| Text on the opener line after
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
| `
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
| `tiles=[intro { kind=text }, orders_tiles -> headline]` (entry not quoted) | Every `source -> view` entry is a quoted string. Unquoted, the tag does not parse. |
|
|
175
|
-
| A bare `intro` in `tiles` for a `## | (markdown) intro` block | A bare name is the query-tile form for a `query:`. Write `intro { kind=text }`. |
|
|
176
|
-
| `colspan` or `break` on a linear report's tile entry | Ignored and reported (`notebook-tile-layout-ignored`). Remove them, or switch to `layout=grid`. |
|
|
177
|
-
| `#(filter)` in any form | Deprecated. Declare a `given:` on the model's source instead. |
|
|
178
|
-
| `import` inside a `run:` cell | Imports are file-wide; put them at the top. |
|
|
179
|
-
| Data-specific insights in markdown | Frame questions instead. |
|
|
180
|
-
| A cross-model join in a quick `skill:malloy-analysis-report` cell | That cell renders against a single model. Write it as a report tile over a source the report declares with the join, since the report compiles with all its imports. |
|
|
92
|
+
| Mistake | Fix |
|
|
93
|
+
| -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
94
|
+
| A tile list without `layout=linear` | Read as a grid. Write `layout=linear`. |
|
|
95
|
+
| A linear tag with no `tiles=[…]` | The retired cell format: no report (`report-cells-removed`). List the tiles. |
|
|
96
|
+
| `view_name { limit: 10 }` | Use `+`: `view_name + { limit: 10 }` |
|
|
97
|
+
| Floating `##(markdown)` prose in a tiled report | Never shown (`notebook-markdown-block-unnamed`). Make it a named text tile and list it. |
|
|
98
|
+
| `## Heading` for a section title | That line is model tags, not prose. Write the heading inside a `##\|(markdown) name` text tile. |
|
|
99
|
+
| `##\| markdown` or `##\|markdown` (no parentheses) | Write `##\|(markdown) name`. |
|
|
100
|
+
| Text on the opener line after `##\|(markdown)` | Only the tile's name may follow it. Put the text on the next line. |
|
|
101
|
+
| `run:` in a report that lists `tiles=[…]` | Never shown. Define a `view:`, list `"source -> view"` in `tiles`, delete the `run:`. |
|
|
102
|
+
| `tiles=[intro { kind=text }, orders_tiles -> headline]` (entry not quoted) | Every `source -> view` entry is a quoted string. Unquoted, the tag does not parse. |
|
|
103
|
+
| A bare `intro` in `tiles` for a `##\|(markdown) intro` block | A bare name is the query-tile form for a `query:`. Write `intro { kind=text }`. |
|
|
104
|
+
| `colspan` or `break` on a linear report's tile entry | Ignored and reported (`notebook-tile-layout-ignored`). Remove them, or switch to `layout=grid`. |
|
|
105
|
+
| `#(filter)` in any form | Deprecated. Declare a `given:` on the model's source instead. |
|
|
106
|
+
| Data-specific insights in a text tile | Frame questions instead. |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: malloy-review
|
|
3
3
|
description: Malloy semantic-model code review. Invoke when the user asks to review, audit, or critique a `.malloy` file, a folder of Malloy models, or a GitHub PR that touches Malloy. Enforces project modeling standards and emits a navigable review file.
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.41
|
|
5
5
|
---
|
|
6
6
|
<!--
|
|
7
7
|
Copyright (c) Credible Data Inc.
|
|
@@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT
|
|
|
8
8
|
**Dimension:** `structure`
|
|
9
9
|
**Rules:** 5
|
|
10
10
|
|
|
11
|
-
Applies to the organization of `.malloy` files within a package. Skip a file for structural review if it's a report (a `.malloy` file anywhere in the package whose model-level notes carry an `## artifact` tag, of either layout, `layout=grid` or `layout=linear` or the deprecated `kind=`, written as `tiles=[…]
|
|
11
|
+
Applies to the organization of `.malloy` files within a package. Skip a file for structural review if it's a report (a `.malloy` file anywhere in the package whose model-level notes carry an `## artifact` tag, of either layout, `layout=grid` or `layout=linear` or the deprecated `kind=`, written as `tiles=[…]`; a legacy query-level `# artifact` on a `query:`; or a `.malloynb`), which has its own conventions (the `malloy-reports` skill), or a one-off sandbox script. Do not flag a report for where it sits: no directory is required for one, and the package root is the default.
|
|
12
12
|
|
|
13
13
|
For every rule, the linked instruction-skill section is the canonical source for rationale and examples.
|
|
14
14
|
|