diff --git a/docs/adr/0004-signalr-datasource-server-side-filtering.md b/docs/adr/0004-signalr-datasource-server-side-filtering.md index edbc6b9..b60f7ac 100644 --- a/docs/adr/0004-signalr-datasource-server-side-filtering.md +++ b/docs/adr/0004-signalr-datasource-server-side-filtering.md @@ -60,7 +60,8 @@ Adopt a **criteria-string** server-side filter for the SignalR DataSource. The D **Follow-ups required:** - File SIGDS + GRID TODO entries: `[FilterCriteriaParam]` attribute (lives beside the other SignalR attributes in AyCode.Services; XML `` MUST state the mandatory last-position + optional convention) + hub injection + registration-time convention validation + **mismatch warning-log** (AyCode.Services.Server); `SignalParams.FilterCriteria` field (AyCode.Services); filter-service — zero-config whitelist default + parse/length hard-reject + criteria Debug-log (AyCode.Services.Server); MgGrid `FilterMode` (Local / Server) + criteria serialization into `SignalParams.FilterCriteria` on `Server` (AyCode.Blazor); add `DevExpress.Data` to the server plugin. `[NotGridFilterable]` (AyCode.Core) is **deferred** — TODO-only when the filter-service lands. -- **Enabled extension — IMPLEMENTED (2026-07-16), local mode**: MgGrid declarative **quick-filter panel**. Shipped shape (simplified from the earlier two-wrapper draft): a `FilterPanel` RenderFragment slot on `MgGridBase` that **itself wraps** its content in the styled `.mg-grid-filter-panel` container and renders it **below the toolbar** (composed into the grid's `ToolbarTemplate`; 2026-07-16) — no separate panel component (the planned `MgGridFilterPanelBase` → `GridFilterPanel` pair was built, then removed as redundant nesting). Consumers place `MgTagBox` (`: DxTagBox` seam) controls directly in the slot, each with a `FieldName` (consistent with grid columns / DevExpress `FieldName`). On selection each control calls `DxGrid.SetFieldFilterCriteria(FieldName, criteria)` via `IMgGridBase.SetFieldQuickFilter`; DevExpress **AND-combines the per-field criteria automatically** (+ the user's filter row) — no manual aggregation, no clobber (**NOT** `SetFilterCriteria`, which replaces the whole filter). A toolbar **Show/Hide Filters** toggle (`IMgGridBase.HasFilterPanel` / `IsFilterPanelVisible` / `ToggleFilterPanel`) appears only when the grid has a filter panel. Implementation notes: imperative `Values` writes inside DevExpress editors must be wrapped in `BeginUpdate()`/`EndUpdate()` (parameter-tracker throws otherwise); first consumer: Partner grid (Currency tag box). The grid's `FilterCriteria` is the single source: locally it filters the bound `Data`; under `FilterMode.Server` the same `FilterCriteria` reaches `GridCustomDataSource.GetItemsAsync(options.FilterCriteria)` → the criteria-string path — so the component is built **once** for both. Needs a server distinct-values source under `FilterMode.Server` (see v2 paging). +- **Enabled extension — IMPLEMENTED (2026-07-16), local mode**: MgGrid declarative **quick-filter panel**. Shipped shape (simplified from the earlier two-wrapper draft): a `FilterPanel` RenderFragment slot on `MgGridBase` that **itself wraps** its content in the styled `.mg-grid-filter-panel` container and renders it **below the toolbar** (composed into the grid's `ToolbarTemplate`; 2026-07-16) — no separate panel component (the planned `MgGridFilterPanelBase` → `GridFilterPanel` pair was built, then removed as redundant nesting). Consumers place `MgGridFilterPanelTagBox` components directly in the slot, each with a `FieldName` (consistent with grid columns / DevExpress `FieldName`). **Seam split (2026-07-16):** `MgTagBox` stays a pure, behavior-free `DxTagBox` seam (universal — no grid wiring; a grid-wired universal seam would even clobber a consumer's `@bind-Values`); the grid filter component is the derived `MgGridFilterPanelTagBox : MgTagBox, IMgGridFilterPanelComponent` — future filter components (dropdown/textbox/checkbox) implement the same contract over their own Mg* seams, with the shared glue to be extracted into a helper when the second one lands. On selection each component calls `DxGrid.SetFieldFilterCriteria(FieldName, criteria)` via `IMgGridBase.SetFilterPanelCriteria` (2026-07-16 rename: the earlier `*QuickFilter*` member/type names unified onto the component's `FilterPanel` name); DevExpress **AND-combines the per-field criteria automatically** (+ the user's filter row) — no manual aggregation, no clobber (**NOT** `SetFilterCriteria`, which replaces the whole filter). **Ownership nuance:** the per-field criteria slot is SHARED with the grid's filter row — the panel only manages criteria its component recognizes as its own shape (`IMgGridFilterPanelComponent.OwnsCriteria`; for the tag box the `IN(FieldName, ...)` it writes), so a value typed into the filter row (visible UI) survives panel hide/clear. The panel has a built-in right-aligned **Clear** button (`IMgGridBase.ClearFilterPanel`, enabled only while a panel-owned criteria is active) clearing the panel-owned filters only. A toolbar **Show/Hide Filters** toggle (`IMgGridBase.HasFilterPanel` / `IsFilterPanelVisible` / `ToggleFilterPanel`) appears only when the grid has a filter panel. Implementation notes: imperative `Values` writes inside DevExpress editors must be wrapped in `BeginUpdate()`/`EndUpdate()` (parameter-tracker throws otherwise); first consumer: Partner grid (Currency tag box). The grid's `FilterCriteria` is the single source: locally it filters the bound `Data`; under `FilterMode.Server` the same `FilterCriteria` reaches `GridCustomDataSource.GetItemsAsync(options.FilterCriteria)` → the criteria-string path — so the component is built **once** for both. Needs a server distinct-values source under `FilterMode.Server` (see v2 paging). + **Persistence & visibility reconcile (2026-07-16):** the panel criteria persists with the grid layout (`GridPersistentLayout.FilterCriteria` — the AutoSave tier and the server-roamed UserSave tier alike, so it can arrive on another machine/browser). The panel components register with the grid (`IMgGridFilterPanelComponent`, weakly held) and the grid **reconciles** them after layout restore / user-layout load / layout reset / panel toggle: visible panel → each control re-reads and re-selects its field's criteria (late-arriving async lookup `Data` re-syncs on parameter update); hidden panel → the controls' **own** criteria are **cleared** — a panel filter must never filter invisibly (this also means hiding the panel deliberately discards the selection); criteria the control does NOT own (shared per-field slot with the filter row — e.g. `Contains("EUR")` typed into the column filter) is left untouched. The panel content is always rendered (CSS-hidden when toggled off) so the controls stay registered — that is how the grid knows *which* fields are panel-owned. Known edge: a roamed layout landing where the grid has **no** `FilterPanel` at all cannot be selectively cleared — AyCode.Blazor `MGGRID_ISSUES.md` **ACBLAZOR-GRID-I-D4T7** (proposed fix there + planned next step: an MgGrid layout envelope, e.g. `MgGridLayoutSave { GridLayoutJson, FilterPanelLayoutJson }`, separating the panel state from the DevExpress layout at save time). - Verify: `DevExpress.Data` version matching the client DevExpress Blazor major; license coverage on the server package feed; linq2db translation of `CriteriaToExpressionConverter` output (function whitelist). - **Quick-search (`SearchText`) — confirmed SEPARATE from `FilterCriteria`.** Per DevExpress docs (Filter API): *"Search settings do not affect the applied filter criteria"* — `SearchText` filters in parallel, NOT folded into `FilterCriteria`. So server-side search does **not** ride the criteria-string path for free; it needs its **own** handling: a thin `SearchText → OR-Contains criteria` adapter over searchable columns (then it rides the same channel), or a separate search field. Same whitelist / DoS caveats apply (search = OR-Contains across many columns). - AyCode.Blazor cross-ref: no `docs/adr/` folder there yet → note in MGGRID docs or a future AyCode.Blazor ADR.