|
| 1 | +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. |
| 2 | + |
| 3 | +import { defineForm } from './view.zod'; |
| 4 | + |
| 5 | +/** |
| 6 | + * Form layout for the Dataset metadata editor (ADR-0021 analytics semantic layer). |
| 7 | + * |
| 8 | + * Bound to {@link DatasetSchema}. Until this entry existed, `dataset` was the |
| 9 | + * only UI-authorable metadata type WITHOUT a registered {@link FormView}, so the |
| 10 | + * metadata-admin create surface fell back to the auto-generated single-section |
| 11 | + * layout. That fallback (a) silently DROPPED the optional `include` (joins) and |
| 12 | + * `filter` (intrinsic scope) fields — making joined / scoped datasets |
| 13 | + * un-authorable online — and (b) rendered the base `object` and dimension/ |
| 14 | + * measure `field`s as bare free-text inputs with no object context. |
| 15 | + * |
| 16 | + * This layout restores `include` + `filter`, groups the surface into sections |
| 17 | + * with guidance, and uses pickers (`ref:object`, `filter-builder` scoped to the |
| 18 | + * base object) so a business user can author a dataset without memorising |
| 19 | + * machine names. Mirrors {@link reportForm} — the sibling analytics editor. |
| 20 | + */ |
| 21 | +export const datasetForm = defineForm({ |
| 22 | + schemaId: 'dataset', |
| 23 | + type: 'simple', |
| 24 | + sections: [ |
| 25 | + { |
| 26 | + name: 'basics', |
| 27 | + label: 'Basics', |
| 28 | + description: 'Dataset identity.', |
| 29 | + columns: 2, |
| 30 | + fields: [ |
| 31 | + { field: 'name', type: 'text', colSpan: 1, required: true, immutable: true, helpText: 'snake_case unique identifier' }, |
| 32 | + { field: 'label', type: 'text', colSpan: 1, required: true, helpText: 'Display name' }, |
| 33 | + { field: 'description', type: 'textarea', colSpan: 2, helpText: 'What this dataset measures' }, |
| 34 | + ], |
| 35 | + }, |
| 36 | + { |
| 37 | + name: 'source', |
| 38 | + label: 'Source', |
| 39 | + description: 'The base object, the relationships to join, and the dataset’s intrinsic scope. Joins are derived from the object graph — pick relationship (lookup / master_detail) names, never write an ON clause.', |
| 40 | + fields: [ |
| 41 | + { field: 'object', widget: 'ref:object', required: true, helpText: 'Base object — the FROM' }, |
| 42 | + { field: 'include', widget: 'string-tags', helpText: 'Relationship (lookup / master_detail) field names to join — enables `relationship.field` dimensions/measures (e.g. include "account" → group by account.region)' }, |
| 43 | + { field: 'filter', widget: 'filter-builder', dependsOn: 'object', helpText: 'Intrinsic scope filter (e.g. exclude soft-deleted records), ANDed into every query' }, |
| 44 | + ], |
| 45 | + }, |
| 46 | + { |
| 47 | + name: 'dimensions', |
| 48 | + label: 'Dimensions', |
| 49 | + description: 'Groupable axes. Use a base field, or `relationship.field` (e.g. account.region) for a relationship included above.', |
| 50 | + fields: [ |
| 51 | + { field: 'dimensions', type: 'repeater', required: true, helpText: 'Each: name (referenced by presentations), field, type, and — for dates — a default bucketing granularity' }, |
| 52 | + ], |
| 53 | + }, |
| 54 | + { |
| 55 | + name: 'measures', |
| 56 | + label: 'Measures', |
| 57 | + description: 'Aggregatable values defined once and referenced by name. A measure is sum/avg/count/… of a field; a derived measure combines other measures (ratio/sum/difference/product). Measure-scoped filters and derived ops are edited per-row in the dataset designer.', |
| 58 | + fields: [ |
| 59 | + { field: 'measures', type: 'repeater', required: true, helpText: 'Each: name, aggregate, field (optional for count), display format/currency, and a “certified” governance flag' }, |
| 60 | + ], |
| 61 | + }, |
| 62 | + ], |
| 63 | +}); |
0 commit comments