Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-17", "symptom": "`describe page` printed a Data grid 2 as `datagrid dg (DataSource: database from Shop.Ord, PageSize: 25, Pagination: virtualScrolling)` although exec had stored `configurationStorageType: localStorage`, `itemSelectionMethod: rowClick`, `refreshInterval: 30`, `columnsHidable: false`, a column's `minWidth`, labels and the empty-state placeholder (mendixlabs/mxcli#1573). Executing the description wrote the widget's default over every one of them, with `check` and `mx check` at 0 errors; a Studio Pro-authored grid lost its `onClickTrigger: double` the same way. The Gallery had the same loss (pagination, pageSize, showEmptyPlaceholder, state storage).", "cause": "The write side takes any pluggable-widget setting by its own property key (the explicit-key pass), but the describer for the widgets it has dedicated extractors for (datagrid2, gallery, combobox, filters) read only the properties MDL has a keyword for. The generic pluggable path that does print by key (extractExplicitProperties) is skipped for those widget types (isKnownCustomWidgetType), and it has no notion of a default, so it could not simply be switched on: it would print the whole schema of every untouched widget.", "fix": "cmd_pages_describe_bykey.go: nonDefaultPropsByKey reads each property's declared type and default from the stored widget's own Type.ObjectType (and the item type of `columns`) and prints a setting only when it differs from what exec writes for it left out; wired into the datagrid2 and gallery describers, each column, and the emptyPlaceholder slot. Text templates compare against the text the widget ships (Translations), an Expression prints unquoted (quoted when it is only a string literal), an attribute prints bare only where exec resolves a bare name; a value with no MDL spelling (attribute over associations) is named in a `-- NOT re-executable` comment.", "file": "mdl/executor/cmd_pages_describe_bykey.go", "test": "mdl/executor/cmd_pages_describe_bykey_test.go (against the shipped Data grid 2 / Gallery templates); mdl-examples/bug-tests/1573-datagrid-by-key-settings-describe.mdl", "insight": "Three traps decided the design. (1) DefaultValue is not the default exec writes: four text templates have DefaultValue '' but store their shipped text from Translations, so comparing with DefaultValue printed four labels on every grid; the untouched-grid test is built from the shipped widget template for that reason. (2) `describe -> exec` printing 'Unchanged page' proves nothing about the writer: exec keeps the stored page when the statement equals its description, so a lossy describer is a fixed point of itself; the round trip only shows after `drop page` + exec of the description, compared as BSON with ids stripped (v0.27.0 differs on all six pages of the bug-test script, the fix is identical on all six). (3) A by-key `itemSelection: Single` is stored as PrimitiveValue with Selection 'None' (the explicit pass routes a Selection-typed key as a primitive: mappedKeys holds mapping Sources, not keys), i.e. no selection at all, so the bug-test uses `Selection:`; describe skips Selection-typed keys, which keeps a describe -> exec -> BSON comparison from hiding that write-side defect."}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed

