Skip to content

Commit ccfd705

Browse files
committed
feat: add combined filter store
1 parent a9abd58 commit ccfd705

4 files changed

Lines changed: 168 additions & 3 deletions

File tree

packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type DatasourceParamsControllerSpec = {
2020
query: QueryController;
2121
columns: Columns;
2222
customFilters: FiltersInput;
23+
widgetName: string;
2324
};
2425

2526
type FiltersMeta = {
@@ -34,13 +35,14 @@ export class DatasourceParamsController implements ReactiveController {
3435
private columns: Columns;
3536
private query: QueryController;
3637
private customFilters: FiltersInput;
37-
readonly widgetName: string = "dataKey";
38+
readonly widgetName: string;
3839

3940
constructor(host: ReactiveControllerHost, spec: DatasourceParamsControllerSpec) {
4041
host.addController(this);
4142
this.columns = spec.columns;
4243
this.query = spec.query;
4344
this.customFilters = spec.customFilters;
45+
this.widgetName = spec.widgetName;
4446

4547
makeAutoObservable(this, { setup: false });
4648
}
@@ -137,7 +139,7 @@ export class DatasourceParamsController implements ReactiveController {
137139
}
138140

139141
static storageKey(widgetName: string, hash: string): string {
140-
return `${widgetName}:[${hash}]`;
142+
return `[${widgetName}:${hash}]`;
141143
}
142144

143145
static restoreMeta(filter: FilterCondition, widgetName: string): FiltersMeta | null {

packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ export class RootGridStore extends BaseControllerHost {
6666
new DatasourceParamsController(this, {
6767
query,
6868
columns,
69-
customFilters: customFilterHost
69+
customFilters: customFilterHost,
70+
widgetName: props.name
7071
});
7172

7273
new RefreshController(this, {
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { reduceArray, restoreArray } from "@mendix/filter-commons/condition-utils";
2+
import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
3+
import { FilterCondition } from "mendix/filters";
4+
import { autorun, reaction } from "mobx";
5+
import { fnv1aHash } from "../../utils/fnv-1a-hash";
6+
7+
type ConditionWithMeta = {
8+
cond: FilterCondition | undefined;
9+
meta: string;
10+
};
11+
12+
interface ObservableInput {
13+
condWithMeta: ConditionWithMeta;
14+
metaKey: string;
15+
hydrate(value: ConditionWithMeta): void;
16+
}
17+
18+
type MetaBag = Record<string, string>;
19+
20+
export class CombinedFilter {
21+
private _inputs: ObservableInput[];
22+
readonly stableKey: string;
23+
readonly ownMetaKey = "CombinedFilter";
24+
25+
constructor(spec: { stableKey: string; inputs: ObservableInput[] }) {
26+
this._inputs = spec.inputs;
27+
this.stableKey = spec.stableKey;
28+
}
29+
30+
storageKey(hash: string): string {
31+
return `${this.stableKey}-${hash}`;
32+
}
33+
34+
readMetaFromStorage(key: string): MetaBag | null {
35+
const item = sessionStorage.getItem(key);
36+
if (!item) {
37+
return null;
38+
}
39+
try {
40+
return JSON.parse(item) as MetaBag;
41+
} catch (e) {
42+
console.error(`CombinedFilter.readFilterMeta: Error parsing meta for key ${key}`, e);
43+
return null;
44+
}
45+
}
46+
47+
clearFilterMeta(hash: string): void {
48+
sessionStorage.removeItem(this.storageKey(hash));
49+
}
50+
51+
filterHash(filter: FilterCondition): string {
52+
return fnv1aHash(JSON.stringify(filter)).toString();
53+
}
54+
55+
restoreMeta(filter: FilterCondition): MetaBag | null {
56+
const hash = this.filterHash(filter);
57+
const key = this.storageKey(hash);
58+
return this.readMetaFromStorage(key);
59+
}
60+
61+
hydrate(filter: FilterCondition | undefined): void {
62+
if (!filter) {
63+
return;
64+
}
65+
66+
const meta = this.restoreMeta(filter);
67+
if (!meta) {
68+
return;
69+
}
70+
71+
const conditions = restoreArray(filter, meta[this.ownMetaKey]);
72+
if (conditions.length !== this._inputs.length) {
73+
console.error(
74+
`CombinedFilter.hydrate: Number of conditions (${conditions.length}) does not match number of inputs (${this._inputs.length})`
75+
);
76+
return;
77+
}
78+
79+
for (let i = 0; i < this._inputs.length; i++) {
80+
const input = this._inputs[i];
81+
const condWithMeta: ConditionWithMeta = {
82+
cond: conditions[i],
83+
meta: meta[input.metaKey]
84+
};
85+
86+
input.hydrate(condWithMeta);
87+
}
88+
}
89+
90+
get filter(): FilterCondition | undefined {
91+
return this.filterWithBag.filter;
92+
}
93+
94+
get filterWithBag(): {
95+
filter: FilterCondition | undefined;
96+
bag: MetaBag;
97+
hash: string | null;
98+
} {
99+
const bag: MetaBag = {};
100+
const conditions: Array<FilterCondition | undefined> = [];
101+
102+
for (const { condWithMeta: data, metaKey } of this._inputs) {
103+
bag[metaKey] = data.meta;
104+
conditions.push(data.cond);
105+
}
106+
107+
const [filter, meta] = reduceArray(conditions);
108+
bag[this.ownMetaKey] = meta;
109+
110+
return { filter, bag, hash: filter ? this.filterHash(filter) : null };
111+
}
112+
113+
saveFilterMeta(hash: string | null, bag: MetaBag): void {
114+
if (!hash) {
115+
return;
116+
}
117+
118+
sessionStorage.setItem(this.storageKey(hash), JSON.stringify(bag));
119+
}
120+
121+
setup(): () => void {
122+
const [add, disposeAll] = disposeBatch();
123+
124+
add(autorun(() => console.dir(this.filter)));
125+
126+
add(
127+
reaction(
128+
() => this.filterWithBag,
129+
(next, prev) => {
130+
if (prev && prev.hash) {
131+
this.clearFilterMeta(prev.hash);
132+
}
133+
if (next.hash) {
134+
this.saveFilterMeta(next.hash, next.bag);
135+
}
136+
}
137+
)
138+
);
139+
140+
return disposeAll;
141+
}
142+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* eslint-disable no-bitwise */
2+
3+
/**
4+
* Generates 32 bit FNV-1a hash from the given string.
5+
* As explained here: http://isthe.com/chongo/tech/comp/fnv/
6+
*
7+
* @param s {string} String to generate hash from.
8+
* @param [h] {number} FNV-1a hash generation init value.
9+
* @returns {number} The result integer hash.
10+
*/
11+
export function fnv1aHash(s: string, h: number = 0x811c9dc5): number {
12+
const l = s.length;
13+
14+
for (let i = 0; i < l; i++) {
15+
h ^= s.charCodeAt(i);
16+
h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
17+
}
18+
19+
return h >>> 0;
20+
}

0 commit comments

Comments
 (0)