Skip to content
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

[WC-2683] Issue with long xpath #1296

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
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
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/datagrid-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- We added new 'Use lazy load' property that can be used to improve the end-user experience.

### Fixed

- Fixed issue with xpath when widget has many filters.

## [2.24.0] - 2024-09-23

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
import { FilterCondition } from "mendix/filters";
import { and } from "mendix/filters/builders";
import { DatagridContainerProps } from "../../../typings/DatagridProps";
import { ProgressStore } from "../../features/data-export/ProgressStore";
import { SortInstruction } from "../../typings/sorting";
import { StaticInfo } from "../../typings/static-info";
import { ColumnGroupStore } from "./ColumnGroupStore";
import { GridPersonalizationStore } from "./GridPersonalizationStore";
import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/HeaderFiltersStore";
import { conjoin, disjoin } from "@mendix/widget-plugin-filtering/condition-utils";
import { compactArray, fromCompactArray, isAnd } from "@mendix/widget-plugin-filtering/condition-utils";

export class RootGridStore {
columnsStore: ColumnGroupStore;
Expand All @@ -23,7 +24,7 @@ export class RootGridStore {
filtersChannelName: `datagrid/${generateUUID()}`
};

const [columnsViewState, headerViewState] = this.getDsViewState(props) ?? [null, null];
const [columnsViewState, headerViewState] = this.getDsViewState(props);
this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsViewState);
this.headerFiltersStore = new HeaderFiltersStore(props, headerViewState);
this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, this.headerFiltersStore);
Expand All @@ -39,7 +40,9 @@ export class RootGridStore {
* Otherwise computed is suspended.
*/
get conditions(): FilterCondition {
return conjoin([conjoin(this.columnsStore.conditions), conjoin(this.headerFiltersStore.conditions)]);
const columns = this.columnsStore.conditions;
const header = this.headerFiltersStore.conditions;
return and(compactArray(columns), compactArray(header));
}

get sortInstructions(): SortInstruction[] | undefined {
Expand Down Expand Up @@ -67,18 +70,18 @@ export class RootGridStore {
private getDsViewState({
datasource
}: DatagridContainerProps):
| [columns: Array<FilterCondition | undefined>, header: Array<FilterCondition | undefined>]
| null {
if (datasource.filter) {
try {
const [columns, header] = disjoin(datasource.filter);
return [disjoin(columns!), disjoin(header!)];
} catch {
//
}
| [columns: Array<FilterCondition | undefined>, header: Array<FilterCondition | undefined>] {
if (!datasource.filter) {
return [[], []];
}

return null;
if (!isAnd(datasource.filter)) {
return [[], []];
}
if (datasource.filter.args.length !== 2) {
return [[], []];
}
const [columns, header] = datasource.filter.args;
return [fromCompactArray(columns), fromCompactArray(header)];
}

updateProps(props: DatagridContainerProps): void {
Expand Down
2 changes: 2 additions & 0 deletions packages/pluggableWidgets/gallery-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- We resolved an issue where the gallery filter was not being applied at first.

- Fixed issue with xpath when widget has many filters.

## [1.12.0] - 2024-09-23

### Changed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { conjoin, disjoin } from "@mendix/widget-plugin-filtering/condition-utils";
import { compactArray, fromCompactArray } from "@mendix/widget-plugin-filtering/condition-utils";
import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/HeaderFiltersStore";
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
import { SortAPIProvider } from "@mendix/widget-plugin-sorting/providers/SortAPIProvider";
import { ListValue } from "mendix";
import { FilterCondition } from "mendix/filters";
import { GalleryContainerProps } from "../../typings/GalleryProps";
import { SortAPIProvider } from "@mendix/widget-plugin-sorting/providers/SortAPIProvider";

type SortInstruction = ListValue["sortOrder"] extends Array<infer T> ? T : never;

Expand All @@ -31,7 +31,7 @@ export class RootGalleryStore {
}

get conditions(): FilterCondition {
return conjoin(this.headerFiltersStore.conditions);
return compactArray(this.headerFiltersStore.conditions);
}

get sortOrder(): SortInstruction[] {
Expand All @@ -54,15 +54,11 @@ export class RootGalleryStore {
}

// Mirror operation from "condition";
private getDsViewState({ datasource }: GalleryContainerProps): Array<FilterCondition | undefined> | null {
if (datasource.filter) {
try {
return disjoin(datasource.filter);
} catch {
//
}
private getDsViewState({ datasource }: GalleryContainerProps): Array<FilterCondition | undefined> {
if (!datasource.filter) {
return [];
}

return null;
return fromCompactArray(datasource.filter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
jest.mock("mendix/filters/builders");

import { equals, literal } from "mendix/filters/builders";
import { compactArray, fromCompactArray, tag } from "../condition-utils";
import { AndCondition } from "mendix/filters";

describe("condition-utils", () => {
describe("compactArray", () => {
it("returns 'and' condition for zero array", () => {
const result = compactArray([]);
expect(result).toMatchObject({ name: "and", type: "function" });
expect((result as AndCondition).args).toHaveLength(2);
});

it("returns 'and' condition for empty array ", () => {
const result = compactArray([undefined, undefined, undefined]);
expect(result).toMatchObject({ name: "and", type: "function" });
expect((result as AndCondition).args).toHaveLength(2);
});

it("returns 'and' condition with 4 args", () => {
const result = compactArray([tag("0"), undefined, tag("2")]);
expect(result).toMatchObject({ name: "and", type: "function" });
expect((result as AndCondition).args).toHaveLength(4);
});
});

describe("fromCompactArray", () => {
it("unpack condition created with compactArray", () => {
const input = [
equals(literal("1"), literal("1")),
undefined,
equals(literal("foo"), literal("bar")),
undefined,
undefined,
equals(literal(new Date("2024-09-17T14:00:00.000Z")), literal(new Date("2024-09-17T14:00:00.000Z")))
];
const cond = compactArray(input);
expect(cond).toBeDefined();
const result = fromCompactArray(cond!);
expect(result).toEqual(input);
});
});
});
94 changes: 69 additions & 25 deletions packages/shared/widget-plugin-filtering/src/condition-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,49 +34,93 @@ export function isNotEmptyExp(exp: FilterCondition): boolean {
return isBinary(exp) && exp.arg2.type === "literal" && exp.name === "!=" && exp.arg2.valueType === "undefined";
}

function placeholder(): FilterCondition {
return equals(literal(true), literal(true));
interface TagName {
readonly type: "literal";
readonly value: string;
readonly valueType: "string";
}

function isPlaceholder(exp: FilterCondition): boolean {
interface TagCond {
readonly type: "function";
readonly name: "=";
readonly arg1: TagName;
readonly arg2: TagName;
}

export function tag(name: string): TagCond {
return equals(literal(name), literal(name)) as TagCond;
}

export function isTag(cond: FilterCondition): cond is TagCond {
return (
exp.name === "=" &&
exp.arg1.type === "literal" &&
exp.arg2.type === "literal" &&
exp.arg1.value === true &&
exp.arg2.value === true
cond.name === "=" &&
cond.arg1.type === "literal" &&
cond.arg2.type === "literal" &&
/string/i.test(cond.arg1.valueType) &&
/string/i.test(cond.arg2.valueType) &&
cond.arg1.value === cond.arg2.value
);
}

export function conjoin(exp: Array<FilterCondition | undefined>): FilterCondition {
switch (exp.length) {
case 0:
return and(placeholder(), placeholder());
case 1:
return and(exp.at(0) ?? placeholder(), placeholder());
default: {
return and(...exp.map(x => (x === undefined ? placeholder() : x)));
}
type ArrayMeta = readonly [len: number, indexes: number[]];

function arrayTag(meta: ArrayMeta): string {
return JSON.stringify(meta);
}

function fromArrayTag(tag: string): ArrayMeta | undefined {
let len: ArrayMeta[0];
let indexes: ArrayMeta[1];
try {
[len, indexes] = JSON.parse(tag);
} catch {
return undefined;
}
if (typeof len !== "number" || !Array.isArray(indexes) || !indexes.every(x => typeof x === "number")) {
return undefined;
}
return [len, indexes];
}

export function disjoin(exp: FilterCondition): Array<FilterCondition | undefined> {
if (exp.name !== "and") {
throw new Error('only "and" expression is supported');
function shrink<T>(array: Array<T | undefined>): [indexes: number[], items: T[]] {
return [array.flatMap((x, i) => (x === undefined ? [] : [i])), array.filter((x): x is T => x !== undefined)];
}

export function compactArray(input: Array<FilterCondition | undefined>): FilterCondition {
const [indexes, items] = shrink(input);
const arrayMeta = [input.length, indexes] as const;
const metaTag = tag(arrayTag(arrayMeta));
// As 'and' requires at least 2 args, we add a placeholder
const placeholder = tag("_");
return and(metaTag, placeholder, ...items);
}

export function fromCompactArray(cond: FilterCondition): Array<FilterCondition | undefined> {
if (!isAnd(cond)) {
return [];
}

const [metaTag] = cond.args;
const arrayMeta = isTag(metaTag) ? fromArrayTag(metaTag.arg1.value) : undefined;

if (!arrayMeta) {
return [];
}

return exp.args.map(x => (isPlaceholder(x) ? undefined : x));
const [length, indexes] = arrayMeta;
const arr: Array<FilterCondition | undefined> = Array(length).fill(undefined);
cond.args.slice(2).forEach((cond, i) => {
arr[indexes[i]] = cond;
});

return arr;
}

export function inputStateFromCond<Fn, V>(
cond: FilterCondition,
fn: (func: FilterFunction | "between" | "empty" | "notEmpty") => Fn,
val: (exp: LiteralExpression) => V
): null | [Fn, V] | [Fn, V, V] {
if (isPlaceholder(cond)) {
return null;
}

// Or - condition build for multiple attrs, get state from the first one.
if (isOr(cond)) {
return inputStateFromCond(cond.args[0], fn, val);
Expand Down
Loading