- **`describe page` prints a Data grid 2's and a Gallery's settings that MDL has no keyword for** (mendixlabs/mxcli#1573) — a pluggable widget takes every setting by its own key (`configurationStorageType: localStorage`, `itemSelectionMethod: rowClick`, `refreshInterval: 30`, a column's `minWidth`), and `exec` stores it with `mx check` clean, but `describe page` printed only the keyword properties (`PageSize`, `Pagination`, `Selection`, `onClick`). Executing the description therefore wrote the widget's default over each of them, silently: measured on Mendix 11.12.5, a grid with 22 settings changed came back with the 4 that have keywords, and a Studio Pro-authored grid lost its `onClickTrigger: double`. `describe` now prints each setting whose stored value differs from the default, under its key — enumerations and plain values, text templates (with their `{N}` parameters), expressions, attributes (bare inside a data container, qualified where a bare name would not resolve), a column's own settings and the empty-state placeholder slot. The default is read from the widget's own stored definition, not a list, and a text template counts as default when it holds the text the widget ships for it, so a grid that sets none of them describes exactly as before. A setting with no spelling (an attribute reached over associations) is named in a `-- NOT re-executable` comment. A Gallery had the same loss and takes the same fix.

## [0.27.0] - 2026-10-15

Headline: **`exec` is all or nothing: when a statement fails at write time, the whole run is rolled back, so a script never leaves a project half-changed.** Alongside it: a wave of `describe` → `check` → `exec` fixes from round-trip runs over real apps (the Siemens FSM template, the GenAI showcase, Marketplace modules), Mendix 9 projects can take microflows, nanoflows and pluggable widgets again, `check -p` resolves many more of the references `exec` and the build would refuse, the classic `filemanager` widget and role-based `Visible: roles (…)` are in MDL, and the companion draws a module's domain model. The MongoDB driver is upgraded for the October 2026 BSON advisories.
Expand Down
27 changes: 27 additions & 0 deletions docs-site/src/language/widget-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,33 @@ DATAGRID dgOrders (DataSource: DATABASE Sales.Order, PageSize: 20) {
| `PagingPosition` | Position of paging controls | `PagingPosition: both` |
| `ShowPagingButtons` | When to show paging buttons | `ShowPagingButtons: auto` |

**Any other Data grid 2 setting** can be set by its widget key — the name the
widget's own definition gives it (`DESCRIBE WIDGET TYPE datagrid` lists them with
their types, defaults and allowed values). The grid's personalization, for
example, is stored in the browser or in an attribute of the surrounding object:

```sql
-- in the browser's local storage
DATAGRID dgLocal (DataSource: DATABASE Sales.Order,
configurationStorageType: localStorage,
storeFiltersInPersonalization: true)

-- in an attribute of the data view the grid sits in
DATAVIEW dvPref (DataSource: $Pref) {
DATAGRID dgAttr (DataSource: DATABASE Sales.Order,
configurationStorageType: attribute,
configurationAttribute: Config)
}
```

`DESCRIBE PAGE` prints every such setting whose stored value differs from the
widget's default, under the same key, so describing a page and executing the
description gives back the page it started from. A setting it cannot spell
(an attribute reached over an association) is named in a `-- NOT re-executable`
comment instead of being dropped without a word. Column settings with no keyword
of their own (`minWidth`, `minWidthLimit`, `allowEventPropagation`, …) are
written and described the same way, and so are a Gallery's.

**Column properties:**

| Property | Values | Default | Description |
Expand Down
158 changes: 158 additions & 0 deletions mdl-examples/bug-tests/1573-datagrid-by-key-settings-describe.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
mdl 1;
-- ============================================================================
-- Bug: DESCRIBE PAGE dropped a Data grid 2's / Gallery's settings that MDL has
-- no keyword for (mendixlabs/mxcli#1573)
-- ============================================================================
--
-- A pluggable widget has far more settings than MDL has keywords. exec takes
-- every one of them under the widget's own property key, stores it, and
-- `mx check` is clean — but `describe page` printed only the keyword
-- properties (PageSize, Pagination, Selection, …). A describe -> edit -> exec
-- cycle therefore wrote the widget's default back over each of them, silently:
-- the personalization storage, the selection method, the labels, the column
-- minimum width, the empty-state placeholder.
--
-- `describe page` now prints every setting whose stored value differs from the
-- default, under its key, so describe -> exec reproduces the stored widget:
--
-- describe page BugDgByKey.Orders_Local
-- datagrid dgLocal (DataSource: database from BugDgByKey.Ord,
-- configurationStorageType: 'localStorage', …)
--
-- This script is the INPUT side: it must keep passing `mxcli check`, and every
-- setting below is one the description has to carry. The BSON round trip needs a
-- project; it is asserted by mdl/executor/cmd_pages_describe_bykey_test.go
-- (against the shipped widget template) and was measured on Mendix 11.12.5:
-- exec this script, drop each page, exec its description, compare the page
-- BSON with ids stripped (identical) and `mx check` (0 errors).
--
-- Two Data grid 2 rules the settings below respect (mxcli check enforces both):
-- * a setting the widget's editor config hides must hold its default
-- (MDL-WIDGET10): itemSelectionMode needs a non-checkbox selection method,
-- showSelectAllToggle needs the checkbox method, a column's hidable needs
-- columnsHidable on;
-- * columnsFilterable: false needs columns without a filter (page E).
--
-- An Expression-typed setting (rowClass) is covered by the Go test: the checker
-- cannot tell it is one without a project (a separate fix).
-- ============================================================================

CREATE MODULE BugDgByKey;

CREATE OR MODIFY PERSISTENT ENTITY BugDgByKey.Ord (
Code: String(20),
Amount: Decimal,
Placed: DateTime,
Open: Boolean
);

CREATE OR MODIFY PERSISTENT ENTITY BugDgByKey.GridPref (
Config: String(unlimited)
);

-- A: selection, refresh, loading, paging, labels, empty-state placeholder, and
-- the column settings that have no keyword.
CREATE PAGE BugDgByKey.Orders_All (Title: 'All settings', Layout: Atlas_Core.Atlas_Default) {
datagrid dgAll (
DataSource: database BugDgByKey.Ord,
Selection: Multi,
itemSelectionMethod: rowClick,
itemSelectionMode: toggle,
keepSelection: true,
refreshInterval: 30,
refreshIndicator: true,
loadingType: skeleton,
columnsSortable: false,
columnsResizable: false,
columnsDraggable: false,
showEmptyPlaceholder: custom,
onClickTrigger: double,
PageSize: 50,
PagingPosition: top,
ShowPagingButtons: auto,
exportDialogLabel: 'Exporting',
cancelExportLabel: 'Stop export',
selectRowLabel: 'Pick row',
selectAllRowsLabel: 'Pick all',
filterSectionTitle: 'Narrow down',
selectedCountTemplateSingular: '1 row picked',
selectedCountTemplatePlural: 'Many rows picked'
) {
emptyplaceholder {
dynamictext noRows (Content: 'Nothing here')
}
column (Attribute: Code, Hidable: no) { textfilter tf }
column (Attribute: Amount, ShowContentAs: customContent, ColumnWidth: autoFit, minWidth: manual, minWidthLimit: 150, allowEventPropagation: false) {
dynamictext amt (Content: 'amt')
}
column (Attribute: Placed, Alignment: center, WrapText: true, Tooltip: 'when') { datefilter df }
}
};

-- B: "load more" paging with a row count.
CREATE PAGE BugDgByKey.Orders_LoadMore (Title: 'Load more', Layout: Atlas_Core.Atlas_Default) {
datagrid dgMore (
DataSource: database BugDgByKey.Ord,
Pagination: loadMore,
showNumberOfRows: true,
loadMoreButtonCaption: 'Show more',
advanced: true
) {
column (Attribute: Code) { textfilter tf }
}
};

-- C: the grid's personalization stored in an attribute of the surrounding object.
CREATE PAGE BugDgByKey.Orders_Attribute (Title: 'Attribute', Layout: Atlas_Core.Atlas_Default, Params: ( $Pref: BugDgByKey.GridPref )) {
dataview dvPref (DataSource: $Pref) {
datagrid dgAttr (
DataSource: database BugDgByKey.Ord,
configurationStorageType: attribute,
configurationAttribute: Config,
storeFiltersInPersonalization: false
) {
column (Attribute: Code) { textfilter tf }
}
}
};

-- D: the grid's personalization stored in the browser; a plain selection grid
-- with the columns' hide switch off.
CREATE PAGE BugDgByKey.Orders_Local (Title: 'Local', Layout: Atlas_Core.Atlas_Default) {
datagrid dgLocal (
DataSource: database BugDgByKey.Ord,
configurationStorageType: localStorage,
storeFiltersInPersonalization: false,
columnsHidable: false,
Selection: Multi,
showSelectAllToggle: false
) {
column (Attribute: Code) { textfilter tf }
}
};

-- E: no column filters, so the grid can switch filtering off.
CREATE PAGE BugDgByKey.Orders_NoFilters (Title: 'No filters', Layout: Atlas_Core.Atlas_Default) {
datagrid dgNoFilter (DataSource: database BugDgByKey.Ord, columnsFilterable: false) {
column (Attribute: Code)
}
};

-- F: a Gallery takes its settings by key the same way.
CREATE PAGE BugDgByKey.Orders_Gallery (Title: 'Gallery', Layout: Atlas_Core.Atlas_Default) {
gallery glOrders (
DataSource: database BugDgByKey.Ord,
Selection: Multi,
itemSelectionMode: toggle,
pagination: loadMore,
pageSize: 5,
showEmptyPlaceholder: custom
) {
emptyplaceholder {
dynamictext glNoRows (Content: 'Nothing here')
}
template {
dynamictext glCode (Content: 'order')
}
}
};
11 changes: 11 additions & 0 deletions mdl/executor/cmd_pages_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,11 @@ type rawDataGridColumn struct {
Visible string // expression, e.g. "true"
DynamicCellClass string // expression
Tooltip string // text
// ExtraProps are the column settings MDL has no keyword for (minWidth,
// allowEventPropagation, …) that differ from their defaults, printed under
// the key the widget declares. OmittedProps names those that could not be.
ExtraProps []rawExplicitProp
OmittedProps []string
}

// rawWidget represents a widget from raw BSON data for MDL output.
Expand Down Expand Up @@ -870,6 +875,12 @@ type rawWidget struct {
// describe -> exec round trip cannot silently delete a widget's body.
// See unreconstructedContainers.
OmittedContainers []string

// OmittedProps names properties whose stored value differs from the default
// and that DESCRIBE has no spelling for (an attribute reached over
// associations, …). Emitted as a comment, like OmittedContainers, so a
// describe -> exec round trip cannot reset them without saying so.
OmittedProps []string
// Data container context: entity qualified name provided by this container
EntityContext string
// Full widget ID (e.g. "com.mendix.widget.custom.switch.Switch")
Expand Down
Loading
Loading