From d47cbe1cfc9b7179377374b5f61eaa356982922c Mon Sep 17 00:00:00 2001 From: Julian Wielga Date: Tue, 10 Dec 2024 12:36:29 +0100 Subject: [PATCH 1/2] minor fix to previous editor fix with scroll view to cursor --- .../graph/node-modal/editors/expression/AceWithSettings.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx b/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx index 51d7b19a96a..c59f8f99743 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/AceWithSettings.tsx @@ -43,6 +43,7 @@ export default forwardRef(function AceWithSettings( const scrollToView = throttle( () => { + if (!editor.isFocused()) return; // before setting cursor position ensure all position calculations are actual editor?.renderer.updateFull(true); const activeElement = editor.container.querySelector(".ace_cursor") || document.activeElement; From ea5279099a1b22234644def11bdfccf374973703 Mon Sep 17 00:00:00 2001 From: Julian Wielga Date: Tue, 10 Dec 2024 13:10:58 +0100 Subject: [PATCH 2/2] better aggregations spel parsing --- designer/client/package-lock.json | 142 +++++----- designer/client/package.json | 2 +- .../src/components/graph/PanZoomPlugin.ts | 2 +- .../node-modal/aggregate/aggMapLikeParser.tsx | 254 +++++++++++------- .../graph/node-modal/aggregate/parser.test.ts | 60 +++++ .../aggregate/useAggParamsSerializer.tsx | 20 +- .../toolbarSettings/DEV_TOOLBARS.ts | 6 +- designer/client/src/devHelpers.ts | 11 + 8 files changed, 312 insertions(+), 185 deletions(-) create mode 100644 designer/client/src/components/graph/node-modal/aggregate/parser.test.ts create mode 100644 designer/client/src/devHelpers.ts diff --git a/designer/client/package-lock.json b/designer/client/package-lock.json index 9c85baa895e..8ca71715477 100644 --- a/designer/client/package-lock.json +++ b/designer/client/package-lock.json @@ -166,7 +166,7 @@ "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", "chalk": "4.1.2", - "chevrotain": "11.0.3", + "chevrotain": "10.5.0", "color": "4.2.0", "copy-webpack-plugin": "11.0.0", "crypto-browserify": "3.12.0", @@ -2308,42 +2308,36 @@ "dev": true }, "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", "dev": true, "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" } }, "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", "dev": true, "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" } }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "dev": true - }, "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", "dev": true }, "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", "dev": true }, "node_modules/@colors/colors": { @@ -6537,9 +6531,9 @@ } }, "node_modules/@types/jest": { - "version": "29.5.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz", - "integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==", + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -9563,17 +9557,17 @@ } }, "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", "dev": true, "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" } }, "node_modules/chokidar": { @@ -23415,6 +23409,12 @@ "node": ">=0.10.0" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "dev": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", @@ -30104,42 +30104,36 @@ "dev": true }, "@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz", + "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==", "dev": true, "requires": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" } }, "@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz", + "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==", "dev": true, "requires": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" } }, - "@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "dev": true - }, "@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz", + "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==", "dev": true }, "@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz", + "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==", "dev": true }, "@colors/colors": { @@ -33100,9 +33094,9 @@ } }, "@types/jest": { - "version": "29.5.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz", - "integrity": "sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==", + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", "dev": true, "requires": { "expect": "^29.0.0", @@ -35471,17 +35465,17 @@ } }, "chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz", + "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==", "dev": true, "requires": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" } }, "chokidar": { @@ -45830,6 +45824,12 @@ } } }, + "regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "dev": true + }, "regexp.prototype.flags": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", diff --git a/designer/client/package.json b/designer/client/package.json index 9ee99243032..6983f7f95c9 100644 --- a/designer/client/package.json +++ b/designer/client/package.json @@ -159,7 +159,7 @@ "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", "chalk": "4.1.2", - "chevrotain": "11.0.3", + "chevrotain": "10.5.0", "color": "4.2.0", "copy-webpack-plugin": "11.0.0", "crypto-browserify": "3.12.0", diff --git a/designer/client/src/components/graph/PanZoomPlugin.ts b/designer/client/src/components/graph/PanZoomPlugin.ts index c1bfc314530..57952bb7c25 100644 --- a/designer/client/src/components/graph/PanZoomPlugin.ts +++ b/designer/client/src/components/graph/PanZoomPlugin.ts @@ -1,11 +1,11 @@ /* @refresh reset */ import { dia, g } from "jointjs"; import { throttle } from "lodash"; +import { isVisualTesting } from "../../devHelpers"; import { GlobalCursor } from "./GlobalCursor"; import { select, Selection } from "d3-selection"; import { D3ZoomEvent, zoom, ZoomBehavior, ZoomedElementBaseType, zoomIdentity, ZoomTransform } from "d3-zoom"; import { rafThrottle } from "./rafThrottle"; -import { isVisualTesting } from "../toolbarSettings/DEV_TOOLBARS"; function isModified(event: MouseEvent | TouchEvent) { return event.shiftKey || event.ctrlKey || event.altKey || event.metaKey; diff --git a/designer/client/src/components/graph/node-modal/aggregate/aggMapLikeParser.tsx b/designer/client/src/components/graph/node-modal/aggregate/aggMapLikeParser.tsx index 793b26c7d9b..0adebac9a3b 100644 --- a/designer/client/src/components/graph/node-modal/aggregate/aggMapLikeParser.tsx +++ b/designer/client/src/components/graph/node-modal/aggregate/aggMapLikeParser.tsx @@ -1,28 +1,33 @@ import { createToken, EmbeddedActionsParser, Lexer } from "chevrotain"; +import { withLogs } from "../../../../devHelpers"; + +const LCurly = createToken({ name: "LCurly", pattern: /\{/, push_mode: "inside" }); +const RCurly = createToken({ name: "RCurly", pattern: /}/, pop_mode: true }); const CollectionOpen = createToken({ name: "CollectionOpen", pattern: /#COLLECTION.join\(\{/, + push_mode: "inside", }); const CollectionClose = createToken({ name: "CollectionClose", pattern: /}, "|"\)/, -}); -const ListOpen = createToken({ - name: "ListOpen", - pattern: /{/, + pop_mode: true, }); const ListClose = createToken({ name: "ListClose", pattern: /}\.toString/, + pop_mode: true, }); const MapOpen = createToken({ name: "MapOpen", - pattern: /(#AGG\.map\()?{/, + pattern: /#AGG\.map\(\{/, + push_mode: "inside", }); const MapClose = createToken({ name: "MapClose", - pattern: /}(\))?/, + pattern: /}\)/, + pop_mode: true, }); const Comma = createToken({ name: "Comma", @@ -34,8 +39,23 @@ const Colon = createToken({ }); const Identifier = createToken({ name: "Identifier", - pattern: /([a-zA-Z]\w*|"[^"]+")/, + pattern: /([a-zA-Z0-9]([.\s]?\w+)*)/, +}); + +const SingleQuoted = createToken({ + name: "SingleQuoted", + pattern: /'[^']*'/, +}); +const DoubleQuoted = createToken({ + name: "DoubleQuoted", + pattern: /"[^"]*"/, }); +const Wrapped = createToken({ + name: "Wrapped", + pattern: /\{[^}]*}/, + pop_mode: true, +}); + const Number = createToken({ name: "Number", pattern: /\d+/, @@ -50,113 +70,167 @@ const WhiteSpace = createToken({ group: Lexer.SKIPPED, }); -const aggMapTokens = [ - CollectionClose, - CollectionOpen, - ListClose, - ListOpen, - MapClose, - MapOpen, - Comma, - Spel, - Identifier, - Number, - Colon, - WhiteSpace, -]; - -export const AggMapLikeLexer = new Lexer(aggMapTokens); +const aggMapTokens = { + modes: { + outside: [ + SingleQuoted, + DoubleQuoted, + CollectionOpen, + MapOpen, + CollectionClose, + MapClose, + ListClose, + LCurly, + RCurly, + Comma, + Colon, + WhiteSpace, + Spel, + Identifier, + Number, + ], + inside: [ + Wrapped, + SingleQuoted, + DoubleQuoted, + CollectionClose, + MapClose, + ListClose, + RCurly, + Comma, + Colon, + WhiteSpace, + Spel, + Identifier, + Number, + ], + }, + defaultMode: "outside", +}; export class AggMapLikeParser extends EmbeddedActionsParser { - constructor() { - super(aggMapTokens, { recoveryEnabled: true }); - - this.performSelfAnalysis(); - } - - object = this.RULE("object", () => { + private lexer: Lexer; + private quoted = this.RULE("quoted", () => { + return this.OR([{ ALT: () => this.CONSUME(SingleQuoted) }, { ALT: () => this.CONSUME(DoubleQuoted) }]); + }); + private collectionItem = this.RULE("collectionItem", () => { + return this.OR([ + { ALT: () => this.CONSUME(Wrapped).image }, + { ALT: () => this.SUBRULE(this.quoted).image }, + { ALT: () => this.CONSUME(Spel).image?.trim() }, + { ALT: () => this.CONSUME(Identifier).image?.trim() }, + { ALT: () => this.CONSUME(Number).image }, + ]); + }); + private objectItem = this.RULE("objectItem", () => { const obj = {}; + const lit = this.OR([ + { ALT: () => this.CONSUME(Identifier).image }, + { + ALT: () => { + const { image } = this.SUBRULE(this.quoted); + return image?.substring?.(1, image.length - 1); + }, + }, + ]); + const colon = this.CONSUME(Colon); + const value = this.SUBRULE(this.collectionItem); - this.OR([{ ALT: () => this.CONSUME(MapOpen) }, { ALT: () => this.CONSUME(ListOpen) }]); + if (colon.isInsertedInRecovery) { + return null; + } + obj[lit] = value; + return obj; + }); + private mapItems = this.RULE("mapItems", () => { + const obj = {}; this.MANY_SEP({ SEP: Comma, DEF: () => { Object.assign(obj, this.SUBRULE(this.objectItem)); }, }); - this.CONSUME(MapClose); - return obj; }); + private aggMap = this.RULE("aggMap", () => { + const opening = this.CONSUME(MapOpen); + const obj = this.SUBRULE(this.mapItems); + this.CONSUME(MapClose); - objectItem = this.RULE("objectItem", () => { - const obj = {}; - - const lit = this.CONSUME(Identifier); - this.CONSUME(Colon); - - const value = this.OR([ - { ALT: () => this.CONSUME(Spel) }, - { ALT: () => this.CONSUME2(Identifier) }, - { ALT: () => this.CONSUME(Number) }, - ]); + if (!opening.image || opening.isInsertedInRecovery) { + return null; + } - let key: string; + return obj; + }); + private plainMap = this.RULE("plainMap", () => { + const opening = this.CONSUME(LCurly); + const obj = this.SUBRULE(this.mapItems); + this.CONSUME(RCurly); - if (!lit.isInsertedInRecovery) { - key = lit.image.replaceAll(/"/g, ""); - obj[key] = value.image; + if (!opening.image || opening.isInsertedInRecovery) { + return null; } return obj; }); - - collection = this.RULE("collection", () => { + private object = this.RULE("object", () => { + return this.OR([{ ALT: () => this.SUBRULE(this.aggMap) }, { ALT: () => this.SUBRULE(this.plainMap) }]); + }); + private collection = this.RULE("collection", () => { const arr = []; - - this.OR([ - { - ALT: () => { - this.CONSUME(CollectionOpen); - this.AT_LEAST_ONE_SEP({ - SEP: Comma, - DEF: () => { - const item = this.SUBRULE(this.collectionItem); - if (!item) return; - arr.push(item); - }, - }); - this.CONSUME(CollectionClose); - }, + this.CONSUME(CollectionOpen); + this.MANY_SEP({ + SEP: Comma, + DEF: () => { + const item = this.SUBRULE(this.collectionItem); + if (!item) return; + arr.push(item); }, - { - ALT: () => { - this.CONSUME(ListOpen); - this.AT_LEAST_ONE_SEP2({ - SEP: Comma, - DEF: () => { - const item = this.SUBRULE2(this.collectionItem); - if (!item) return; - arr.push(item); - }, - }); - this.CONSUME(ListClose); - }, + }); + this.CONSUME(CollectionClose); + return arr; + }); + private list = this.RULE("list", () => { + const arr = []; + this.CONSUME(LCurly); + this.MANY_SEP({ + SEP: Comma, + DEF: () => { + const item = this.SUBRULE(this.collectionItem); + if (!item) return; + arr.push(item); }, - ]); - + }); + this.CONSUME(ListClose); return arr; }); + private groupBy = this.RULE("groupBy", () => { + return this.OR([{ ALT: () => this.SUBRULE(this.collection) }, { ALT: () => this.SUBRULE(this.list) }]); + }); + private fullText = ""; - collectionItem = this.RULE("collectionItem", () => { - const value = this.OR([ - { ALT: () => this.CONSUME(Spel) }, - { ALT: () => this.CONSUME(Identifier) }, - { ALT: () => this.CONSUME(Number) }, - ]); + constructor(tokens = aggMapTokens) { + super(tokens, { recoveryEnabled: true }); + this.lexer = new Lexer(tokens); + this.parseObject = withLogs(this.parseObject.bind(this)); + this.parseList = withLogs(this.parseList.bind(this)); + this.performSelfAnalysis(); + } - if (!value.isInsertedInRecovery) { - return value.image; - } - }); + parseList(input: string): Array { + this.tokenizeInput(input); + return this.groupBy() || null; + } + + parseObject(input: string): Record { + this.tokenizeInput(input); + return this.object() || null; + } + + private tokenizeInput(input: string) { + const lexResult = this.lexer.tokenize(input); + this.fullText = input; + this.input = lexResult.tokens; + } } diff --git a/designer/client/src/components/graph/node-modal/aggregate/parser.test.ts b/designer/client/src/components/graph/node-modal/aggregate/parser.test.ts new file mode 100644 index 00000000000..5705cdc19bd --- /dev/null +++ b/designer/client/src/components/graph/node-modal/aggregate/parser.test.ts @@ -0,0 +1,60 @@ +import { AggMapLikeParser } from "./aggMapLikeParser"; + +describe("AggMapLikeParser", () => { + let parser: AggMapLikeParser; + + beforeEach(() => { + parser = new AggMapLikeParser(); + }); + + it.each([ + [`aaa`, null], + [`"aaa"`, null], + [`{`, null], + [`{}`, null], + [`{123}`, null], + [`{}.toString`, []], + [`#COLLECTION.join({}, "|")`, []], + [`{123}.toString`, ["123"]], + [`{ 123 }.toString`, ["123"]], + [`{123,456}.toString`, ["123", "456"]], + [`{ 123, 456 }.toString`, ["123", "456"]], + [`{ 123, aaa, 456 }.toString`, ["123", "aaa", "456"]], + [`{ 123, aaa.bbb }.toString`, ["123", "aaa.bbb"]], + [`{ 123, aaa_bbb }.toString`, ["123", "aaa_bbb"]], + [`{ 123, aaa bbb, ccc }.toString`, ["123", "aaa bbb", "ccc"]], + [`{ 123, "aaa bbb" }.toString`, ["123", `"aaa bbb"`]], + [`{ 123, 'aaa bbb' }.toString`, ["123", `'aaa bbb'`]], + [`{ 123, #aaa.bbb }.toString`, ["123", `#aaa.bbb`]], + [`{ 123, 123.bbb }.toString`, ["123", `123.bbb`]], + [`{ 123, 123.456.bbb }.toString`, ["123", `123.456.bbb`]], + [`{ 123, "{ 123, 456 }" }.toString`, ["123", `"{ 123, 456 }"`]], + [`{ 123, { 123, "456" } }.toString`, ["123", `{ 123, "456" }`]], + [`{ 123, { aaa: 123, bbb: "456" } }.toString`, ["123", `{ aaa: 123, bbb: "456" }`]], + [`#COLLECTION.join({ 123, 456 }, "|")`, ["123", "456"]], + ])("should parse list: %s => %s", (input, output) => { + expect(parser.parseList(input)).toEqual(output); + }); + + it.each([ + [`aaa`, null], + [`"aaa"`, null], + [`{}`, {}], + [`#AGG.map({})`, {}], + [`{aaa:123}`, { aaa: "123" }], + [`#AGG.map({aaa:123})`, { aaa: "123" }], + [`{ aaa: 123, bbb: "456" }`, { aaa: "123", bbb: `"456"` }], + [`{ a: 123, b c: 123 }`, { a: "123", "b c": "123" }], + [`{ aaa: 1 2 3 }`, { aaa: "1 2 3" }], + [`{ "a a a": 123 }`, { "a a a": "123" }], + [`{ aaa: #aaa.bbb }`, { aaa: "#aaa.bbb" }], + [`{ aaa: aaa.bbb }`, { aaa: "aaa.bbb" }], + [`{ aaa: "aaa bbb" }`, { aaa: `"aaa bbb"` }], + [`{ aaa: {} }`, { aaa: `{}` }], + [`{ aaa: { bbb: 123, ccc: "456" } }`, { aaa: `{ bbb: 123, ccc: "456" }` }], + [`{ aaa: { 123, "456" } }`, { aaa: `{ 123, "456" }` }], + [`{ aaa: "{ 123, '456' }" }`, { aaa: `"{ 123, '456' }"` }], + ])("should parse map: %s => %s", (input, output) => { + expect(parser.parseObject(input)).toEqual(output); + }); +}); diff --git a/designer/client/src/components/graph/node-modal/aggregate/useAggParamsSerializer.tsx b/designer/client/src/components/graph/node-modal/aggregate/useAggParamsSerializer.tsx index 3a154e7b7aa..44dc4b49a50 100644 --- a/designer/client/src/components/graph/node-modal/aggregate/useAggParamsSerializer.tsx +++ b/designer/client/src/components/graph/node-modal/aggregate/useAggParamsSerializer.tsx @@ -1,6 +1,6 @@ import { padStart } from "lodash"; import { useCallback, useMemo } from "react"; -import { AggMapLikeLexer, AggMapLikeParser } from "./aggMapLikeParser"; +import { AggMapLikeParser } from "./aggMapLikeParser"; export function useAggParamsSerializer(): [ (text: string) => Record, @@ -8,14 +8,7 @@ export function useAggParamsSerializer(): [ ] { const parser = useMemo(() => new AggMapLikeParser(), []); - const deserialize = useCallback( - (text: string): Record => { - const lexingResult = AggMapLikeLexer.tokenize(text); - parser.input = lexingResult.tokens; - return parser.object() || null; - }, - [parser], - ); + const deserialize = useCallback((input: string) => parser.parseObject(input), [parser]); const serialize = useCallback((paramName: string, map: Record): string => { const entries = Object.entries(map || {}).map(([key, value]) => { @@ -40,14 +33,7 @@ export function useAggParamsSerializer(): [ export function useGroupByParamsSerializer(): [(text: string) => string[], (paramName: string, arr: string[]) => string] { const parser = useMemo(() => new AggMapLikeParser(), []); - const deserialize = useCallback( - (text: string): string[] => { - const lexingResult = AggMapLikeLexer.tokenize(text); - parser.input = lexingResult.tokens; - return parser.collection() || null; - }, - [parser], - ); + const deserialize = useCallback((input: string) => parser.parseList(input), [parser]); const serialize = useCallback((paramName: string, arr: string[]): string => { const entries = arr.map((value) => { diff --git a/designer/client/src/components/toolbarSettings/DEV_TOOLBARS.ts b/designer/client/src/components/toolbarSettings/DEV_TOOLBARS.ts index c53c5ae94d8..f743c23f6e0 100644 --- a/designer/client/src/components/toolbarSettings/DEV_TOOLBARS.ts +++ b/designer/client/src/components/toolbarSettings/DEV_TOOLBARS.ts @@ -1,8 +1,4 @@ +import { isDev } from "../../devHelpers"; import { ToolbarConfig } from "./types"; -const isProd = process.env.NODE_ENV === "production"; -export const isVisualTesting = window["Cypress"]; - -const isDev = !isProd && !isVisualTesting; - export const DEV_TOOLBARS: ToolbarConfig[] = isDev ? [{ id: "user-settings-panel" }] : []; diff --git a/designer/client/src/devHelpers.ts b/designer/client/src/devHelpers.ts new file mode 100644 index 00000000000..02f1e6430fb --- /dev/null +++ b/designer/client/src/devHelpers.ts @@ -0,0 +1,11 @@ +export const isProd = process.env.NODE_ENV === "production"; +export const isVisualTesting = window["Cypress"]; +export const isDev = !isProd && !isVisualTesting; + +export function withLogs(fn: (...args: A) => R, message?: string): (...args: A) => R { + return function (...args) { + const result = fn(...args); + if (isDev) console.debug(message || fn.name, args, result); + return result; + }; +}