Skip to content

[WC-2978] Datagrid: Support OData request on filtering #1784

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 15, 2025
Merged
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
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@
"rc-trigger": "patches/rc-trigger.patch"
},
"onlyBuiltDependencies": [
"@swc/core",
"canvas"
],
"ignoredBuiltDependencies": [
"@parcel/watcher",
"core-js",
"es5-ext"
]
},
"prettier": "@mendix/prettier-config-web-widgets"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ describe("Date Filter", () => {
hasError: false,
value: { type: "direct", store: dateFilterStore }
},
filterObserver: {} as ObservableFilterHost,
sharedInitFilter: []
filterObserver: {} as ObservableFilterHost
};

(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI | null>(
Expand Down Expand Up @@ -168,8 +167,7 @@ describe("Date Filter", () => {
hasError: false,
value: { type: "direct", store: dateFilterStore }
},
filterObserver: {} as ObservableFilterHost,
sharedInitFilter: []
filterObserver: {} as ObservableFilterHost
};

(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI | null>(
Expand Down Expand Up @@ -223,8 +221,7 @@ describe("Date Filter", () => {
hasError: false,
value: { type: "direct", store: dateFilterStore }
},
filterObserver: {} as ObservableFilterHost,
sharedInitFilter: []
filterObserver: {} as ObservableFilterHost
};

(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI | null>(
Expand Down Expand Up @@ -264,8 +261,7 @@ describe("Date Filter", () => {
hasError: false,
value: { type: "direct", store: dateFilterStore }
},
filterObserver: {} as ObservableFilterHost,
sharedInitFilter: []
filterObserver: {} as ObservableFilterHost
};

(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI | null>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class RefStoreProvider extends BaseStoreProvider<RefFilterStore> {
this.dataKey = gate.props.name;
this._store = new RefFilterStore({
gate,
initCond: this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey)
initCond: null
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ const setContext = (store: NumberInputFilterStore) => {
hasError: false,
value: { type: "direct", store }
},
filterObserver: {} as ObservableFilterHost,
sharedInitFilter: []
filterObserver: {} as ObservableFilterHost
};
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(filterAPI);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ const setContext = (store: StringInputFilterStore) => {
hasError: false,
value: { type: "direct", store }
},
filterObserver: {} as ObservableFilterHost,
sharedInitFilter: []
filterObserver: {} as ObservableFilterHost
};
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(filterAPI);
};
Expand Down
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/datagrid-web/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.0.0/),

## [Unreleased]

### Changed

- We removed all metadata stored in xpath to improve integration with other services.

## [3.0.1] - 2025-08-05

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/datagrid-web/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@mendix/datagrid-web",
"widgetName": "Datagrid",
"version": "3.0.1",
"version": "3.0.2",
"description": "",
"copyright": "© Mendix Technology BV 2025. All rights reserved.",
"license": "Apache-2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const FilterContext = getGlobalFilterContextObject();
function HeaderContainer(props: WidgetHeaderContextProps): ReactElement {
const selectionContext = useCreateSelectionContextValue(props.selectionHelper);
return (
<FilterContext.Provider value={props.rootStore.autonomousFilterAPI}>
<FilterContext.Provider value={props.rootStore.filterAPI}>
<SelectionContext.Provider value={selectionContext}>{props.children}</SelectionContext.Provider>
</FilterContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,85 +1,53 @@
import { compactArray, fromCompactArray, isAnd } from "@mendix/filter-commons/condition-utils";
import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller";
import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller";
import { FilterCondition } from "mendix/filters";
import { and } from "mendix/filters/builders";
import { makeAutoObservable, reaction } from "mobx";
import { reaction } from "mobx";
import { SortInstruction } from "../typings/sorting";

interface Columns {
conditions: Array<FilterCondition | undefined>;
sortInstructions: SortInstruction[] | undefined;
interface ObservableFilterStore {
filter: FilterCondition | undefined;
}

interface FiltersInput {
conditions: Array<FilterCondition | undefined>;
interface ObservableSortStore {
sortInstructions: SortInstruction[] | undefined;
}

type DatasourceParamsControllerSpec = {
query: QueryController;
columns: Columns;
customFilters: FiltersInput;
filterHost: ObservableFilterStore;
sortHost: ObservableSortStore;
};

export class DatasourceParamsController implements ReactiveController {
private columns: Columns;
private query: QueryController;
private customFilters: FiltersInput;
private filterHost: ObservableFilterStore;
private sortHost: ObservableSortStore;

constructor(host: ReactiveControllerHost, spec: DatasourceParamsControllerSpec) {
host.addController(this);
this.columns = spec.columns;
this.filterHost = spec.filterHost;
this.sortHost = spec.sortHost;
this.query = spec.query;
this.customFilters = spec.customFilters;

makeAutoObservable(this, { setup: false });
}

private get derivedFilter(): FilterCondition | undefined {
const { columns, customFilters } = this;

return and(compactArray(columns.conditions), compactArray(customFilters.conditions));
}

private get derivedSortOrder(): SortInstruction[] | undefined {
return this.columns.sortInstructions;
}

setup(): () => void {
const [add, disposeAll] = disposeBatch();
add(
reaction(
() => this.derivedSortOrder,
() => this.sortHost.sortInstructions,
sortOrder => this.query.setSortOrder(sortOrder),
{ fireImmediately: true }
)
);
add(
reaction(
() => this.derivedFilter,
() => this.filterHost.filter,
filter => this.query.setFilter(filter),
{ fireImmediately: true }
)
);

return disposeAll;
}

static unzipFilter(
filter?: FilterCondition
): [columns: Array<FilterCondition | undefined>, sharedFilter: Array<FilterCondition | undefined>] {
if (!filter) {
return [[], []];
}
if (!isAnd(filter)) {
return [[], []];
}
if (filter.args.length !== 2) {
return [[], []];
}

const [columns, shared] = filter.args;
return [fromCompactArray(columns), fromCompactArray(shared)];
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { reduceArray, restoreArray } from "@mendix/filter-commons/condition-utils";
import { FiltersSettingsMap } from "@mendix/filter-commons/typings/settings";
import { ConditionWithMeta } from "@mendix/widget-plugin-filtering/typings/ConditionWithMeta";
import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost";
import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
import { FilterCondition } from "mendix/filters";

import { action, computed, makeObservable, observable } from "mobx";
import { DatagridContainerProps } from "../../../typings/DatagridProps";
import { ColumnId, GridColumn } from "../../typings/GridColumn";
Expand All @@ -13,7 +16,7 @@ import {
sortInstructionsToSortRules,
sortRulesToSortInstructions
} from "./ColumnsSortingStore";
import { ColumnFilterStore, ObserverBag } from "./column/ColumnFilterStore";
import { ColumnFilterStore } from "./column/ColumnFilterStore";
import { ColumnStore } from "./column/ColumnStore";

export interface IColumnGroupStore {
Expand All @@ -40,24 +43,24 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore {

readonly columnFilters: ColumnFilterStore[];

readonly metaKey = "ColumnGroupStore";

sorting: ColumnsSortingStore;
isResizing = false;

constructor(
props: Pick<DatagridContainerProps, "columns" | "datasource">,
info: StaticInfo,
initFilter: Array<FilterCondition | undefined>,
observerBag: ObserverBag
filterHost: ObservableFilterHost
) {
this._allColumns = [];
this.columnFilters = [];

props.columns.forEach((columnProps, i) => {
const initCond = initFilter.at(i) ?? null;
const column = new ColumnStore(i, columnProps, this);
this._allColumnsById.set(column.columnId, column);
this._allColumns[i] = column;
this.columnFilters[i] = new ColumnFilterStore(columnProps, info, initCond, observerBag);
this.columnFilters[i] = new ColumnFilterStore(columnProps, info, filterHost);
});

this.sorting = new ColumnsSortingStore(
Expand All @@ -72,13 +75,14 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore {
_allColumnsOrdered: computed,
availableColumns: computed,
visibleColumns: computed,
conditions: computed.struct,
condWithMeta: computed,
columnSettings: computed.struct,
filterSettings: computed({ keepAlive: true }),
updateProps: action,
setIsResizing: action,
swapColumns: action,
setColumnSettings: action
setColumnSettings: action,
hydrate: action
});
}

Expand Down Expand Up @@ -142,12 +146,6 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore {
return [...this.availableColumns].filter(column => !column.isHidden);
}

get conditions(): Array<FilterCondition | undefined> {
return this.columnFilters.map((store, index) => {
return this._allColumns[index].isHidden ? undefined : store.condition;
});
}

get sortInstructions(): SortInstruction[] | undefined {
return sortRulesToSortInstructions(this.sorting.rules, this._allColumns);
}
Expand Down Expand Up @@ -194,4 +192,21 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore {
isLastVisible(column: ColumnStore): boolean {
return this.visibleColumns.at(-1) === column;
}

get condWithMeta(): ConditionWithMeta {
const conditions = this.columnFilters.map((store, index) => {
return this._allColumns[index].isHidden ? undefined : store.condition;
});
const [cond, meta] = reduceArray(conditions);
return { cond, meta };
}

hydrate({ cond, meta }: ConditionWithMeta): void {
restoreArray(cond, meta).forEach((condition, index) => {
const filter = this.columnFilters[index];
if (filter && condition) {
filter.fromViewState(condition);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createContextWithStub, FilterAPI } from "@mendix/widget-plugin-filtering/context";
import { CombinedFilter } from "@mendix/widget-plugin-filtering/stores/generic/CombinedFilter";
import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
import { DatasourceController } from "@mendix/widget-plugin-grid/query/DatasourceController";
import { RefreshController } from "@mendix/widget-plugin-grid/query/RefreshController";
Expand Down Expand Up @@ -30,40 +31,47 @@ export class RootGridStore extends BaseControllerHost {
exportProgressCtrl: ProgressStore;
loaderCtrl: DerivedLoaderController;
paginationCtrl: PaginationController;
readonly autonomousFilterAPI: FilterAPI;
readonly filterAPI: FilterAPI;

private gate: Gate;

constructor({ gate, exportCtrl }: Spec) {
super();

const { props } = gate;
const [columnsInitFilter, sharedInitFilter] = DatasourceParamsController.unzipFilter(props.datasource.filter);

this.gate = gate;

this.staticInfo = {
name: props.name,
filtersChannelName: `datagrid/${generateUUID()}`
};
const customFilterHost = new CustomFilterHost();

const filterHost = new CustomFilterHost();

const query = new DatasourceController(this, { gate });
this.autonomousFilterAPI = createContextWithStub({
filterObserver: customFilterHost,
sharedInitFilter,

this.filterAPI = createContextWithStub({
filterObserver: filterHost,
parentChannelName: this.staticInfo.filtersChannelName
});
const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsInitFilter, {
customFilterHost,
sharedInitFilter
}));
this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, customFilterHost);

this.columnsStore = new ColumnGroupStore(props, this.staticInfo, filterHost);

const combinedFilter = new CombinedFilter(this, {
stableKey: props.name,
inputs: [filterHost, this.columnsStore]
});

this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, filterHost);

this.paginationCtrl = new PaginationController(this, { gate, query });

this.exportProgressCtrl = exportCtrl;

new DatasourceParamsController(this, {
query,
columns,
customFilters: customFilterHost
filterHost: combinedFilter,
sortHost: this.columnsStore
});

new RefreshController(this, {
Expand All @@ -73,9 +81,11 @@ export class RootGridStore extends BaseControllerHost {

this.loaderCtrl = new DerivedLoaderController({
exp: exportCtrl,
cols: columns,
cols: this.columnsStore,
query
});

combinedFilter.hydrate(props.datasource.filter);
}

setup(): () => void {
Expand Down
Loading
Loading