feat: peek overlay nested properties + perf improvements (#23414)
Fixes #23057 Fixes #23054 ## Description TL;DR Added support for peeking on nested properties. e.g. `Api1.data[0].id`. This won't work when: - local variables are involved in the expression. e.g. `Api1.data[x].id` won't support peeking at the variable `[x]` or anything after that. - library code is involved e.g. `moment`, `_` etc... - when functions are called. e.g. Api1.data[0].id.toFixed() Because these cases requires evaluation. <img width="355" alt="image" src="https://github.com/appsmithorg/appsmith/assets/66776129/d09d1f0d-1692-46f5-8ec1-592f4fe75f7a"> #### Media (old vs new) https://www.loom.com/share/dedcf113439c4ee2a19028acca54045e ## Performance improvements: - Use AST to identify expressions instead marking text manually. - This reduces the number of markers we process (~ half). - Before  - After  - AST logs https://www.loom.com/share/ddde93233cc8470ea04309d8a8332240 #### Type of change - Bug fix (non-breaking change which fixes an issue) - New feature (non-breaking change which adds functionality) ## Testing > #### How Has This Been Tested? - [x] Manual - [x] Jest - [x] Cypress > > #### Test Plan https://github.com/appsmithorg/TestSmith/issues/2402 #### Issues raised during DP testing https://github.com/appsmithorg/appsmith/pull/23414#issuecomment-1553164908 ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag #### QA activity: - [x] [Speedbreak features](https://github.com/appsmithorg/TestSmith/wiki/Test-plan-implementation#speedbreaker-features-to-consider-for-every-change) have been covered - [x] Test plan covers all impacted features and [areas of interest](https://github.com/appsmithorg/TestSmith/wiki/Guidelines-for-test-plans/_edit#areas-of-interest) - [ ] Test plan has been peer reviewed by project stakeholders and other QA members - [x] Manually tested functionality on DP - [ ] We had an implementation alignment call with stakeholders post QA Round 2 - [x] Cypress test cases have been added and approved by SDET/manual QA - [x] Added `Test Plan Approved` label after Cypress tests were reviewed - [ ] Added `Test Plan Approved` label after JUnit tests were reviewed
This commit is contained in:
parent
dcdc280750
commit
9dd015a1e6
|
|
@ -8,75 +8,49 @@ describe("Peek overlay", () => {
|
|||
_.apiPage.CreateAndFillApi(datasourceFormData["mockApiUrl"]);
|
||||
_.apiPage.RunAPI();
|
||||
_.apiPage.CreateAndFillApi(datasourceFormData["mockApiUrl"]);
|
||||
_.jsEditor.CreateJSObject(
|
||||
`export default {
|
||||
numArray: [1, 2, 3],
|
||||
objectArray: [ {x: 123}, { y: "123"} ],
|
||||
objectData: { x: 123, y: "123" },
|
||||
nullData: null,
|
||||
numberData: 1,
|
||||
myFun1: () => {
|
||||
// TODO: handle this keyword failure on CI tests
|
||||
JSObject1.numArray; JSObject1.objectData; JSObject1.nullData; JSObject1.numberData;
|
||||
Api1.run(); Api1.isLoading; Api2.data;
|
||||
appsmith.mode; appsmith.store.abc;
|
||||
Table1.pageNo; Table1.tableData;
|
||||
},
|
||||
myFun2: async () => {
|
||||
storeValue("abc", 123)
|
||||
return Api1.run()
|
||||
}
|
||||
}`,
|
||||
{
|
||||
paste: true,
|
||||
completeReplace: true,
|
||||
toRun: false,
|
||||
shouldCreateNewJSObj: true,
|
||||
lineNumber: 0,
|
||||
prettify: true,
|
||||
},
|
||||
);
|
||||
_.jsEditor.CreateJSObject(JsObjectContent, {
|
||||
paste: true,
|
||||
completeReplace: true,
|
||||
toRun: false,
|
||||
shouldCreateNewJSObj: true,
|
||||
lineNumber: 0,
|
||||
prettify: true,
|
||||
});
|
||||
_.jsEditor.SelectFunctionDropdown("myFun2");
|
||||
_.jsEditor.RunJSObj();
|
||||
_.agHelper.Sleep();
|
||||
_.debuggerHelper.CloseBottomBar();
|
||||
|
||||
// check number array
|
||||
_.peekOverlay.HoverCode("JSObject1.numArray");
|
||||
_.peekOverlay.HoverCode(8, 3, "numArray");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("array");
|
||||
_.peekOverlay.CheckPrimitveArrayInOverlay([1, 2, 3]);
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// check basic object
|
||||
_.peekOverlay.HoverCode("JSObject1.objectData");
|
||||
_.peekOverlay.HoverCode(9, 3, "objectData");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("object");
|
||||
_.peekOverlay.CheckBasicObjectInOverlay({ x: 123, y: "123" });
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// check null - with this keyword
|
||||
_.peekOverlay.HoverCode("JSObject1.nullData");
|
||||
_.peekOverlay.HoverCode(10, 3, "nullData");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("null");
|
||||
_.peekOverlay.CheckPrimitiveValue("null");
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// check number
|
||||
_.peekOverlay.HoverCode("JSObject1.numberData");
|
||||
_.peekOverlay.HoverCode(11, 3, "numberData");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("number");
|
||||
_.peekOverlay.CheckPrimitiveValue("1");
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// check undefined
|
||||
_.peekOverlay.HoverCode("Api2.data");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("undefined");
|
||||
_.peekOverlay.CheckPrimitiveValue("undefined");
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// check boolean
|
||||
_.peekOverlay.HoverCode("Api1.isLoading");
|
||||
_.peekOverlay.HoverCode(12, 3, "isLoading");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("boolean");
|
||||
_.peekOverlay.CheckPrimitiveValue("false");
|
||||
|
|
@ -84,40 +58,47 @@ describe("Peek overlay", () => {
|
|||
|
||||
// TODO: handle this function failure on CI tests -> "function(){}"
|
||||
// check function
|
||||
// _.peekOverlay.HoverCode("Api1.run");
|
||||
// _.peekOverlay.HoverCode(13, 3, "run");
|
||||
// _.peekOverlay.IsOverlayOpen();
|
||||
// _.peekOverlay.VerifyDataType("function");
|
||||
// _.peekOverlay.CheckPrimitiveValue("function () {}");
|
||||
// _.peekOverlay.ResetHover();
|
||||
|
||||
// check undefined
|
||||
_.peekOverlay.HoverCode(14, 3, "data");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("undefined");
|
||||
_.peekOverlay.CheckPrimitiveValue("undefined");
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// check string
|
||||
_.peekOverlay.HoverCode("appsmith.mode");
|
||||
_.peekOverlay.HoverCode(15, 3, "mode");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("string");
|
||||
_.peekOverlay.CheckPrimitiveValue("EDIT");
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// check if overlay closes
|
||||
_.peekOverlay.HoverCode("appsmith.store");
|
||||
_.peekOverlay.HoverCode(16, 3, "store");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.ResetHover();
|
||||
_.peekOverlay.IsOverlayOpen(false);
|
||||
|
||||
// widget object
|
||||
_.peekOverlay.HoverCode("Table1");
|
||||
_.peekOverlay.HoverCode(17, 1, "Table1");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("object");
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// widget property
|
||||
_.peekOverlay.HoverCode("Table1.pageNo");
|
||||
_.peekOverlay.HoverCode(18, 3, "pageNo");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("number");
|
||||
_.peekOverlay.CheckPrimitiveValue("1");
|
||||
_.peekOverlay.ResetHover();
|
||||
|
||||
// widget property
|
||||
_.peekOverlay.HoverCode("Table1.tableData");
|
||||
_.peekOverlay.HoverCode(19, 3, "tableData");
|
||||
_.peekOverlay.IsOverlayOpen();
|
||||
_.peekOverlay.VerifyDataType("array");
|
||||
_.peekOverlay.CheckObjectArrayInOverlay([{}, {}, {}]);
|
||||
|
|
@ -125,3 +106,31 @@ describe("Peek overlay", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
const JsObjectContent = `export default {
|
||||
numArray: [1, 2, 3],
|
||||
objectArray: [ {x: 123}, { y: "123"} ],
|
||||
objectData: { x: 123, y: "123" },
|
||||
nullData: null,
|
||||
numberData: 1,
|
||||
myFun1: () => {
|
||||
// TODO: handle this keyword failure on CI tests
|
||||
JSObject1.numArray;
|
||||
JSObject1.objectData;
|
||||
JSObject1.nullData;
|
||||
JSObject1.numberData;
|
||||
Api1.isLoading;
|
||||
Api1.run();
|
||||
Api2.data;
|
||||
appsmith.mode;
|
||||
appsmith.store.abc;
|
||||
Table1;
|
||||
Table1.pageNo;
|
||||
Table1.tableData;
|
||||
Api1.data.users[0].id;
|
||||
},
|
||||
myFun2: async () => {
|
||||
storeValue("abc", 123)
|
||||
return Api1.run()
|
||||
}
|
||||
}`;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { ObjectsRegistry } from "../Objects/Registry";
|
||||
|
||||
export class PeekOverlay {
|
||||
private readonly PEEKABLE_ATTRIBUTE = "peek-data";
|
||||
private readonly locators = {
|
||||
_overlayContainer: "#t--peek-overlay-container",
|
||||
_dataContainer: "#t--peek-overlay-data",
|
||||
_peekableCode: (peekableAttr: string) =>
|
||||
`[${this.PEEKABLE_ATTRIBUTE}="${peekableAttr}"]`,
|
||||
|
||||
// react json viewer selectors
|
||||
_rjv_variableValue: ".variable-value",
|
||||
|
|
@ -19,15 +16,18 @@ export class PeekOverlay {
|
|||
};
|
||||
private readonly agHelper = ObjectsRegistry.AggregateHelper;
|
||||
|
||||
HoverCode(peekableAttribute: string, visibleText?: string) {
|
||||
(visibleText
|
||||
? this.agHelper.GetNAssertContains(
|
||||
this.locators._peekableCode(peekableAttribute),
|
||||
visibleText,
|
||||
)
|
||||
: this.agHelper.GetElement(this.locators._peekableCode(peekableAttribute))
|
||||
).realHover();
|
||||
this.agHelper.Sleep();
|
||||
HoverCode(lineNumber: number, tokenNumber: number, verifyText: string) {
|
||||
this.agHelper
|
||||
.GetElement(".CodeMirror-line")
|
||||
.eq(lineNumber)
|
||||
.children()
|
||||
.children()
|
||||
.eq(tokenNumber)
|
||||
.should("have.text", verifyText)
|
||||
.then(($el) => {
|
||||
const pos = $el[0].getBoundingClientRect();
|
||||
this.HoverByPosition({ x: pos.left, y: pos.top });
|
||||
});
|
||||
}
|
||||
|
||||
IsOverlayOpen(checkIsOpen = true) {
|
||||
|
|
@ -36,8 +36,15 @@ export class PeekOverlay {
|
|||
: this.agHelper.AssertElementAbsence(this.locators._overlayContainer);
|
||||
}
|
||||
|
||||
HoverByPosition(position: { x: number; y: number }) {
|
||||
this.agHelper.GetElement("body").realHover({ position });
|
||||
this.agHelper.Sleep();
|
||||
}
|
||||
|
||||
ResetHover() {
|
||||
this.agHelper.GetElement("body").realHover({ position: "bottomLeft" });
|
||||
this.agHelper
|
||||
.GetElement(".CodeMirror-code")
|
||||
.realHover({ position: "bottomLeft" });
|
||||
this.agHelper.Sleep();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -282,10 +282,10 @@
|
|||
"cypress-multi-reporters": "^1.2.4",
|
||||
"cypress-network-idle": "^1.14.2",
|
||||
"cypress-plugin-tab": "^1.0.5",
|
||||
"cypress-real-events": "^1.7.1",
|
||||
"cypress-real-events": "^1.8.1",
|
||||
"cypress-tags": "^1.1.2",
|
||||
"cypress-wait-until": "^1.7.2",
|
||||
"cypress-xpath": "^1.4.0",
|
||||
"cypress-xpath": "^1.6.0",
|
||||
"diff": "^5.0.0",
|
||||
"dotenv": "^8.1.0",
|
||||
"eslint": "^8.36.0",
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ import {
|
|||
checkIfArgumentExistAtPosition,
|
||||
} from "./src/actionCreator";
|
||||
|
||||
// peekOverlay
|
||||
import type { PeekOverlayExpressionIdentifierOptions } from "./src/peekOverlay";
|
||||
import { PeekOverlayExpressionIdentifier } from "./src/peekOverlay";
|
||||
|
||||
// types or interfaces should be exported with type keyword, while enums can be exported like normal functions
|
||||
export type {
|
||||
ObjectExpression,
|
||||
|
|
@ -68,6 +72,7 @@ export type {
|
|||
IdentifierInfo,
|
||||
TParsedJSProperty,
|
||||
JSPropertyPosition,
|
||||
PeekOverlayExpressionIdentifierOptions,
|
||||
};
|
||||
|
||||
export {
|
||||
|
|
@ -118,4 +123,5 @@ export {
|
|||
checkIfArgumentExistAtPosition,
|
||||
isJSFunctionProperty,
|
||||
isFunctionPresent,
|
||||
PeekOverlayExpressionIdentifier,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export const ECMA_VERSION = 11;
|
||||
|
||||
/* Indicates the mode the code should be parsed in.
|
||||
/* Indicates the mode the code should be parsed in.
|
||||
This influences global strict mode and parsing of import and export declarations.
|
||||
*/
|
||||
export enum SourceType {
|
||||
|
|
@ -31,4 +31,6 @@ export enum NodeTypes {
|
|||
BinaryExpression = "BinaryExpression",
|
||||
ExpressionStatement = "ExpressionStatement",
|
||||
BlockStatement = "BlockStatement",
|
||||
ConditionalExpression = "ConditionalExpression",
|
||||
AwaitExpression = "AwaitExpression",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,6 +118,25 @@ export interface CallExpressionNode extends Node {
|
|||
arguments: ArgumentTypes[];
|
||||
}
|
||||
|
||||
// https://github.com/estree/estree/blob/master/es5.md#thisexpression
|
||||
export interface ThisExpressionNode extends Expression {
|
||||
type: "ThisExpression";
|
||||
}
|
||||
|
||||
// https://github.com/estree/estree/blob/master/es5.md#conditionalexpression
|
||||
export interface ConditionalExpressionNode extends Expression {
|
||||
type: "ConditionalExpression";
|
||||
test: Expression;
|
||||
alternate: Expression;
|
||||
consequent: Expression;
|
||||
}
|
||||
|
||||
// https://github.com/estree/estree/blob/master/es2017.md#awaitexpression
|
||||
export interface AwaitExpressionNode extends Expression {
|
||||
type: "AwaitExpression";
|
||||
argument: Expression;
|
||||
}
|
||||
|
||||
export interface BlockStatementNode extends Node {
|
||||
type: "BlockStatement";
|
||||
body: [Node];
|
||||
|
|
@ -182,6 +201,21 @@ export const isMemberExpressionNode = (
|
|||
return node.type === NodeTypes.MemberExpression;
|
||||
};
|
||||
|
||||
export const isThisExpressionNode = (
|
||||
node: Node,
|
||||
): node is ThisExpressionNode => {
|
||||
return node.type === NodeTypes.ThisExpression;
|
||||
};
|
||||
|
||||
export const isConditionalExpressionNode = (
|
||||
node: Node,
|
||||
): node is ConditionalExpressionNode =>
|
||||
node.type === NodeTypes.ConditionalExpression;
|
||||
|
||||
export const isAwaitExpressionNode = (
|
||||
node: Node,
|
||||
): node is AwaitExpressionNode => node.type === NodeTypes.AwaitExpression;
|
||||
|
||||
export const isBinaryExpressionNode = (
|
||||
node: Node,
|
||||
): node is BinaryExpressionNode => {
|
||||
|
|
|
|||
192
app/client/packages/ast/src/peekOverlay/index.test.ts
Normal file
192
app/client/packages/ast/src/peekOverlay/index.test.ts
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
import { SourceType } from "../constants/ast";
|
||||
import { PeekOverlayExpressionIdentifier } from "./index";
|
||||
|
||||
describe("extractExpressionAtPositionWholeDoc", () => {
|
||||
const scriptIdentifier = new PeekOverlayExpressionIdentifier({
|
||||
sourceType: SourceType.script,
|
||||
});
|
||||
|
||||
const jsObjectIdentifier = new PeekOverlayExpressionIdentifier({
|
||||
sourceType: SourceType.module,
|
||||
thisExpressionReplacement: "JsObject",
|
||||
});
|
||||
|
||||
const checkExpressionAtScript = async (
|
||||
pos: number,
|
||||
resultString?: string,
|
||||
) => {
|
||||
let result;
|
||||
try {
|
||||
result = await scriptIdentifier.extractExpressionAtPosition(pos);
|
||||
} catch (e) {
|
||||
expect(e).toBe(
|
||||
"PeekOverlayExpressionIdentifier - No expression found at position",
|
||||
);
|
||||
}
|
||||
expect(result).toBe(resultString);
|
||||
};
|
||||
|
||||
const checkExpressionAtJsObject = async (
|
||||
pos: number,
|
||||
resultString?: string,
|
||||
) => {
|
||||
let result;
|
||||
try {
|
||||
result = await jsObjectIdentifier.extractExpressionAtPosition(pos);
|
||||
} catch (e) {
|
||||
expect(e).toBe(
|
||||
"PeekOverlayExpressionIdentifier - No expression found at position",
|
||||
);
|
||||
}
|
||||
expect(result).toBe(resultString);
|
||||
};
|
||||
|
||||
it("handles MemberExpressions", async () => {
|
||||
// nested properties
|
||||
scriptIdentifier.updateScript("Api1.data[0].id");
|
||||
// at position 'A'
|
||||
// 'A'pi1.data[0].id
|
||||
checkExpressionAtScript(0, "Api1");
|
||||
// Ap'i'1.data[0].id
|
||||
checkExpressionAtScript(2, "Api1");
|
||||
// Api1.'d'ata[0].id
|
||||
checkExpressionAtScript(6, "Api1.data");
|
||||
// Api1.data['0'].id
|
||||
checkExpressionAtScript(11, "Api1.data[0]");
|
||||
// Api1.data[0].i'd'
|
||||
checkExpressionAtScript(14, "Api1.data[0].id");
|
||||
|
||||
// function call
|
||||
// argument hover - Api1
|
||||
scriptIdentifier.updateScript(`storeValue("abc", Api1.run)`);
|
||||
checkExpressionAtScript(18, "Api1");
|
||||
scriptIdentifier.updateScript("Api1.check.run()");
|
||||
// Ap'i'1.check.run()
|
||||
checkExpressionAtScript(2, "Api1");
|
||||
// Api1.check.'r'un()
|
||||
checkExpressionAtScript(12, "Api1.check.run");
|
||||
|
||||
// local varibles are filtered
|
||||
scriptIdentifier.updateScript("Api1.check.data[x].id");
|
||||
// Api1
|
||||
checkExpressionAtScript(2, "Api1");
|
||||
// check
|
||||
checkExpressionAtScript(7, "Api1.check");
|
||||
// data
|
||||
checkExpressionAtScript(12, "Api1.check.data");
|
||||
// x
|
||||
checkExpressionAtScript(16);
|
||||
// id
|
||||
checkExpressionAtScript(19);
|
||||
});
|
||||
|
||||
it("handles ExpressionStatements", async () => {
|
||||
// simple statement
|
||||
scriptIdentifier.updateScript("Api1");
|
||||
// Ap'i'1
|
||||
checkExpressionAtScript(2, "Api1");
|
||||
|
||||
// function call
|
||||
scriptIdentifier.updateScript(`storeValue("abc", 123)`);
|
||||
// storeValue("a'b'c", 123)
|
||||
checkExpressionAtScript(13);
|
||||
// st'o'reValue("abc", 123) - functionality not supported now
|
||||
checkExpressionAtScript(2);
|
||||
|
||||
// consequent function calls
|
||||
scriptIdentifier.updateScript(`Api1.data[0].id.toFixed().toString()`);
|
||||
// toFixed
|
||||
checkExpressionAtScript(16, "Api1.data[0].id.toFixed");
|
||||
// toString
|
||||
checkExpressionAtScript(26);
|
||||
|
||||
// function call argument hover
|
||||
scriptIdentifier.updateScript(`storeValue("abc", Api1)`);
|
||||
checkExpressionAtScript(18, "Api1");
|
||||
});
|
||||
|
||||
it("handles BinaryExpressions", async () => {
|
||||
// binary expression
|
||||
scriptIdentifier.updateScript(
|
||||
`Api1.data.users[0].id === "myData test" ? "Yes" : "No"`,
|
||||
);
|
||||
|
||||
// id
|
||||
checkExpressionAtScript(19, "Api1.data.users[0].id");
|
||||
// myData
|
||||
checkExpressionAtScript(27);
|
||||
// ?
|
||||
checkExpressionAtScript(40);
|
||||
// Yes
|
||||
checkExpressionAtScript(43);
|
||||
// :
|
||||
checkExpressionAtScript(48);
|
||||
// No
|
||||
checkExpressionAtScript(51);
|
||||
|
||||
// hardcoded LHS
|
||||
scriptIdentifier.updateScript(`"sample" === "myData test" ? "Yes" : "No"`);
|
||||
// sample
|
||||
checkExpressionAtScript(1);
|
||||
|
||||
// nested expressions
|
||||
scriptIdentifier.updateScript(
|
||||
`"sample" === "myData test" ? "nested" === "nested check" ? "Yes" : "No" : "No"`,
|
||||
);
|
||||
// nested
|
||||
checkExpressionAtScript(31);
|
||||
// nested check
|
||||
checkExpressionAtScript(44);
|
||||
// Yes
|
||||
checkExpressionAtScript(61);
|
||||
// No
|
||||
checkExpressionAtScript(69);
|
||||
});
|
||||
|
||||
it("handles JsObject cases", async () => {
|
||||
jsObjectIdentifier.updateScript(JsObjectWithThisKeyword);
|
||||
|
||||
// this keyword cases
|
||||
// this
|
||||
checkExpressionAtJsObject(140, "JsObject");
|
||||
// numArray
|
||||
checkExpressionAtJsObject(159, "JsObject.numArray");
|
||||
// objectArray
|
||||
checkExpressionAtJsObject(180, "JsObject.objectArray");
|
||||
// [0]
|
||||
checkExpressionAtJsObject(183, "JsObject.objectArray[0]");
|
||||
// x
|
||||
checkExpressionAtJsObject(186, "JsObject.objectArray[0].x");
|
||||
// 'x'
|
||||
checkExpressionAtJsObject(208, "JsObject.objectData['x']");
|
||||
// 'a'
|
||||
checkExpressionAtJsObject(238, "JsObject.objectData['x']['a']");
|
||||
// b
|
||||
checkExpressionAtJsObject(243, "JsObject.objectData['x']['a'].b");
|
||||
|
||||
// await keyword cases
|
||||
// resetWidget
|
||||
checkExpressionAtJsObject(255);
|
||||
// "Switch1"
|
||||
checkExpressionAtJsObject(266);
|
||||
// Api1
|
||||
checkExpressionAtJsObject(287, "Api1");
|
||||
// run
|
||||
checkExpressionAtJsObject(292, "Api1.run");
|
||||
});
|
||||
});
|
||||
|
||||
const JsObjectWithThisKeyword = `export default {
|
||||
numArray: [1, 2, 3],
|
||||
objectArray: [ {x: 123}, { y: "123"} ],
|
||||
objectData: { x: 123, y: "123" },
|
||||
myFun1: async () => {
|
||||
this;
|
||||
this.numArray;
|
||||
this.objectArray[0].x;
|
||||
this.objectData["x"];
|
||||
this.objectData["x"]["a"].b;
|
||||
await resetWidget("Switch1");
|
||||
await Api1.run();
|
||||
},
|
||||
}`;
|
||||
83
app/client/packages/ast/src/peekOverlay/index.ts
Normal file
83
app/client/packages/ast/src/peekOverlay/index.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import type { Node } from "acorn";
|
||||
import { parse } from "acorn";
|
||||
import { simple } from "acorn-walk";
|
||||
import type { SourceType } from "../constants/ast";
|
||||
import { ECMA_VERSION } from "../constants/ast";
|
||||
import { getExpressionStringAtPos, isPositionWithinNode } from "./utils";
|
||||
|
||||
export class PeekOverlayExpressionIdentifier {
|
||||
private parsedScript?: Node;
|
||||
private options: PeekOverlayExpressionIdentifierOptions;
|
||||
|
||||
constructor(
|
||||
options: PeekOverlayExpressionIdentifierOptions,
|
||||
script?: string,
|
||||
) {
|
||||
this.options = options;
|
||||
if (script) this.updateScript(script);
|
||||
}
|
||||
|
||||
hasParsedScript() {
|
||||
return !!this.parsedScript;
|
||||
}
|
||||
|
||||
updateScript(script: string) {
|
||||
try {
|
||||
this.parsedScript = parse(script, {
|
||||
ecmaVersion: ECMA_VERSION,
|
||||
sourceType: this.options.sourceType,
|
||||
});
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
clearScript() {
|
||||
this.parsedScript = undefined;
|
||||
}
|
||||
|
||||
extractExpressionAtPosition(pos: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.parsedScript) {
|
||||
throw "PeekOverlayExpressionIdentifier - No valid script found";
|
||||
}
|
||||
|
||||
let nodeFound: Node | undefined;
|
||||
|
||||
simple(this.parsedScript, {
|
||||
MemberExpression(node: Node) {
|
||||
if (!nodeFound && isPositionWithinNode(node, pos)) {
|
||||
nodeFound = node;
|
||||
}
|
||||
},
|
||||
ExpressionStatement(node: Node) {
|
||||
if (!nodeFound && isPositionWithinNode(node, pos)) {
|
||||
nodeFound = node;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (nodeFound) {
|
||||
const expressionFound = getExpressionStringAtPos(
|
||||
nodeFound,
|
||||
pos,
|
||||
this.options,
|
||||
);
|
||||
if (expressionFound) {
|
||||
resolve(expressionFound);
|
||||
} else {
|
||||
reject(
|
||||
"PeekOverlayExpressionIdentifier - No expression found at position",
|
||||
);
|
||||
}
|
||||
}
|
||||
reject("PeekOverlayExpressionIdentifier - No node found");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type PeekOverlayExpressionIdentifierOptions = {
|
||||
sourceType: SourceType;
|
||||
thisExpressionReplacement?: string;
|
||||
};
|
||||
184
app/client/packages/ast/src/peekOverlay/utils.ts
Normal file
184
app/client/packages/ast/src/peekOverlay/utils.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import type { Node } from "acorn";
|
||||
import type {
|
||||
BinaryExpressionNode,
|
||||
CallExpressionNode,
|
||||
ConditionalExpressionNode,
|
||||
ExpressionStatement,
|
||||
IdentifierNode,
|
||||
MemberExpressionNode,
|
||||
} from "../index";
|
||||
import { isAwaitExpressionNode } from "../index";
|
||||
import { isBinaryExpressionNode } from "../index";
|
||||
import { isConditionalExpressionNode } from "../index";
|
||||
import {
|
||||
isCallExpressionNode,
|
||||
isExpressionStatementNode,
|
||||
isIdentifierNode,
|
||||
isMemberExpressionNode,
|
||||
isThisExpressionNode,
|
||||
} from "../index";
|
||||
import * as escodegen from "escodegen";
|
||||
import { NodeTypes } from "../constants/ast";
|
||||
import type { PeekOverlayExpressionIdentifierOptions } from "./index";
|
||||
|
||||
export const isPositionWithinNode = (node: Node, pos: number) =>
|
||||
pos >= node.start && pos <= node.end;
|
||||
|
||||
export const getExpressionStringAtPos = (
|
||||
node: Node,
|
||||
pos: number,
|
||||
options?: PeekOverlayExpressionIdentifierOptions,
|
||||
replaceThisExpression = true,
|
||||
): string | undefined => {
|
||||
if (!isPositionWithinNode(node, pos)) return;
|
||||
if (isMemberExpressionNode(node)) {
|
||||
return getExpressionAtPosFromMemberExpression(
|
||||
node,
|
||||
pos,
|
||||
options,
|
||||
replaceThisExpression,
|
||||
);
|
||||
} else if (isExpressionStatementNode(node)) {
|
||||
return getExpressionAtPosFromExpressionStatement(node, pos, options);
|
||||
} else if (isCallExpressionNode(node)) {
|
||||
return getExpressionAtPosFromCallExpression(node, pos, options);
|
||||
} else if (isBinaryExpressionNode(node)) {
|
||||
return getExpressionAtPosFromBinaryExpression(node, pos, options);
|
||||
} else if (isAwaitExpressionNode(node)) {
|
||||
return getExpressionStringAtPos(node.argument, pos, options);
|
||||
} else if (isConditionalExpressionNode(node)) {
|
||||
return getExpressionAtPosFromConditionalExpression(node, pos, options);
|
||||
} else if (isIdentifierNode(node)) {
|
||||
return removeSemiColon(escodegen.generate(node));
|
||||
}
|
||||
};
|
||||
|
||||
const getExpressionAtPosFromMemberExpression = (
|
||||
node: MemberExpressionNode,
|
||||
pos: number,
|
||||
options?: PeekOverlayExpressionIdentifierOptions,
|
||||
replaceThisExpression = true,
|
||||
): string | undefined => {
|
||||
const objectNode = node.object;
|
||||
if (isLocalVariableNode(node) || isLocalVariableNode(objectNode)) return;
|
||||
if (replaceThisExpression && options?.thisExpressionReplacement) {
|
||||
node = replaceThisinMemberExpression(node, options);
|
||||
}
|
||||
// stop if objectNode is a function call -> needs evaluation
|
||||
if (isCallExpressionNode(objectNode)) return;
|
||||
// position is within the object node
|
||||
if (pos <= objectNode.end) {
|
||||
return getExpressionStringAtPos(objectNode, pos, options, false);
|
||||
}
|
||||
// position is within the property node
|
||||
else {
|
||||
const propertyNode = node.property;
|
||||
if (isMemberExpressionNode(propertyNode)) {
|
||||
return getExpressionAtPosFromMemberExpression(
|
||||
propertyNode,
|
||||
pos,
|
||||
options,
|
||||
false,
|
||||
);
|
||||
}
|
||||
// generate string for the whole path
|
||||
return escodegen.generate(node);
|
||||
}
|
||||
};
|
||||
|
||||
const getExpressionAtPosFromExpressionStatement = (
|
||||
node: ExpressionStatement,
|
||||
pos: number,
|
||||
options?: PeekOverlayExpressionIdentifierOptions,
|
||||
): string | undefined => {
|
||||
if (
|
||||
isThisExpressionNode(node.expression) &&
|
||||
options?.thisExpressionReplacement
|
||||
) {
|
||||
node.expression = thisReplacementNode(node.expression, options);
|
||||
}
|
||||
return getExpressionStringAtPos(node.expression, pos, options);
|
||||
};
|
||||
|
||||
const getExpressionAtPosFromCallExpression = (
|
||||
node: CallExpressionNode,
|
||||
pos: number,
|
||||
options?: PeekOverlayExpressionIdentifierOptions,
|
||||
): string | undefined => {
|
||||
let selectedNode: Node | undefined;
|
||||
// function call -> needs evaluation
|
||||
// if (isPositionWithinNode(node.callee, pos)) {
|
||||
// selectedNode = node.callee;
|
||||
// }
|
||||
if (node.arguments.length > 0) {
|
||||
const argumentNode = node.arguments.find((node) =>
|
||||
isPositionWithinNode(node, pos),
|
||||
);
|
||||
if (argumentNode) {
|
||||
selectedNode = argumentNode;
|
||||
}
|
||||
}
|
||||
return selectedNode && getExpressionStringAtPos(selectedNode, pos, options);
|
||||
};
|
||||
|
||||
const getExpressionAtPosFromConditionalExpression = (
|
||||
node: ConditionalExpressionNode,
|
||||
pos: number,
|
||||
options?: PeekOverlayExpressionIdentifierOptions,
|
||||
): string | undefined => {
|
||||
let selectedNode: Node | undefined;
|
||||
if (isPositionWithinNode(node.test, pos)) {
|
||||
selectedNode = node.test;
|
||||
} else if (isPositionWithinNode(node.consequent, pos)) {
|
||||
selectedNode = node.consequent;
|
||||
} else if (isPositionWithinNode(node.alternate, pos)) {
|
||||
selectedNode = node.alternate;
|
||||
}
|
||||
return selectedNode && getExpressionStringAtPos(selectedNode, pos, options);
|
||||
};
|
||||
|
||||
const getExpressionAtPosFromBinaryExpression = (
|
||||
node: BinaryExpressionNode,
|
||||
pos: number,
|
||||
options?: PeekOverlayExpressionIdentifierOptions,
|
||||
): string | undefined => {
|
||||
let selectedNode: Node | undefined;
|
||||
if (isPositionWithinNode(node.left, pos)) {
|
||||
selectedNode = node.left;
|
||||
} else if (isPositionWithinNode(node.right, pos)) {
|
||||
selectedNode = node.right;
|
||||
}
|
||||
return selectedNode && getExpressionStringAtPos(selectedNode, pos, options);
|
||||
};
|
||||
|
||||
export const replaceThisinMemberExpression = (
|
||||
node: MemberExpressionNode,
|
||||
options: PeekOverlayExpressionIdentifierOptions,
|
||||
): MemberExpressionNode => {
|
||||
if (isMemberExpressionNode(node.object)) {
|
||||
node.object = replaceThisinMemberExpression(node.object, options);
|
||||
} else if (isThisExpressionNode(node.object)) {
|
||||
node.object = thisReplacementNode(node.object, options);
|
||||
}
|
||||
return node;
|
||||
};
|
||||
|
||||
// replace "this" node with the provided replacement
|
||||
const thisReplacementNode = (
|
||||
node: Node,
|
||||
options: PeekOverlayExpressionIdentifierOptions,
|
||||
) => {
|
||||
return {
|
||||
...node,
|
||||
type: NodeTypes.Identifier,
|
||||
name: options.thisExpressionReplacement,
|
||||
} as IdentifierNode;
|
||||
};
|
||||
|
||||
const removeSemiColon = (value: string) =>
|
||||
value.slice(-1) === ";" ? value.slice(0, value.length - 1) : value;
|
||||
|
||||
const isLocalVariableNode = (node: Node) =>
|
||||
isMemberExpressionNode(node) &&
|
||||
node.computed &&
|
||||
isIdentifierNode(node.property);
|
||||
|
|
@ -12,13 +12,6 @@ const hasReference = (token: CodeMirror.Token) => {
|
|||
return token.type === "variable" || tokenString === "this";
|
||||
};
|
||||
|
||||
export const PEEKABLE_CLASSNAME = "peekable-entity-highlight";
|
||||
export const PEEKABLE_ATTRIBUTE = "peek-data";
|
||||
export const PEEKABLE_LINE = "peek-line";
|
||||
export const PEEKABLE_CH_START = "peek-ch-start";
|
||||
export const PEEKABLE_CH_END = "peek-ch-end";
|
||||
export const PEEK_STYLE_PERSIST_CLASS = "peek-style-persist";
|
||||
|
||||
export const entityMarker: MarkHelper = (
|
||||
editor: CodeMirror.Editor,
|
||||
entityNavigationData,
|
||||
|
|
@ -66,11 +59,11 @@ const addMarksForLine = (
|
|||
const tokenString = token.string;
|
||||
if (hasReference(token) && tokenString in entityNavigationData) {
|
||||
const data = entityNavigationData[tokenString];
|
||||
if (data.navigable || data.peekable) {
|
||||
if (data.navigable) {
|
||||
editor.markText(
|
||||
{ ch: token.start, line: lineNo },
|
||||
{ ch: token.end, line: lineNo },
|
||||
getMarkOptions(data, token, lineNo),
|
||||
getMarkOptions(data),
|
||||
);
|
||||
}
|
||||
addMarksForChildren(
|
||||
|
|
@ -100,11 +93,11 @@ const addMarksForChildren = (
|
|||
);
|
||||
if (token.string in childNodes) {
|
||||
const childLink = childNodes[token.string];
|
||||
if (childLink.navigable || childLink.peekable) {
|
||||
if (childLink.navigable) {
|
||||
editor.markText(
|
||||
{ ch: token.start, line: lineNo },
|
||||
{ ch: token.end, line: lineNo },
|
||||
getMarkOptions(childLink, token, lineNo),
|
||||
getMarkOptions(childLink),
|
||||
);
|
||||
}
|
||||
addMarksForChildren(childNodes[token.string], lineNo, token.end, editor);
|
||||
|
|
@ -112,27 +105,15 @@ const addMarksForChildren = (
|
|||
}
|
||||
};
|
||||
|
||||
const getMarkOptions = (
|
||||
data: NavigationData,
|
||||
token: CodeMirror.Token,
|
||||
lineNo: number,
|
||||
): CodeMirror.TextMarkerOptions => {
|
||||
const getMarkOptions = (data: NavigationData): CodeMirror.TextMarkerOptions => {
|
||||
return {
|
||||
className: `${data.navigable ? NAVIGATION_CLASSNAME : ""} ${
|
||||
data.peekable ? PEEKABLE_CLASSNAME : ""
|
||||
}`,
|
||||
className: `${data.navigable ? NAVIGATION_CLASSNAME : ""}`,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
attributes: {
|
||||
...(data.navigable && {
|
||||
[NAVIGATE_TO_ATTRIBUTE]: `${data.name}`,
|
||||
}),
|
||||
...(data.peekable && {
|
||||
[PEEKABLE_ATTRIBUTE]: data.name,
|
||||
[PEEKABLE_CH_START]: token.start,
|
||||
[PEEKABLE_CH_END]: token.end,
|
||||
[PEEKABLE_LINE]: lineNo,
|
||||
}),
|
||||
},
|
||||
atomic: false,
|
||||
title: data.name,
|
||||
|
|
@ -141,10 +122,6 @@ const getMarkOptions = (
|
|||
|
||||
const clearMarkers = (markers: CodeMirror.TextMarker[]) => {
|
||||
markers.forEach((marker) => {
|
||||
if (
|
||||
marker.className?.includes(NAVIGATION_CLASSNAME) ||
|
||||
marker.className?.includes(PEEKABLE_CLASSNAME)
|
||||
)
|
||||
marker.clear();
|
||||
if (marker.className?.includes(NAVIGATION_CLASSNAME)) marker.clear();
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
import type { MutableRefObject } from "react";
|
||||
import { useState } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import ReactJson from "react-json-view";
|
||||
import { JsonWrapper, reactJsonProps } from "./JsonWrapper";
|
||||
import { componentWillAppendToBody } from "react-append-to-body";
|
||||
import { debounce } from "lodash";
|
||||
import _, { debounce } from "lodash";
|
||||
import { zIndexLayers } from "constants/CanvasEditorConstants";
|
||||
import { objectCollapseAnalytics, textSelectAnalytics } from "./Analytics";
|
||||
import { Divider } from "design-system";
|
||||
import { useSelector } from "react-redux";
|
||||
import { getDataTree } from "selectors/dataTreeSelectors";
|
||||
import { filterInternalProperties } from "utils/FilterInternalProperties";
|
||||
import { getJSCollections } from "selectors/entitiesSelector";
|
||||
|
||||
export type PeekOverlayStateProps = {
|
||||
name: string;
|
||||
objectName: string;
|
||||
propertyPath: string[];
|
||||
position: DOMRect;
|
||||
textWidth: number;
|
||||
data: unknown;
|
||||
dataType: string;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
@ -29,6 +33,19 @@ export const PeekOverlayPopUp = componentWillAppendToBody(
|
|||
|
||||
export const PEEK_OVERLAY_DELAY = 200;
|
||||
|
||||
const getPropertyData = (src: unknown, propertyPath: string[]) => {
|
||||
return propertyPath.length > 0 ? _.get(src, propertyPath) : src;
|
||||
};
|
||||
|
||||
const getDataTypeHeader = (data: unknown) => {
|
||||
const dataType = typeof data;
|
||||
if (dataType === "object") {
|
||||
if (Array.isArray(data)) return "array";
|
||||
if (data === null) return "null";
|
||||
}
|
||||
return dataType;
|
||||
};
|
||||
|
||||
export function PeekOverlayPopUpContent(
|
||||
props: PeekOverlayStateProps & {
|
||||
hidePeekOverlay: () => void;
|
||||
|
|
@ -36,6 +53,23 @@ export function PeekOverlayPopUpContent(
|
|||
) {
|
||||
const CONTAINER_MAX_HEIGHT_PX = 252;
|
||||
const dataWrapperRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
|
||||
const dataTree = useSelector(getDataTree);
|
||||
const jsActions = useSelector(getJSCollections);
|
||||
|
||||
const filteredData = filterInternalProperties(
|
||||
props.objectName,
|
||||
dataTree[props.objectName],
|
||||
jsActions,
|
||||
dataTree,
|
||||
);
|
||||
|
||||
// Because getPropertyData can return a function
|
||||
// And we don't want to execute it.
|
||||
const [jsData] = useState(() =>
|
||||
getPropertyData(filteredData, props.propertyPath),
|
||||
);
|
||||
|
||||
const [dataType] = useState(getDataTypeHeader(jsData));
|
||||
|
||||
useEffect(() => {
|
||||
const wheelCallback = () => {
|
||||
|
|
@ -60,14 +94,6 @@ export function PeekOverlayPopUpContent(
|
|||
PEEK_OVERLAY_DELAY,
|
||||
);
|
||||
|
||||
const getDataTypeHeader = (dataType: string) => {
|
||||
if (props.dataType === "object") {
|
||||
if (Array.isArray(props.data)) return "array";
|
||||
if (props.data === null) return "null";
|
||||
}
|
||||
return dataType;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute ${zIndexLayers.PEEK_OVERLAY}`}
|
||||
|
|
@ -101,7 +127,7 @@ export function PeekOverlayPopUpContent(
|
|||
fontSize: "10px",
|
||||
}}
|
||||
>
|
||||
{getDataTypeHeader(props.dataType)}
|
||||
{dataType}
|
||||
</div>
|
||||
<Divider style={{ display: "block" }} />
|
||||
<div
|
||||
|
|
@ -113,7 +139,7 @@ export function PeekOverlayPopUpContent(
|
|||
fontSize: "10px",
|
||||
}}
|
||||
>
|
||||
{props.dataType === "object" && props.data !== null && (
|
||||
{(dataType === "object" || dataType === "array") && jsData !== null && (
|
||||
<JsonWrapper
|
||||
onClick={objectCollapseAnalytics}
|
||||
style={{
|
||||
|
|
@ -122,31 +148,22 @@ export function PeekOverlayPopUpContent(
|
|||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
<ReactJson src={props.data} {...reactJsonProps} />
|
||||
<ReactJson src={jsData} {...reactJsonProps} />
|
||||
</JsonWrapper>
|
||||
)}
|
||||
{props.dataType === "function" && (
|
||||
<div>{(props.data as any).toString()}</div>
|
||||
)}
|
||||
{props.dataType === "boolean" && (
|
||||
<div>{(props.data as any).toString()}</div>
|
||||
)}
|
||||
{props.dataType === "string" && (
|
||||
<div>{(props.data as any).toString()}</div>
|
||||
)}
|
||||
{props.dataType === "number" && (
|
||||
<div>{(props.data as any).toString()}</div>
|
||||
)}
|
||||
{((props.dataType !== "object" &&
|
||||
props.dataType !== "function" &&
|
||||
props.dataType !== "boolean" &&
|
||||
props.dataType !== "string" &&
|
||||
props.dataType !== "number") ||
|
||||
props.data === null) && (
|
||||
{dataType === "function" && <div>{(jsData as any).toString()}</div>}
|
||||
{dataType === "boolean" && <div>{(jsData as any).toString()}</div>}
|
||||
{dataType === "string" && <div>{(jsData as any).toString()}</div>}
|
||||
{dataType === "number" && <div>{(jsData as any).toString()}</div>}
|
||||
{((dataType !== "object" &&
|
||||
dataType !== "function" &&
|
||||
dataType !== "boolean" &&
|
||||
dataType !== "string" &&
|
||||
dataType !== "array" &&
|
||||
dataType !== "number") ||
|
||||
jsData === null) && (
|
||||
<div>
|
||||
{(props.data as any)?.toString() ??
|
||||
props.data ??
|
||||
props.data === undefined
|
||||
{(jsData as any)?.toString() ?? jsData ?? jsData === undefined
|
||||
? "undefined"
|
||||
: "null"}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -60,16 +60,12 @@ import {
|
|||
DynamicAutocompleteInputWrapper,
|
||||
EditorWrapper,
|
||||
IconContainer,
|
||||
PEEK_STYLE_PERSIST_CLASS,
|
||||
} from "components/editorComponents/CodeEditor/styledComponents";
|
||||
import { bindingMarker } from "components/editorComponents/CodeEditor/MarkHelpers/bindingMarker";
|
||||
import {
|
||||
entityMarker,
|
||||
NAVIGATE_TO_ATTRIBUTE,
|
||||
PEEKABLE_ATTRIBUTE,
|
||||
PEEKABLE_CH_END,
|
||||
PEEKABLE_CH_START,
|
||||
PEEKABLE_LINE,
|
||||
PEEK_STYLE_PERSIST_CLASS,
|
||||
} from "components/editorComponents/CodeEditor/MarkHelpers/entityMarker";
|
||||
import {
|
||||
bindingHint,
|
||||
|
|
@ -153,7 +149,14 @@ import {
|
|||
APPSMITH_AI,
|
||||
askAIEnabled,
|
||||
} from "@appsmith/components/editorComponents/GPT/trigger";
|
||||
import { getAllDatasourceTableKeys } from "selectors/entitiesSelector";
|
||||
import {
|
||||
getAllDatasourceTableKeys,
|
||||
selectInstalledLibraries,
|
||||
} from "selectors/entitiesSelector";
|
||||
import { debug } from "loglevel";
|
||||
import { PeekOverlayExpressionIdentifier, SourceType } from "@shared/ast";
|
||||
import type { MultiplexingModeConfig } from "components/editorComponents/CodeEditor/modes";
|
||||
import { MULTIPLEXING_MODE_CONFIGS } from "components/editorComponents/CodeEditor/modes";
|
||||
|
||||
type ReduxStateProps = ReturnType<typeof mapStateToProps>;
|
||||
type ReduxDispatchProps = ReturnType<typeof mapDispatchToProps>;
|
||||
|
|
@ -229,6 +232,7 @@ export type EditorProps = EditorStyleProps &
|
|||
isReadOnly?: boolean;
|
||||
isRawView?: boolean;
|
||||
isJSObject?: boolean;
|
||||
jsObjectName?: string;
|
||||
containerHeight?: number;
|
||||
// Custom gutter
|
||||
customGutter?: CodeEditorGutter;
|
||||
|
|
@ -253,7 +257,7 @@ type State = {
|
|||
ctrlPressed: boolean;
|
||||
peekOverlayProps:
|
||||
| (PeekOverlayStateProps & {
|
||||
marker?: CodeMirror.TextMarker;
|
||||
tokenElement: Element;
|
||||
})
|
||||
| undefined;
|
||||
isDynamic: boolean;
|
||||
|
|
@ -278,9 +282,11 @@ class CodeEditor extends Component<Props, State> {
|
|||
hinters: Hinter[] = [];
|
||||
annotations: Annotation[] = [];
|
||||
updateLintingCallback: UpdateLintingCallback | undefined;
|
||||
private peekOverlayExpressionIdentifier: PeekOverlayExpressionIdentifier;
|
||||
private editorWrapperRef = React.createRef<HTMLDivElement>();
|
||||
currentLineNumber: number | null = null;
|
||||
AIEnabled = false;
|
||||
private multiplexConfig?: MultiplexingModeConfig;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
|
@ -296,6 +302,18 @@ class CodeEditor extends Component<Props, State> {
|
|||
showAIWindow: false,
|
||||
};
|
||||
this.updatePropertyValue = this.updatePropertyValue.bind(this);
|
||||
this.peekOverlayExpressionIdentifier = new PeekOverlayExpressionIdentifier(
|
||||
props.isJSObject
|
||||
? {
|
||||
sourceType: SourceType.module,
|
||||
thisExpressionReplacement: props.jsObjectName,
|
||||
}
|
||||
: {
|
||||
sourceType: SourceType.script,
|
||||
},
|
||||
props.input.value,
|
||||
);
|
||||
this.multiplexConfig = MULTIPLEXING_MODE_CONFIGS[this.props.mode];
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
|
|
@ -577,48 +595,40 @@ class CodeEditor extends Component<Props, State> {
|
|||
};
|
||||
|
||||
showPeekOverlay = (
|
||||
peekableAttribute: string,
|
||||
expression: string,
|
||||
paths: string[],
|
||||
tokenElement: Element,
|
||||
tokenElementPosition: DOMRect,
|
||||
dataToShow: unknown,
|
||||
) => {
|
||||
const line = tokenElement.getAttribute(PEEKABLE_LINE),
|
||||
chStart = tokenElement.getAttribute(PEEKABLE_CH_START),
|
||||
chEnd = tokenElement.getAttribute(PEEKABLE_CH_END);
|
||||
|
||||
this.state.peekOverlayProps?.marker?.clear();
|
||||
let marker: CodeMirror.TextMarker | undefined;
|
||||
if (line && chStart && chEnd) {
|
||||
marker = this.editor.markText(
|
||||
{ ch: Number(chStart), line: Number(line) },
|
||||
{ ch: Number(chEnd), line: Number(line) },
|
||||
{
|
||||
className: PEEK_STYLE_PERSIST_CLASS,
|
||||
},
|
||||
);
|
||||
const tokenElementPosition = tokenElement.getBoundingClientRect();
|
||||
if (this.state.peekOverlayProps) {
|
||||
if (tokenElement === this.state.peekOverlayProps.tokenElement) return;
|
||||
this.hidePeekOverlay();
|
||||
}
|
||||
|
||||
tokenElement.classList.add(PEEK_STYLE_PERSIST_CLASS);
|
||||
this.setState({
|
||||
peekOverlayProps: {
|
||||
name: peekableAttribute,
|
||||
objectName: paths[0],
|
||||
propertyPath: paths.slice(1),
|
||||
position: tokenElementPosition,
|
||||
tokenElement,
|
||||
textWidth: tokenElementPosition.width,
|
||||
marker,
|
||||
data: dataToShow,
|
||||
dataType: typeof dataToShow,
|
||||
},
|
||||
});
|
||||
|
||||
AnalyticsUtil.logEvent("PEEK_OVERLAY_OPENED", {
|
||||
property: peekableAttribute,
|
||||
property: expression,
|
||||
});
|
||||
};
|
||||
|
||||
hidePeekOverlay = () => {
|
||||
this.state.peekOverlayProps?.marker?.clear();
|
||||
this.setState({
|
||||
peekOverlayProps: undefined,
|
||||
});
|
||||
if (this.state.peekOverlayProps) {
|
||||
this.state.peekOverlayProps.tokenElement.classList.remove(
|
||||
PEEK_STYLE_PERSIST_CLASS,
|
||||
);
|
||||
this.setState({
|
||||
peekOverlayProps: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
debounceHandleMouseOver = debounce(
|
||||
|
|
@ -648,36 +658,109 @@ class CodeEditor extends Component<Props, State> {
|
|||
setTimeout(delayedWork, 0);
|
||||
};
|
||||
|
||||
handleMouseOver = (event: MouseEvent) => {
|
||||
isPeekableElement = (element: Element) => {
|
||||
if (
|
||||
event.target instanceof Element &&
|
||||
event.target.hasAttribute(PEEKABLE_ATTRIBUTE)
|
||||
!element.classList.contains("cm-m-javascript") ||
|
||||
element.classList.contains("binding-brackets")
|
||||
)
|
||||
return false;
|
||||
if (
|
||||
// global variables and functions
|
||||
// JsObject1, storeValue()
|
||||
element.classList.contains("cm-variable") ||
|
||||
// properties and function calls
|
||||
// JsObject.myFun(), Api1.data
|
||||
element.classList.contains("cm-property") ||
|
||||
// array indices - [0]
|
||||
element.classList.contains("cm-number") ||
|
||||
// string accessor - ["x"]
|
||||
element.classList.contains("cm-string")
|
||||
) {
|
||||
const tokenElement = event.target;
|
||||
const tokenElementPosition = tokenElement.getBoundingClientRect();
|
||||
const peekableAttribute = tokenElement.getAttribute(PEEKABLE_ATTRIBUTE);
|
||||
if (peekableAttribute) {
|
||||
// don't retrigger if hovering over the same token
|
||||
if (
|
||||
this.state.peekOverlayProps?.name === peekableAttribute &&
|
||||
this.state.peekOverlayProps?.position.top ===
|
||||
tokenElementPosition.top &&
|
||||
this.state.peekOverlayProps?.position.left ===
|
||||
tokenElementPosition.left
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const paths = peekableAttribute.split(".");
|
||||
if (paths.length) {
|
||||
paths.splice(1, 0, "peekData");
|
||||
this.showPeekOverlay(
|
||||
peekableAttribute,
|
||||
tokenElement,
|
||||
tokenElementPosition,
|
||||
_.get(this.props.entitiesForNavigation, paths),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
} else if (element.classList.contains("cm-keyword")) {
|
||||
// this keyword for jsObjects
|
||||
if (this.props.isJSObject && element.innerHTML === "this") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getBindingSnippetAtPos = (
|
||||
multiPlexConfig: MultiplexingModeConfig,
|
||||
pos: number,
|
||||
) => {
|
||||
return multiPlexConfig.innerModes.map((innerMode) => {
|
||||
const doc = this.editor.getValue();
|
||||
const openPos =
|
||||
doc.lastIndexOf(innerMode.open, pos) + innerMode.open.length;
|
||||
const closePos = doc.indexOf(innerMode.close, pos);
|
||||
return {
|
||||
value: doc.slice(openPos, closePos),
|
||||
offset: openPos,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
updateScriptForPeekOverlay = (chIndex: number) => {
|
||||
if (
|
||||
!this.peekOverlayExpressionIdentifier.hasParsedScript() ||
|
||||
this.multiplexConfig
|
||||
) {
|
||||
if (this.multiplexConfig) {
|
||||
const bindingSnippetsByInnerMode = this.getBindingSnippetAtPos(
|
||||
this.multiplexConfig,
|
||||
chIndex,
|
||||
);
|
||||
for (const snippet of bindingSnippetsByInnerMode) {
|
||||
if (snippet.value) {
|
||||
this.peekOverlayExpressionIdentifier.updateScript(snippet.value);
|
||||
chIndex -= snippet.offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.peekOverlayExpressionIdentifier.updateScript(
|
||||
this.editor.getValue(),
|
||||
);
|
||||
}
|
||||
}
|
||||
return chIndex;
|
||||
};
|
||||
|
||||
isPathLibrary = (paths: string[]) => {
|
||||
return !!this.props.installedLibraries.find((installedLib) =>
|
||||
installedLib.accessor.find((accessor) => accessor === paths[0]),
|
||||
);
|
||||
};
|
||||
|
||||
handleMouseOver = (event: MouseEvent) => {
|
||||
const tokenElement = event.target;
|
||||
if (
|
||||
tokenElement instanceof Element &&
|
||||
this.isPeekableElement(tokenElement)
|
||||
) {
|
||||
const tokenPos = this.editor.coordsChar({
|
||||
left: event.clientX,
|
||||
top: event.clientY,
|
||||
});
|
||||
const chIndex = this.updateScriptForPeekOverlay(
|
||||
this.editor.indexFromPos(tokenPos),
|
||||
);
|
||||
|
||||
this.peekOverlayExpressionIdentifier
|
||||
.extractExpressionAtPosition(chIndex)
|
||||
.then((lineExpression: string) => {
|
||||
const paths = _.toPath(lineExpression);
|
||||
if (!this.isPathLibrary(paths)) {
|
||||
this.showPeekOverlay(lineExpression, paths, tokenElement);
|
||||
} else {
|
||||
this.hidePeekOverlay();
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
this.hidePeekOverlay();
|
||||
debug(e);
|
||||
});
|
||||
} else {
|
||||
this.hidePeekOverlay();
|
||||
}
|
||||
|
|
@ -1086,6 +1169,8 @@ class CodeEditor extends Component<Props, State> {
|
|||
changeObj.to,
|
||||
);
|
||||
}
|
||||
|
||||
this.peekOverlayExpressionIdentifier.clearScript();
|
||||
};
|
||||
|
||||
handleDebouncedChange = _.debounce(this.handleChange, 600);
|
||||
|
|
@ -1568,6 +1653,7 @@ const mapStateToProps = (state: AppState, props: EditorProps) => ({
|
|||
),
|
||||
featureFlags: selectFeatureFlags(state),
|
||||
datasourceTableKeys: getAllDatasourceTableKeys(state, props.dataTreePath),
|
||||
installedLibraries: selectInstalledLibraries(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => ({
|
||||
|
|
|
|||
|
|
@ -1,73 +1,96 @@
|
|||
import CodeMirror from "codemirror";
|
||||
import type { TEditorModes } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import { EditorModes } from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import "codemirror/addon/mode/multiplex";
|
||||
import "codemirror/mode/javascript/javascript";
|
||||
import "codemirror/mode/sql/sql";
|
||||
import "codemirror/addon/hint/sql-hint";
|
||||
import type { TEditorSqlModes } from "./sql/config";
|
||||
import { sqlModesConfig } from "./sql/config";
|
||||
|
||||
CodeMirror.defineMode(EditorModes.TEXT_WITH_BINDING, function (config) {
|
||||
// @ts-expect-error: Types are not available
|
||||
return CodeMirror.multiplexingMode(
|
||||
CodeMirror.getMode(config, EditorModes.TEXT),
|
||||
{
|
||||
open: "{{",
|
||||
close: "}}",
|
||||
mode: CodeMirror.getMode(config, {
|
||||
name: "javascript",
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
export const BINDING_OPEN = "{{",
|
||||
BINDING_CLOSE = "}}";
|
||||
|
||||
CodeMirror.defineMode(EditorModes.JSON_WITH_BINDING, function (config) {
|
||||
// @ts-expect-error: Types are not available
|
||||
return CodeMirror.multiplexingMode(
|
||||
CodeMirror.getMode(config, { name: "javascript", json: true }),
|
||||
{
|
||||
open: "{{",
|
||||
close: "}}",
|
||||
mode: CodeMirror.getMode(config, {
|
||||
name: "javascript",
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
export interface MultiplexingModeConfig {
|
||||
outerMode: string | { name: string; json?: boolean };
|
||||
innerModes: {
|
||||
open: string;
|
||||
close: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
CodeMirror.defineMode(EditorModes.GRAPHQL_WITH_BINDING, function (config) {
|
||||
// @ts-expect-error: Types are not available
|
||||
return CodeMirror.multiplexingMode(
|
||||
CodeMirror.getMode(config, EditorModes.GRAPHQL),
|
||||
{
|
||||
open: "{{",
|
||||
close: "}}",
|
||||
mode: CodeMirror.getMode(config, {
|
||||
name: "javascript",
|
||||
}),
|
||||
},
|
||||
{
|
||||
open: '"{{',
|
||||
close: '}}"',
|
||||
mode: CodeMirror.getMode(config, {
|
||||
name: "javascript",
|
||||
}),
|
||||
},
|
||||
);
|
||||
});
|
||||
export type MultiplexingModeConfigs = Record<
|
||||
TEditorModes,
|
||||
MultiplexingModeConfig | undefined
|
||||
>;
|
||||
|
||||
for (const sqlModeConfig of Object.values(sqlModesConfig)) {
|
||||
if (!sqlModeConfig.isMultiplex) continue;
|
||||
CodeMirror.defineMode(sqlModeConfig.mode, function (config) {
|
||||
export const MULTIPLEXING_MODE_CONFIGS: MultiplexingModeConfigs = {
|
||||
[EditorModes.TEXT_WITH_BINDING]: {
|
||||
outerMode: EditorModes.TEXT,
|
||||
innerModes: [
|
||||
{
|
||||
open: BINDING_OPEN,
|
||||
close: BINDING_CLOSE,
|
||||
},
|
||||
],
|
||||
},
|
||||
[EditorModes.JSON_WITH_BINDING]: {
|
||||
outerMode: { name: "javascript", json: true },
|
||||
innerModes: [
|
||||
{
|
||||
open: BINDING_OPEN,
|
||||
close: BINDING_CLOSE,
|
||||
},
|
||||
],
|
||||
},
|
||||
[EditorModes.GRAPHQL_WITH_BINDING]: {
|
||||
outerMode: EditorModes.GRAPHQL,
|
||||
innerModes: [
|
||||
{
|
||||
open: BINDING_OPEN,
|
||||
close: BINDING_CLOSE,
|
||||
},
|
||||
{
|
||||
// https://github.com/appsmithorg/appsmith/issues/16702
|
||||
open: '"{{',
|
||||
close: '}}"',
|
||||
},
|
||||
],
|
||||
},
|
||||
...Object.values(sqlModesConfig)
|
||||
.filter((config) => config.isMultiplex)
|
||||
.reduce((prev, current) => {
|
||||
prev[current.mode] = {
|
||||
outerMode: current.mime,
|
||||
innerModes: [
|
||||
{
|
||||
open: BINDING_OPEN,
|
||||
close: BINDING_CLOSE,
|
||||
},
|
||||
],
|
||||
};
|
||||
return prev;
|
||||
}, {} as Record<TEditorSqlModes, MultiplexingModeConfig | undefined>),
|
||||
"text/plain": undefined,
|
||||
"application/json": undefined,
|
||||
javascript: undefined,
|
||||
graphql: undefined,
|
||||
};
|
||||
|
||||
Object.keys(MULTIPLEXING_MODE_CONFIGS).forEach((key) => {
|
||||
const multiplexConfig = MULTIPLEXING_MODE_CONFIGS[key as TEditorModes];
|
||||
if (!multiplexConfig) return;
|
||||
CodeMirror.defineMode(key, function (config) {
|
||||
// @ts-expect-error: Types are not available
|
||||
return CodeMirror.multiplexingMode(
|
||||
CodeMirror.getMode(config, sqlModeConfig.mime),
|
||||
{
|
||||
open: "{{",
|
||||
close: "}}",
|
||||
CodeMirror.getMode(config, multiplexConfig.outerMode),
|
||||
...multiplexConfig.innerModes.map((innerMode) => ({
|
||||
open: innerMode.open,
|
||||
close: innerMode.close,
|
||||
mode: CodeMirror.getMode(config, {
|
||||
name: "javascript",
|
||||
}),
|
||||
},
|
||||
})),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,11 +7,9 @@ import {
|
|||
import type { Theme } from "constants/DefaultTheme";
|
||||
import { Skin } from "constants/DefaultTheme";
|
||||
import { Colors } from "constants/Colors";
|
||||
import {
|
||||
NAVIGATION_CLASSNAME,
|
||||
PEEKABLE_CLASSNAME,
|
||||
PEEK_STYLE_PERSIST_CLASS,
|
||||
} from "./MarkHelpers/entityMarker";
|
||||
import { NAVIGATION_CLASSNAME } from "./MarkHelpers/entityMarker";
|
||||
|
||||
export const PEEK_STYLE_PERSIST_CLASS = "peek-style-persist";
|
||||
|
||||
const getBorderStyle = (
|
||||
props: { theme: Theme } & {
|
||||
|
|
@ -263,7 +261,7 @@ export const EditorWrapper = styled.div<{
|
|||
font-weight: 700;
|
||||
}
|
||||
|
||||
.${PEEKABLE_CLASSNAME}:hover, .${PEEK_STYLE_PERSIST_CLASS} {
|
||||
.${PEEK_STYLE_PERSIST_CLASS} {
|
||||
border-color: var(--ads-v2-color-border-emphasis);
|
||||
background-color: #ededed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -404,6 +404,7 @@ function JSEditorForm({ jsCollection: currentJSCollection }: Props) {
|
|||
onChange: handleEditorChange,
|
||||
}}
|
||||
isJSObject
|
||||
jsObjectName={currentJSCollection.name}
|
||||
mode={EditorModes.JAVASCRIPT}
|
||||
placeholder="Let's write some code!"
|
||||
showLightningMenu={false}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import type {
|
||||
DataTree,
|
||||
AppsmithEntity,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import type { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||
import { createSelector } from "reselect";
|
||||
import {
|
||||
|
|
@ -15,11 +12,9 @@ import { getCurrentPageId } from "selectors/editorSelectors";
|
|||
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
|
||||
import { jsCollectionIdURL, widgetURL } from "RouteBuilder";
|
||||
import { getDataTree } from "selectors/dataTreeSelectors";
|
||||
import { getActionChildrenNavData } from "utils/NavigationSelector/ActionChildren";
|
||||
import { createNavData } from "utils/NavigationSelector/common";
|
||||
import { getWidgetChildrenNavData } from "utils/NavigationSelector/WidgetChildren";
|
||||
import { getJsChildrenNavData } from "utils/NavigationSelector/JsChildren";
|
||||
import { getAppsmithNavData } from "utils/NavigationSelector/AppsmithNavData";
|
||||
import {
|
||||
getEntityNameAndPropertyPath,
|
||||
isJSAction,
|
||||
|
|
@ -36,8 +31,6 @@ export type NavigationData = {
|
|||
url: string | undefined;
|
||||
navigable: boolean;
|
||||
children: EntityNavigationData;
|
||||
peekable: boolean;
|
||||
peekData?: unknown;
|
||||
key?: string;
|
||||
pluginName?: string;
|
||||
isMock?: boolean;
|
||||
|
|
@ -79,8 +72,6 @@ export const getEntitiesForNavigation = createSelector(
|
|||
(datasource) => datasource.id === datasourceId,
|
||||
);
|
||||
const config = getActionConfig(action.config.pluginType);
|
||||
// dataTree used to get entityDefinitions and peekData
|
||||
const result = getActionChildrenNavData(action, dataTree);
|
||||
if (!config) return;
|
||||
navigationData[action.config.name] = createNavData({
|
||||
id: action.config.id,
|
||||
|
|
@ -92,9 +83,7 @@ export const getEntitiesForNavigation = createSelector(
|
|||
action.config.pluginType,
|
||||
plugin,
|
||||
),
|
||||
peekable: true,
|
||||
peekData: result?.peekData,
|
||||
children: result?.childNavData || {},
|
||||
children: {},
|
||||
// Adding below data as it is required for analytical events
|
||||
pluginName: plugin?.name,
|
||||
datasourceId: datasource?.id,
|
||||
|
|
@ -105,36 +94,33 @@ export const getEntitiesForNavigation = createSelector(
|
|||
});
|
||||
|
||||
jsActions.forEach((jsAction) => {
|
||||
// dataTree for null check and peekData
|
||||
// dataTree for null check
|
||||
const result = getJsChildrenNavData(jsAction, pageId, dataTree);
|
||||
navigationData[jsAction.config.name] = createNavData({
|
||||
id: jsAction.config.id,
|
||||
name: jsAction.config.name,
|
||||
type: ENTITY_TYPE.JSACTION,
|
||||
url: jsCollectionIdURL({ pageId, collectionId: jsAction.config.id }),
|
||||
peekable: true,
|
||||
peekData: result?.peekData,
|
||||
children: result?.childNavData || {},
|
||||
});
|
||||
});
|
||||
|
||||
Object.values(widgets).forEach((widget) => {
|
||||
// dataTree to get entityDefinitions, for url (can use getWidgetByName?) and peekData
|
||||
const result = getWidgetChildrenNavData(widget, dataTree, pageId);
|
||||
// dataTree to get entityDefinitions, for url (can use getWidgetByName?)
|
||||
const result = getWidgetChildrenNavData(
|
||||
widget.widgetName,
|
||||
widget.type,
|
||||
dataTree,
|
||||
pageId,
|
||||
);
|
||||
navigationData[widget.widgetName] = createNavData({
|
||||
id: widget.widgetId,
|
||||
name: widget.widgetName,
|
||||
type: ENTITY_TYPE.WIDGET,
|
||||
url: widgetURL({ pageId, selectedWidgets: [widget.widgetId] }),
|
||||
peekable: true,
|
||||
peekData: result?.peekData,
|
||||
children: result?.childNavData || {},
|
||||
});
|
||||
});
|
||||
// dataTree to get entity definitions and peekData
|
||||
navigationData["appsmith"] = getAppsmithNavData(
|
||||
dataTree.appsmith as AppsmithEntity,
|
||||
);
|
||||
if (
|
||||
entityName &&
|
||||
isJSAction(dataTree[entityName]) &&
|
||||
|
|
|
|||
26
app/client/src/utils/FilterInternalProperties/Action.ts
Normal file
26
app/client/src/utils/FilterInternalProperties/Action.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { entityDefinitions } from "@appsmith/utils/autocomplete/EntityDefinitions";
|
||||
import type { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import type { ActionEntity } from "entities/DataTree/types";
|
||||
|
||||
export const getActionChildrenPeekData = (
|
||||
actionName: string,
|
||||
dataTree: DataTree,
|
||||
) => {
|
||||
const dataTreeAction = dataTree[actionName] as ActionEntity;
|
||||
if (dataTreeAction) {
|
||||
const definitions = entityDefinitions.ACTION(dataTreeAction, {});
|
||||
const peekData: Record<string, unknown> = {};
|
||||
Object.keys(definitions).forEach((key) => {
|
||||
if (key.indexOf("!") === -1) {
|
||||
if (key === "data" || key === "isLoading" || key === "responseMeta") {
|
||||
peekData[key] = dataTreeAction[key];
|
||||
} else if (key === "run" || key === "clear") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
peekData[key] = function () {}; // tern inference required here
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { peekData };
|
||||
}
|
||||
};
|
||||
14
app/client/src/utils/FilterInternalProperties/Appsmith.ts
Normal file
14
app/client/src/utils/FilterInternalProperties/Appsmith.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { entityDefinitions } from "@appsmith/utils/autocomplete/EntityDefinitions";
|
||||
import type {
|
||||
AppsmithEntity,
|
||||
DataTree,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import { createObjectPeekData } from "./Common";
|
||||
|
||||
export const getAppsmithPeekData = (dataTree: DataTree) => {
|
||||
const defs: any = entityDefinitions.APPSMITH(
|
||||
dataTree.appsmith as AppsmithEntity,
|
||||
{},
|
||||
);
|
||||
return createObjectPeekData(defs, dataTree.appsmith, {}, "appsmith");
|
||||
};
|
||||
40
app/client/src/utils/FilterInternalProperties/Common.ts
Normal file
40
app/client/src/utils/FilterInternalProperties/Common.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import _ from "lodash";
|
||||
|
||||
export const isTernFunctionDef = (data: any) =>
|
||||
typeof data === "string" && /^fn\((?:[\w,: \(\)->])*\) -> [\w]*$/.test(data);
|
||||
|
||||
export const createObjectPeekData = (
|
||||
defs: any,
|
||||
data: any,
|
||||
peekData: any,
|
||||
parentKey: string,
|
||||
) => {
|
||||
Object.keys(defs).forEach((key: string) => {
|
||||
if (key.indexOf("!") === -1) {
|
||||
const childKeyPathArray = [parentKey, key];
|
||||
if (isObject(defs[key])) {
|
||||
if (Object.keys(defs[key]).length > 0) {
|
||||
peekData[key] = {};
|
||||
const result = createObjectPeekData(
|
||||
defs[key],
|
||||
data[key],
|
||||
peekData[key],
|
||||
key,
|
||||
);
|
||||
_.set(peekData, childKeyPathArray, result.peekData);
|
||||
} else {
|
||||
peekData[key] = data[key];
|
||||
}
|
||||
} else {
|
||||
peekData[key] = isTernFunctionDef(defs[key])
|
||||
? // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
function () {} // tern inference required here
|
||||
: data[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
return { peekData };
|
||||
};
|
||||
|
||||
const isObject = (data: any) =>
|
||||
typeof data === "object" && !Array.isArray(data) && data !== null;
|
||||
29
app/client/src/utils/FilterInternalProperties/JsAction.ts
Normal file
29
app/client/src/utils/FilterInternalProperties/JsAction.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import type { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import type { JSCollectionData } from "reducers/entityReducers/jsActionsReducer";
|
||||
import type { JSActionEntity } from "entities/DataTree/types";
|
||||
|
||||
export const getJsActionPeekData = (
|
||||
jsAction: JSCollectionData,
|
||||
dataTree: DataTree,
|
||||
) => {
|
||||
const peekData: Record<string, unknown> = {};
|
||||
|
||||
const dataTreeAction = dataTree[jsAction.config.name] as JSActionEntity;
|
||||
|
||||
if (dataTreeAction) {
|
||||
jsAction.config.actions.forEach((jsChild) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
peekData[jsChild.name] = function () {};
|
||||
|
||||
if (jsAction.data?.[jsChild.id] && jsChild.executeOnLoad) {
|
||||
(peekData[jsChild.name] as any).data = jsAction.data[jsChild.id];
|
||||
}
|
||||
});
|
||||
|
||||
jsAction.config.variables.forEach((jsChild) => {
|
||||
if (dataTreeAction) peekData[jsChild.name] = dataTreeAction[jsChild.name];
|
||||
});
|
||||
|
||||
return { peekData };
|
||||
}
|
||||
};
|
||||
34
app/client/src/utils/FilterInternalProperties/Widget.ts
Normal file
34
app/client/src/utils/FilterInternalProperties/Widget.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import type { EntityDefinitionsOptions } from "ce/utils/autocomplete/EntityDefinitions";
|
||||
import type { DataTree, WidgetEntity } from "entities/DataTree/dataTreeFactory";
|
||||
import { isFunction } from "lodash";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
|
||||
export const getWidgetChildrenPeekData = (
|
||||
widgetName: string,
|
||||
widgetType: string,
|
||||
dataTree: DataTree,
|
||||
) => {
|
||||
const peekData: Record<string, unknown> = {};
|
||||
const dataTreeWidget: WidgetEntity = dataTree[widgetName] as WidgetEntity;
|
||||
if (widgetType !== "FORM_WIDGET" && dataTreeWidget) {
|
||||
const type: Exclude<
|
||||
EntityDefinitionsOptions,
|
||||
| "CANVAS_WIDGET"
|
||||
| "ICON_WIDGET"
|
||||
| "SKELETON_WIDGET"
|
||||
| "TABS_MIGRATOR_WIDGET"
|
||||
> = dataTreeWidget.type as any;
|
||||
let config: any = WidgetFactory.getAutocompleteDefinitions(type);
|
||||
if (config) {
|
||||
if (isFunction(config)) config = config(dataTreeWidget);
|
||||
const widgetProps = Object.keys(config).filter(
|
||||
(k) => k.indexOf("!") === -1,
|
||||
);
|
||||
widgetProps.forEach((prop) => {
|
||||
const data = dataTreeWidget[prop];
|
||||
peekData[prop] = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
return { peekData };
|
||||
};
|
||||
42
app/client/src/utils/FilterInternalProperties/index.ts
Normal file
42
app/client/src/utils/FilterInternalProperties/index.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { getActionChildrenPeekData } from "./Action";
|
||||
import type {
|
||||
DataTree,
|
||||
DataTreeEntity,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import type { JSCollectionDataState } from "reducers/entityReducers/jsActionsReducer";
|
||||
import { getWidgetChildrenPeekData } from "./Widget";
|
||||
import { getJsActionPeekData } from "./JsAction";
|
||||
import { getAppsmithPeekData } from "./Appsmith";
|
||||
import {
|
||||
isActionEntity,
|
||||
isWidgetEntity,
|
||||
} from "components/editorComponents/CodeEditor/codeEditorUtils";
|
||||
import {
|
||||
isAppsmithEntity,
|
||||
isJSAction,
|
||||
} from "ce/workers/Evaluation/evaluationUtils";
|
||||
|
||||
export const filterInternalProperties = (
|
||||
objectName: string,
|
||||
dataTreeEntity: DataTreeEntity,
|
||||
jsActions: JSCollectionDataState,
|
||||
dataTree: DataTree,
|
||||
) => {
|
||||
if (!dataTreeEntity) return;
|
||||
if (isActionEntity(dataTreeEntity)) {
|
||||
return getActionChildrenPeekData(objectName, dataTree)?.peekData;
|
||||
} else if (isAppsmithEntity(dataTreeEntity)) {
|
||||
return getAppsmithPeekData(dataTree).peekData;
|
||||
} else if (isJSAction(dataTreeEntity)) {
|
||||
const jsAction = jsActions.find(
|
||||
(jsAction) => jsAction.config.id === dataTreeEntity.actionId,
|
||||
);
|
||||
return jsAction
|
||||
? getJsActionPeekData(jsAction, dataTree)?.peekData
|
||||
: dataTreeEntity;
|
||||
} else if (isWidgetEntity(dataTreeEntity)) {
|
||||
return getWidgetChildrenPeekData(objectName, dataTreeEntity.type, dataTree)
|
||||
?.peekData;
|
||||
}
|
||||
return dataTreeEntity;
|
||||
};
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import { entityDefinitions } from "@appsmith/utils/autocomplete/EntityDefinitions";
|
||||
import type { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||
import type { ActionData } from "reducers/entityReducers/actionsReducer";
|
||||
import type { EntityNavigationData } from "selectors/navigationSelectors";
|
||||
import { createNavData } from "./common";
|
||||
import type { ActionEntity } from "entities/DataTree/types";
|
||||
|
||||
export const getActionChildrenNavData = (
|
||||
action: ActionData,
|
||||
dataTree: DataTree,
|
||||
) => {
|
||||
const dataTreeAction = dataTree[action.config.name] as ActionEntity;
|
||||
if (dataTreeAction) {
|
||||
const definitions = entityDefinitions.ACTION(dataTreeAction, {});
|
||||
const peekData: Record<string, unknown> = {};
|
||||
const childNavData: EntityNavigationData = {};
|
||||
Object.keys(definitions).forEach((key) => {
|
||||
if (key.indexOf("!") === -1) {
|
||||
if (key === "data" || key === "isLoading" || key === "responseMeta") {
|
||||
peekData[key] = dataTreeAction[key];
|
||||
childNavData[key] = createNavData({
|
||||
id: `${action.config.name}.${key}`,
|
||||
name: `${action.config.name}.${key}`,
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
children: {},
|
||||
});
|
||||
} else if (key === "run" || key === "clear") {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
peekData[key] = function () {}; // tern inference required here
|
||||
childNavData[key] = createNavData({
|
||||
id: `${action.config.name}.${key}`,
|
||||
name: `${action.config.name}.${key}`,
|
||||
type: ENTITY_TYPE.ACTION,
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
children: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { peekData, childNavData };
|
||||
}
|
||||
};
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
import { entityDefinitions } from "@appsmith/utils/autocomplete/EntityDefinitions";
|
||||
import type { AppsmithEntity } from "entities/DataTree/dataTreeFactory";
|
||||
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||
import { createNavData, createObjectNavData } from "./common";
|
||||
|
||||
export const getAppsmithNavData = (dataTree: AppsmithEntity) => {
|
||||
const defs: any = entityDefinitions.APPSMITH(dataTree, {});
|
||||
|
||||
const result = createObjectNavData(
|
||||
defs,
|
||||
dataTree,
|
||||
"appsmith",
|
||||
{},
|
||||
{
|
||||
// restricting peek after appsmith.store because it can contain user data
|
||||
// which if large will slow down nav data generation
|
||||
"appsmith.store": true,
|
||||
},
|
||||
);
|
||||
|
||||
return createNavData({
|
||||
id: "appsmith",
|
||||
name: "appsmith",
|
||||
type: ENTITY_TYPE.APPSMITH,
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: result.peekData,
|
||||
children: result.entityNavigationData,
|
||||
});
|
||||
};
|
||||
|
|
@ -15,31 +15,12 @@ export const getJsChildrenNavData = (
|
|||
pageId: string,
|
||||
dataTree: DataTree,
|
||||
) => {
|
||||
const peekData: Record<string, unknown> = {};
|
||||
let childNavData: EntityNavigationData = {};
|
||||
|
||||
const dataTreeAction = dataTree[jsAction.config.name] as JSActionEntity;
|
||||
|
||||
if (dataTreeAction) {
|
||||
let children: NavigationData[] = jsAction.config.actions.map((jsChild) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
peekData[jsChild.name] = function () {}; // can use new Function to parse string
|
||||
|
||||
const children: EntityNavigationData = {};
|
||||
if (jsAction.data?.[jsChild.id] && jsChild.executeOnLoad) {
|
||||
(peekData[jsChild.name] as any).data = jsAction.data[jsChild.id];
|
||||
children.data = createNavData({
|
||||
id: `${jsAction.config.name}.${jsChild.name}.data`,
|
||||
name: `${jsAction.config.name}.${jsChild.name}.data`,
|
||||
type: ENTITY_TYPE.JSACTION,
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
children: {},
|
||||
key: jsChild.name + ".data",
|
||||
});
|
||||
}
|
||||
|
||||
return createNavData({
|
||||
id: `${jsAction.config.name}.${jsChild.name}`,
|
||||
name: `${jsAction.config.name}.${jsChild.name}`,
|
||||
|
|
@ -49,17 +30,13 @@ export const getJsChildrenNavData = (
|
|||
collectionId: jsAction.config.id,
|
||||
functionName: jsChild.name,
|
||||
}),
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
children,
|
||||
children: {},
|
||||
key: jsChild.name,
|
||||
});
|
||||
});
|
||||
|
||||
const variableChildren: NavigationData[] = jsAction.config.variables.map(
|
||||
(jsChild) => {
|
||||
if (dataTreeAction)
|
||||
peekData[jsChild.name] = dataTreeAction[jsChild.name];
|
||||
return createNavData({
|
||||
id: `${jsAction.config.name}.${jsChild.name}`,
|
||||
name: `${jsAction.config.name}.${jsChild.name}`,
|
||||
|
|
@ -69,8 +46,6 @@ export const getJsChildrenNavData = (
|
|||
collectionId: jsAction.config.id,
|
||||
functionName: jsChild.name,
|
||||
}),
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
children: {},
|
||||
key: jsChild.name,
|
||||
});
|
||||
|
|
@ -84,6 +59,6 @@ export const getJsChildrenNavData = (
|
|||
NavigationData
|
||||
>;
|
||||
|
||||
return { childNavData, peekData };
|
||||
return { childNavData };
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,80 +1,33 @@
|
|||
import type { EntityDefinitionsOptions } from "@appsmith/utils/autocomplete/EntityDefinitions";
|
||||
import type { DataTree, WidgetEntity } from "entities/DataTree/dataTreeFactory";
|
||||
import { ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||
import { isFunction } from "lodash";
|
||||
import type { FlattenedWidgetProps } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import { builderURL } from "RouteBuilder";
|
||||
import type { EntityNavigationData } from "selectors/navigationSelectors";
|
||||
import { createNavData } from "./common";
|
||||
import WidgetFactory from "utils/WidgetFactory";
|
||||
|
||||
export const getWidgetChildrenNavData = (
|
||||
widget: FlattenedWidgetProps,
|
||||
widgetName: string,
|
||||
widgetType: string,
|
||||
dataTree: DataTree,
|
||||
pageId: string,
|
||||
) => {
|
||||
const peekData: Record<string, unknown> = {};
|
||||
const childNavData: EntityNavigationData = {};
|
||||
const dataTreeWidget: WidgetEntity = dataTree[
|
||||
widget.widgetName
|
||||
] as WidgetEntity;
|
||||
if (widget.type === "FORM_WIDGET") {
|
||||
const dataTreeWidget: WidgetEntity = dataTree[widgetName] as WidgetEntity;
|
||||
if (widgetType === "FORM_WIDGET") {
|
||||
const children: EntityNavigationData = {};
|
||||
const formChildren: EntityNavigationData = {};
|
||||
if (dataTreeWidget) {
|
||||
Object.keys(dataTreeWidget.data || {}).forEach((widgetName) => {
|
||||
const childWidgetId = (dataTree[widgetName] as WidgetEntity).widgetId;
|
||||
formChildren[widgetName] = createNavData({
|
||||
id: `${widget.widgetName}.data.${widgetName}`,
|
||||
name: widgetName,
|
||||
Object.keys(dataTreeWidget.data || {}).forEach((childWidgetName) => {
|
||||
const childWidgetId = (dataTree[childWidgetName] as WidgetEntity)
|
||||
.widgetId;
|
||||
formChildren[childWidgetName] = createNavData({
|
||||
id: `${widgetName}.data.${childWidgetName}`,
|
||||
name: childWidgetName,
|
||||
type: ENTITY_TYPE.WIDGET,
|
||||
url: builderURL({ pageId, hash: childWidgetId }),
|
||||
peekable: false,
|
||||
peekData: undefined,
|
||||
children: {},
|
||||
});
|
||||
});
|
||||
}
|
||||
children.data = createNavData({
|
||||
id: `${widget.widgetName}.data`,
|
||||
name: "data",
|
||||
type: ENTITY_TYPE.WIDGET,
|
||||
url: undefined,
|
||||
peekable: false,
|
||||
peekData: undefined,
|
||||
children: formChildren,
|
||||
});
|
||||
|
||||
return { childNavData: children, peekData };
|
||||
}
|
||||
if (dataTreeWidget) {
|
||||
const type: Exclude<
|
||||
EntityDefinitionsOptions,
|
||||
| "CANVAS_WIDGET"
|
||||
| "ICON_WIDGET"
|
||||
| "SKELETON_WIDGET"
|
||||
| "TABS_MIGRATOR_WIDGET"
|
||||
> = dataTreeWidget.type as any;
|
||||
let config: any = WidgetFactory.getAutocompleteDefinitions(type);
|
||||
if (config) {
|
||||
if (isFunction(config)) config = config(dataTreeWidget);
|
||||
const widgetProps = Object.keys(config).filter(
|
||||
(k) => k.indexOf("!") === -1,
|
||||
);
|
||||
widgetProps.forEach((prop) => {
|
||||
const data = dataTreeWidget[prop];
|
||||
peekData[prop] = data;
|
||||
childNavData[prop] = createNavData({
|
||||
id: `${widget.widgetName}.${prop}`,
|
||||
name: `${widget.widgetName}.${prop}`,
|
||||
type: ENTITY_TYPE.WIDGET,
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
children: {},
|
||||
});
|
||||
});
|
||||
}
|
||||
return { childNavData, peekData };
|
||||
return { childNavData: children };
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ENTITY_TYPE } from "entities/DataTree/types";
|
||||
import type { ENTITY_TYPE } from "entities/DataTree/types";
|
||||
import type {
|
||||
EntityNavigationData,
|
||||
NavigationData,
|
||||
|
|
@ -11,8 +11,6 @@ export const createNavData = (general: {
|
|||
children: EntityNavigationData;
|
||||
key?: string;
|
||||
url: string | undefined;
|
||||
peekable: boolean;
|
||||
peekData: unknown;
|
||||
pluginName?: string;
|
||||
datasourceId?: string;
|
||||
isMock?: boolean;
|
||||
|
|
@ -26,80 +24,9 @@ export const createNavData = (general: {
|
|||
key: general.key,
|
||||
url: general.url,
|
||||
navigable: !!general.url,
|
||||
peekable: general.peekable,
|
||||
peekData: general.peekData,
|
||||
pluginName: general.pluginName,
|
||||
datasourceId: general.datasourceId,
|
||||
isMock: general.isMock,
|
||||
actionType: general.actionType,
|
||||
};
|
||||
};
|
||||
|
||||
export const isTernFunctionDef = (data: any) =>
|
||||
typeof data === "string" && /^fn\((?:[\w,: \(\)->])*\) -> [\w]*$/.test(data);
|
||||
|
||||
export const createObjectNavData = (
|
||||
defs: any,
|
||||
data: any,
|
||||
parentKey: string,
|
||||
peekData: any,
|
||||
restrictKeysFrom: Record<string, boolean>,
|
||||
) => {
|
||||
const entityNavigationData: EntityNavigationData = {};
|
||||
Object.keys(defs).forEach((key: string) => {
|
||||
if (key.indexOf("!") === -1) {
|
||||
const childKey = parentKey + "." + key;
|
||||
if (isObject(defs[key])) {
|
||||
if (Object.keys(defs[key]).length > 0 && !restrictKeysFrom[childKey]) {
|
||||
peekData[key] = {};
|
||||
const result = createObjectNavData(
|
||||
defs[key],
|
||||
data[key],
|
||||
childKey,
|
||||
peekData[key],
|
||||
restrictKeysFrom,
|
||||
);
|
||||
peekData[key] = result.peekData;
|
||||
entityNavigationData[key] = createNavData({
|
||||
id: childKey,
|
||||
name: childKey,
|
||||
type: ENTITY_TYPE.APPSMITH,
|
||||
children: result.entityNavigationData,
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
});
|
||||
} else {
|
||||
peekData[key] = data[key];
|
||||
entityNavigationData[key] = createNavData({
|
||||
id: childKey,
|
||||
name: childKey,
|
||||
type: ENTITY_TYPE.APPSMITH,
|
||||
children: {},
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
peekData[key] = isTernFunctionDef(defs[key])
|
||||
? // eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
function () {} // tern inference required here
|
||||
: data[key];
|
||||
entityNavigationData[key] = createNavData({
|
||||
id: childKey,
|
||||
name: childKey,
|
||||
type: ENTITY_TYPE.APPSMITH,
|
||||
children: {},
|
||||
url: undefined,
|
||||
peekable: true,
|
||||
peekData: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return { peekData, entityNavigationData };
|
||||
};
|
||||
|
||||
const isObject = (data: any) =>
|
||||
typeof data === "object" && !Array.isArray(data) && data !== null;
|
||||
|
|
|
|||
|
|
@ -9576,10 +9576,10 @@ __metadata:
|
|||
cypress-multi-reporters: ^1.2.4
|
||||
cypress-network-idle: ^1.14.2
|
||||
cypress-plugin-tab: ^1.0.5
|
||||
cypress-real-events: ^1.7.1
|
||||
cypress-real-events: ^1.8.1
|
||||
cypress-tags: ^1.1.2
|
||||
cypress-wait-until: ^1.7.2
|
||||
cypress-xpath: ^1.4.0
|
||||
cypress-xpath: ^1.6.0
|
||||
dayjs: ^1.10.6
|
||||
deep-diff: ^1.0.2
|
||||
design-system: "npm:@appsmithorg/design-system@2.1.9"
|
||||
|
|
@ -13203,12 +13203,12 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cypress-real-events@npm:^1.7.1":
|
||||
version: 1.7.1
|
||||
resolution: "cypress-real-events@npm:1.7.1"
|
||||
"cypress-real-events@npm:^1.8.1":
|
||||
version: 1.8.1
|
||||
resolution: "cypress-real-events@npm:1.8.1"
|
||||
peerDependencies:
|
||||
cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x
|
||||
checksum: b31c2facfa03e01e298926cd0925260b12474770fc1a3ce8998da21818db7e6d9fc2f9eb60d1771aa4ce3c29aca63d04da21e1a63e3bb117b1506a72ab0c3eb1
|
||||
cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x
|
||||
checksum: 48f229335f26f8c225428563e94ca091ab7a00e943a4c953e062135b1d37672edec9cb7667452d894b43528b52924cd1b6061df044ccb0665b9c0965a0fe214f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -13232,10 +13232,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cypress-xpath@npm:^1.4.0":
|
||||
version: 1.6.0
|
||||
resolution: "cypress-xpath@npm:1.6.0"
|
||||
checksum: 67cc5613d70a090b1a162d31d0ef11831576592c87ca07d242e2c07a6df2fca413fc81ea7df148a0d0c888025bb7ec0dad34684f2a322eeaa2863b8f3b65dd32
|
||||
"cypress-xpath@npm:^1.6.0":
|
||||
version: 1.8.0
|
||||
resolution: "cypress-xpath@npm:1.8.0"
|
||||
checksum: 11792ec46b898f2e00ced81dedcdca8e30d5c232778780210423d1ec0b07cd8b2a29a475d38eadaf9fd7e962efec7f5cee17f92884c308636cfa506efe6017e6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user