Sub tree evaluation (#1841)

Co-authored-by: Trisha Anand <trisha@appsmith.com>
Co-authored-by: Piyush Mishra <piyush@codeitout.com>
Co-authored-by: Nikhil Nandagopal <nikhil@appsmith.com>
Co-authored-by: Akash N <akash@codemonk.in>
This commit is contained in:
Hetu Nandu 2021-01-04 15:46:08 +05:30 committed by GitHub
parent bb81d3501a
commit 4387200262
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3276 additions and 2233 deletions

View File

@ -38,6 +38,7 @@
"env": {
"browser": true,
"node": true,
"cypress/globals": true
"cypress/globals": true,
"worker": true
}
}

View File

@ -126,12 +126,10 @@
"label": "",
"options": [
{
"id": "1",
"label":"Male",
"value":"M"
},
{
"id": "2",
"label":"Female",
"value":"F"
}

View File

@ -87,90 +87,6 @@
"userName": "Tobias Funke",
"productName": "Beef steak",
"orderAmount": 19.99
},
{
"id": 7434532,
"email": "byron.fields@reqres.in",
"userName": "Byron Fields",
"productName": "Chicken Sandwich",
"orderAmount": 4.99
},
{
"id": 7434532,
"email": "ryan.holmes@reqres.in",
"userName": "Ryan Holmes",
"productName": "Avocado Panini",
"orderAmount": 7.99
},
{
"id": 2381224,
"email": "michael.lawson@reqres.in",
"userName": "Michael Lawson",
"productName": "Chicken Sandwich",
"orderAmount": 4.99
},
{
"id": 2736212,
"email": "lindsay.ferguson@reqres.in",
"userName": "Lindsay Ferguson",
"productName": "Tuna Salad",
"orderAmount": 9.99
},
{
"id": 6788734,
"email": "tobias.funke@reqres.in",
"userName": "Tobias Funke",
"productName": "Beef steak",
"orderAmount": 19.99
},
{
"id": 7434532,
"email": "byron.fields@reqres.in",
"userName": "Byron Fields",
"productName": "Chicken Sandwich",
"orderAmount": 4.99
},
{
"id": 7434532,
"email": "ryan.holmes@reqres.in",
"userName": "Ryan Holmes",
"productName": "Avocado Panini",
"orderAmount": 7.99
},
{
"id": 2381224,
"email": "michael.lawson@reqres.in",
"userName": "Michael Lawson",
"productName": "Chicken Sandwich",
"orderAmount": 4.99
},
{
"id": 2736212,
"email": "lindsay.ferguson@reqres.in",
"userName": "Lindsay Ferguson",
"productName": "Tuna Salad",
"orderAmount": 9.99
},
{
"id": 6788734,
"email": "tobias.funke@reqres.in",
"userName": "Tobias Funke",
"productName": "Beef steak",
"orderAmount": 19.99
},
{
"id": 7434532,
"email": "byron.fields@reqres.in",
"userName": "Byron Fields",
"productName": "Chicken Sandwich",
"orderAmount": 4.99
},
{
"id": 7434532,
"email": "ryan.holmes@reqres.in",
"userName": "Ryan Holmes",
"productName": "Avocado Panini",
"orderAmount": 7.99
}
],
"addInputWidgetBinding": "{{Table1.selectedRow.id",

View File

@ -27,7 +27,7 @@ describe("Binding the Table and input Widget", function() {
.type("2736212", { force: true });
cy.get(commonlocators.editPropCrossButton).click();
cy.wait("@updateLayout").isSelectRow(0);
cy.readTabledataPublish("0", "0").then(tabData => {
cy.readTabledataPublish("0", "0").then((tabData) => {
const tabValue = tabData;
expect(tabValue).to.be.equal("2736212");
cy.log("the value is" + tabValue);

View File

@ -6,7 +6,7 @@ describe("Create org and a new app / delete and recreate app", function() {
it("create app within an org and delete and re-create another app with same name", function() {
cy.NavigateToHome();
cy.generateUUID().then(uid => {
cy.generateUUID().then((uid) => {
orgid = uid;
appid = uid;
localStorage.setItem("OrgName", orgid);

View File

@ -6,7 +6,7 @@ describe("Create new org and an app within the same", function() {
it("create multiple apps and validate", function() {
cy.NavigateToHome();
cy.generateUUID().then(uid => {
cy.generateUUID().then((uid) => {
orgid = uid;
appid = uid;
localStorage.setItem("OrgName", orgid);

View File

@ -8,7 +8,7 @@ describe("Create new org and share with a user", function () {
it("create org and then share with a user from UI", function() {
cy.NavigateToHome();
cy.generateUUID().then(uid => {
cy.generateUUID().then((uid) => {
orgid = uid;
appid = uid;
localStorage.setItem("OrgName", orgid);

View File

@ -1,9 +1,7 @@
const homePage = require("../../../locators/HomePage.json");
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
it("Duplicating an application", function()
{
it("Duplicating an application", function() {
// Navigate to home Page
// Click on any application action icon (Three dots)
// Click on "Duplicate" option
@ -13,7 +11,5 @@ describe("Duplicate an application must duplicate every API ,Query widget and Da
// Click on Delete option
// Click on "Are You Sure?" option
// Ensure the App gets deleted
}
)
}
)
});
});

View File

@ -1,15 +1,11 @@
const homePage = require("../../../locators/HomePage.json");
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
it("Duplicating an application", function()
{
it("Duplicating an application", function() {
// Navigate to home Page
// Click on any application action icon (Three dots)
// Click on "Duplicate" option
// Ensure the application gets copied
// Ensure the name is appended with the word "Copy"
}
)
}
)
});
});

View File

@ -1,18 +1,14 @@
const homePage = require("../../../locators/HomePage.json");
describe("Duplicate an application must duplicate every API ,Query widget and Datasource", function() {
it("Duplicating an application", function()
{
it("Duplicating an application", function() {
// Navigate to home Page
// Click on any application action icon (Three dots)
// Click on "Duplicate" option
// Ensure the application gets copied
// Ensure the name is appended with the word "Copy"
}
)
it("Deleting the duplicated Application ", function()
{
});
it("Deleting the duplicated Application ", function() {
// Navigate to home Page
// Click on any application action icon (Three dots)
// Click on "Duplicate" option
@ -22,7 +18,5 @@ describe("Duplicate an application must duplicate every API ,Query widget and Da
// Click on Delete option
// Click on "Are You Sure?" option
// Ensure the App gets deleted
}
)
}
)
});
});

View File

@ -1,15 +1,11 @@
const homePage = require("../../../locators/HomePage.json");
describe("Checking for error message on Organisation Name ", function() {
it("Ensure of Inactive Submit button ", function()
{
it("Ensure of Inactive Submit button ", function() {
// Navigate to home Page
// Click on Create Organisation
// Type "Space" as first character
// Ensure "Submit" button does not get Active
// Now click on "X" (Close icon) ensure the pop up closes
}
)
}
)
});
});

View File

@ -1,18 +1,14 @@
const homePage = require("../../../locators/HomePage.json");
describe("Checking for error message on Organisation Name ", function() {
it("Ensure of Inactive Submit button ", function()
{
it("Ensure of Inactive Submit button ", function() {
// Navigate to home Page
// Click on Create Organisation
// Type "Space" as first character
// Ensure "Submit" button does not get Active
// Now click on "X" (Close icon) ensure the pop up closes
}
)
it("Reuse the name of the deleted application name ", function()
{
});
it("Reuse the name of the deleted application name ", function() {
// Navigate to home Page
// Create an Application by name "XYZ"
// Add some widgets
@ -21,16 +17,12 @@ describe("Checking for error message on Organisation Name ", function() {
// Click on "Create New" option under samee organisation
// Enter the name "XYZ"
// Ensure the application can be created with the same name
}
)
it("Adding Special Character ", function()
{
});
it("Adding Special Character ", function() {
// Navigate to home Page
// Click on Create Organisation
// Add special as first character
// Ensure "Submit" get Active
// Now click outside and ensure the pop up closes
}
)
}
)
});
});

View File

@ -1,9 +1,7 @@
const homePage = require("../../../locators/HomePage.json");
describe("Reuse the name of the deleted application name inside the same organisation", function() {
it("Reuse the name of the deleted application name ", function()
{
it("Reuse the name of the deleted application name ", function() {
// Navigate to home Page
// Create an Application by name "XYZ"
// Add some widgets
@ -12,7 +10,5 @@ describe("Reuse the name of the deleted application name inside the same organis
// Click on "Create New" option under samee organisation
// Enter the name "XYZ"
// Ensure the application can be created with the same name
}
)
}
)
});
});

View File

@ -1,15 +1,11 @@
const homePage = require("../../../locators/HomePage.json");
describe("Adding Special Character ", function() {
it("Adding Special Character ", function()
{
it("Adding Special Character ", function() {
// Navigate to home Page
// Click on Create Organisation
// Add special as first character
// Ensure "Submit" get Active
// Now click outside and ensure the pop up closes
}
)
}
)
});
});

View File

@ -20,7 +20,7 @@ const explorer = require("../locators/explorerlocators.json");
let pageidcopy = " ";
Cypress.Commands.add("createOrg", orgName => {
Cypress.Commands.add("createOrg", (orgName) => {
cy.get(homePage.createOrg)
.should("be.visible")
.first()
@ -45,7 +45,7 @@ Cypress.Commands.add(
},
);
Cypress.Commands.add("navigateToOrgSettings", orgName => {
Cypress.Commands.add("navigateToOrgSettings", (orgName) => {
cy.get(homePage.orgList.concat(orgName).concat(")"))
.scrollIntoView()
.should("be.visible");
@ -207,7 +207,7 @@ Cypress.Commands.add("updateUserRoleForOrg", (orgName, email, role) => {
);
});
Cypress.Commands.add("launchApp", appName => {
Cypress.Commands.add("launchApp", (appName) => {
cy.get(homePage.appView)
.should("be.visible")
.first()
@ -239,7 +239,7 @@ Cypress.Commands.add("CreateAppForOrg", (orgName, appname) => {
);
});
Cypress.Commands.add("CreateApp", appname => {
Cypress.Commands.add("CreateApp", (appname) => {
cy.get(homePage.createNew)
.first()
.click({ force: true });
@ -259,7 +259,7 @@ Cypress.Commands.add("CreateApp", appname => {
);
});
Cypress.Commands.add("DeleteApp", appName => {
Cypress.Commands.add("DeleteApp", (appName) => {
cy.get(commonlocators.homeIcon).click({ force: true });
cy.wait("@applications").should(
"have.nested.property",
@ -312,13 +312,13 @@ Cypress.Commands.add("LoginFromAPI", (uname, pword) => {
username: uname,
password: pword,
},
}).then(response => {
}).then((response) => {
expect(response.status).equal(302);
cy.log(response.body);
});
});
Cypress.Commands.add("DeleteApp", appName => {
Cypress.Commands.add("DeleteApp", (appName) => {
cy.get(commonlocators.homeIcon).click({ force: true });
cy.get(homePage.searchInput).type(appName);
cy.wait(2000);
@ -360,7 +360,7 @@ Cypress.Commands.add("NavigateToHome", () => {
);
});
Cypress.Commands.add("NavigateToWidgets", pageName => {
Cypress.Commands.add("NavigateToWidgets", (pageName) => {
cy.get(pages.pagesIcon).click({ force: true });
cy.get(".t--page-sidebar-" + pageName + "")
.find(">div")
@ -371,7 +371,7 @@ Cypress.Commands.add("NavigateToWidgets", pageName => {
cy.get("#loading").should("not.exist");
});
Cypress.Commands.add("SearchApp", appname => {
Cypress.Commands.add("SearchApp", (appname) => {
cy.get(homePage.searchInput).type(appname);
cy.wait(2000);
cy.get(homePage.applicationCard)
@ -398,7 +398,7 @@ Cypress.Commands.add("SearchEntity", (apiname1, apiname2) => {
).should("not.be.visible");
});
Cypress.Commands.add("GlobalSearchEntity", apiname1 => {
Cypress.Commands.add("GlobalSearchEntity", (apiname1) => {
cy.get(commonlocators.entityExplorersearch).should("be.visible");
cy.get(commonlocators.entityExplorersearch)
.clear()
@ -409,12 +409,12 @@ Cypress.Commands.add("GlobalSearchEntity", apiname1 => {
).should("be.visible");
});
Cypress.Commands.add("ResponseStatusCheck", statusCode => {
Cypress.Commands.add("ResponseStatusCheck", (statusCode) => {
cy.xpath(apiwidget.responseStatus).should("be.visible");
cy.xpath(apiwidget.responseStatus).contains(statusCode);
});
Cypress.Commands.add("ResponseCheck", textTocheck => {
Cypress.Commands.add("ResponseCheck", (textTocheck) => {
//Explicit assert
cy.get(apiwidget.responseText).should("be.visible");
});
@ -433,7 +433,7 @@ Cypress.Commands.add("NavigateToEntityExplorer", () => {
cy.get("#loading").should("not.exist");
});
Cypress.Commands.add("CreateAPI", apiname => {
Cypress.Commands.add("CreateAPI", (apiname) => {
cy.get(apiwidget.createapi).click({ force: true });
cy.wait("@createNewApi");
cy.get(apiwidget.resourceUrl).should("be.visible");
@ -449,7 +449,7 @@ Cypress.Commands.add("CreateAPI", apiname => {
cy.wait(2000);
});
Cypress.Commands.add("CreateSubsequentAPI", apiname => {
Cypress.Commands.add("CreateSubsequentAPI", (apiname) => {
cy.get(apiwidget.createApiOnSideBar)
.first()
.click({ force: true });
@ -462,7 +462,7 @@ Cypress.Commands.add("CreateSubsequentAPI", apiname => {
cy.WaitAutoSave();
});
Cypress.Commands.add("EditApiName", apiname => {
Cypress.Commands.add("EditApiName", (apiname) => {
cy.get(apiwidget.ApiName).click({ force: true });
cy.get(apiwidget.apiTxt)
.clear()
@ -471,7 +471,7 @@ Cypress.Commands.add("EditApiName", apiname => {
cy.WaitAutoSave();
});
Cypress.Commands.add("EditApiNameFromExplorer", apiname => {
Cypress.Commands.add("EditApiNameFromExplorer", (apiname) => {
cy.xpath(apiwidget.popover)
.last()
.click({ force: true });
@ -530,7 +530,7 @@ Cypress.Commands.add("validateRequest", (baseurl, path, verb) => {
.click({ force: true });
});
Cypress.Commands.add("SelectAction", action => {
Cypress.Commands.add("SelectAction", (action) => {
cy.get(ApiEditor.ApiVerb)
.first()
.click({ force: true });
@ -565,7 +565,7 @@ Cypress.Commands.add(
},
);
Cypress.Commands.add("SearchEntityandOpen", apiname1 => {
Cypress.Commands.add("SearchEntityandOpen", (apiname1) => {
cy.get(commonlocators.entityExplorersearch).should("be.visible");
cy.get(commonlocators.entityExplorersearch)
.clear()
@ -595,7 +595,7 @@ Cypress.Commands.add("enterDatasourceAndPath", (datasource, path) => {
.type(path, { parseSpecialCharSequences: false });
});
Cypress.Commands.add("changeZoomLevel", zoomValue => {
Cypress.Commands.add("changeZoomLevel", (zoomValue) => {
cy.get(commonlocators.changeZoomlevel).click();
cy.get("ul.bp3-menu")
.children()
@ -609,7 +609,7 @@ Cypress.Commands.add("changeZoomLevel", zoomValue => {
cy.get(commonlocators.selectedZoomlevel)
.first()
.invoke("text")
.then(text => {
.then((text) => {
const someText = text;
expect(someText).to.equal(zoomValue);
});
@ -663,14 +663,14 @@ Cypress.Commands.add("switchToAPIInputTab", () => {
.click({ force: true });
});
Cypress.Commands.add("selectPaginationType", option => {
Cypress.Commands.add("selectPaginationType", (option) => {
cy.get(apiwidget.paginationOption)
.first()
.click({ force: true });
cy.xpath(option).click({ force: true });
});
Cypress.Commands.add("clickTest", testbutton => {
Cypress.Commands.add("clickTest", (testbutton) => {
cy.wait(2000);
cy.wait("@saveAction");
cy.get(testbutton)
@ -726,7 +726,7 @@ Cypress.Commands.add(
},
);
Cypress.Commands.add("CreationOfUniqueAPIcheck", apiname => {
Cypress.Commands.add("CreationOfUniqueAPIcheck", (apiname) => {
cy.get(pages.addEntityAPI).click();
cy.get(apiwidget.createapi).click({ force: true });
cy.wait("@createNewApi");
@ -738,13 +738,13 @@ Cypress.Commands.add("CreationOfUniqueAPIcheck", apiname => {
.type(apiname, { force: true })
.should("have.value", apiname)
.focus();
cy.get(".bp3-popover-content").should($x => {
cy.get(".bp3-popover-content").should(($x) => {
console.log($x);
expect($x).contain(apiname.concat(" is already being used."));
});
});
Cypress.Commands.add("MoveAPIToHome", apiname => {
Cypress.Commands.add("MoveAPIToHome", (apiname) => {
cy.xpath(apiwidget.popover)
.last()
.click({ force: true });
@ -757,7 +757,7 @@ Cypress.Commands.add("MoveAPIToHome", apiname => {
);
});
Cypress.Commands.add("MoveAPIToPage", pageName => {
Cypress.Commands.add("MoveAPIToPage", (pageName) => {
cy.xpath(apiwidget.popover)
.last()
.click({ force: true });
@ -772,7 +772,7 @@ Cypress.Commands.add("MoveAPIToPage", pageName => {
);
});
Cypress.Commands.add("copyEntityToPage", pageName => {
Cypress.Commands.add("copyEntityToPage", (pageName) => {
cy.xpath(apiwidget.popover)
.last()
.click({ force: true });
@ -825,7 +825,7 @@ Cypress.Commands.add("deleteEntity", () => {
cy.get(apiwidget.delete).click({ force: true });
});
Cypress.Commands.add("DeleteAPI", apiname => {
Cypress.Commands.add("DeleteAPI", (apiname) => {
cy.get(apiwidget.deleteAPI)
.first()
.click({ force: true });
@ -896,14 +896,14 @@ Cypress.Commands.add("createModal", (modalType, ModalName) => {
cy.get(".bp3-overlay-backdrop").click({ force: true });
});
Cypress.Commands.add("CheckWidgetProperties", checkboxCss => {
Cypress.Commands.add("CheckWidgetProperties", (checkboxCss) => {
cy.get(checkboxCss).check({
force: true,
});
cy.assertPageSave();
});
Cypress.Commands.add("UncheckWidgetProperties", checkboxCss => {
Cypress.Commands.add("UncheckWidgetProperties", (checkboxCss) => {
cy.get(checkboxCss).uncheck({
force: true,
});
@ -933,13 +933,13 @@ Cypress.Commands.add("widgetText", (text, inputcss, innercss) => {
cy.get(innercss).should("have.text", text);
});
Cypress.Commands.add("EvaluateDataType", dataType => {
Cypress.Commands.add("EvaluateDataType", (dataType) => {
cy.get(commonlocators.evaluatedType)
.should("be.visible")
.contains(dataType);
});
Cypress.Commands.add("EvaluateCurrentValue", currentValue => {
Cypress.Commands.add("EvaluateCurrentValue", (currentValue) => {
cy.wait(2000);
cy.get(commonlocators.evaluatedCurrentValue)
.should("be.visible")
@ -954,8 +954,8 @@ Cypress.Commands.add("PublishtheApp", () => {
cy.assertPageSave();
// Stubbing window.open to open in the same tab
cy.window().then(window => {
cy.stub(window, "open").callsFake(url => {
cy.window().then((window) => {
cy.stub(window, "open").callsFake((url) => {
window.location.href = Cypress.config().baseUrl + url.substring(1);
window.location.target = "_self";
});
@ -976,12 +976,12 @@ Cypress.Commands.add("getCodeMirror", () => {
.type("{ctrl}{shift}{downarrow}");
});
Cypress.Commands.add("testCodeMirror", value => {
Cypress.Commands.add("testCodeMirror", (value) => {
cy.get(".CodeMirror textarea")
.first()
.focus()
.type("{ctrl}{shift}{downarrow}")
.then($cm => {
.then(($cm) => {
if ($cm.val() !== "") {
cy.get(".CodeMirror textarea")
.first()
@ -1009,7 +1009,7 @@ Cypress.Commands.add("testJsontext", (endp, value) => {
.focus({ force: true })
.type("{uparrow}", { force: true })
.type("{ctrl}{shift}{downarrow}", { force: true });
cy.focused().then($cm => {
cy.focused().then(($cm) => {
if ($cm.contents != "") {
cy.log("The field is empty");
cy.get(".t--property-control-" + endp + " .CodeMirror textarea")
@ -1028,14 +1028,14 @@ Cypress.Commands.add("testJsontext", (endp, value) => {
cy.wait(200);
});
Cypress.Commands.add("selectShowMsg", value => {
Cypress.Commands.add("selectShowMsg", (value) => {
cy.get(commonlocators.chooseAction)
.children()
.contains("Show Message")
.click();
});
Cypress.Commands.add("addSuccessMessage", value => {
Cypress.Commands.add("addSuccessMessage", (value) => {
cy.get(commonlocators.chooseMsgType)
.last()
.click();
@ -1052,12 +1052,12 @@ Cypress.Commands.add("SetDateToToday", () => {
cy.assertPageSave();
});
Cypress.Commands.add("enterActionValue", value => {
Cypress.Commands.add("enterActionValue", (value) => {
cy.get(".CodeMirror textarea")
.last()
.focus()
.type("{ctrl}{shift}{downarrow}")
.then($cm => {
.then(($cm) => {
if ($cm.val() !== "") {
cy.get(".CodeMirror textarea")
.last()
@ -1079,7 +1079,7 @@ Cypress.Commands.add("enterActionValue", value => {
});
});
Cypress.Commands.add("enterNavigatePageName", value => {
Cypress.Commands.add("enterNavigatePageName", (value) => {
cy.get("ul.tree")
.children()
.first()
@ -1088,7 +1088,7 @@ Cypress.Commands.add("enterNavigatePageName", value => {
.first()
.focus()
.type("{ctrl}{shift}{downarrow}")
.then($cm => {
.then(($cm) => {
if ($cm.val() !== "") {
cy.get(".CodeMirror textarea")
.first()
@ -1125,7 +1125,7 @@ Cypress.Commands.add("DeleteModal", () => {
.click({ force: true });
});
Cypress.Commands.add("Createpage", Pagename => {
Cypress.Commands.add("Createpage", (Pagename) => {
cy.get(pages.pagesIcon).click({ force: true });
cy.get(pages.AddPage)
.first()
@ -1148,7 +1148,7 @@ Cypress.Commands.add("Createpage", Pagename => {
cy.wait(2000);
});
Cypress.Commands.add("Deletepage", Pagename => {
Cypress.Commands.add("Deletepage", (Pagename) => {
cy.get(pages.pagesIcon).click({ force: true });
cy.get(".t--page-sidebar-" + Pagename + "");
cy.get(
@ -1167,11 +1167,11 @@ Cypress.Commands.add("generateUUID", () => {
return id.split("-")[0];
});
Cypress.Commands.add("addDsl", dsl => {
Cypress.Commands.add("addDsl", (dsl) => {
let currentURL;
let pageid;
let layoutId;
cy.url().then(url => {
cy.url().then((url) => {
currentURL = url;
const myRegexp = /pages(.*)/;
const match = myRegexp.exec(currentURL);
@ -1180,7 +1180,7 @@ Cypress.Commands.add("addDsl", dsl => {
cy.log(pageid + "page id");
//Fetch the layout id
cy.server();
cy.request("GET", "api/v1/pages/" + pageid).then(response => {
cy.request("GET", "api/v1/pages/" + pageid).then((response) => {
const len = JSON.stringify(response.body);
cy.log(len);
layoutId = JSON.parse(len).data.layouts[0].id;
@ -1189,7 +1189,7 @@ Cypress.Commands.add("addDsl", dsl => {
"PUT",
"api/v1/layouts/" + layoutId + "/pages/" + pageid,
dsl,
).then(response => {
).then((response) => {
expect(response.status).equal(200);
cy.reload();
});
@ -1200,7 +1200,7 @@ Cypress.Commands.add("addDsl", dsl => {
Cypress.Commands.add("DeleteAppByApi", () => {
let currentURL;
let appId;
cy.url().then(url => {
cy.url().then((url) => {
currentURL = url;
const myRegexp = /applications(.*)/;
const match = myRegexp.exec(currentURL);
@ -1212,7 +1212,7 @@ Cypress.Commands.add("DeleteAppByApi", () => {
}
});
});
Cypress.Commands.add("togglebar", value => {
Cypress.Commands.add("togglebar", (value) => {
cy.get(value)
.check({ force: true })
.should("be.checked");
@ -1229,7 +1229,7 @@ Cypress.Commands.add("optionValue", (value, value2) => {
.clear()
.type(value2);
});
Cypress.Commands.add("dropdownDynamic", text => {
Cypress.Commands.add("dropdownDynamic", (text) => {
cy.wait(2000);
cy.get("ul[class='bp3-menu']")
.first()
@ -1238,7 +1238,7 @@ Cypress.Commands.add("dropdownDynamic", text => {
.should("have.text", text);
});
Cypress.Commands.add("getAlert", alertcss => {
Cypress.Commands.add("getAlert", (alertcss) => {
cy.get(commonlocators.dropdownSelectButton).click({ force: true });
cy.get(widgetsPage.menubar)
.contains("Show Alert")
@ -1268,18 +1268,18 @@ Cypress.Commands.add("tabVerify", (index, text) => {
.should("be.visible");
});
Cypress.Commands.add("togglebar", value => {
Cypress.Commands.add("togglebar", (value) => {
cy.get(value)
.check({ force: true })
.should("be.checked");
});
Cypress.Commands.add("togglebarDisable", value => {
Cypress.Commands.add("togglebarDisable", (value) => {
cy.get(value)
.uncheck({ force: true })
.should("not.checked");
});
Cypress.Commands.add("getAlert", alertcss => {
Cypress.Commands.add("getAlert", (alertcss) => {
cy.get(commonlocators.dropdownSelectButton).click({ force: true });
cy.get(widgetsPage.menubar)
.contains("Show Message")
@ -1473,7 +1473,7 @@ Cypress.Commands.add("createPostgresDatasource", () => {
cy.testSaveDatasource();
});
Cypress.Commands.add("deleteDatasource", datasourceName => {
Cypress.Commands.add("deleteDatasource", (datasourceName) => {
cy.NavigateToQueryEditor();
cy.contains(".t--datasource-name", datasourceName)
@ -1553,7 +1553,7 @@ Cypress.Commands.add("dragAndDropToCanvas", (widgetType, { x, y }) => {
.trigger("mouseup", { force: true });
});
Cypress.Commands.add("executeDbQuery", queryName => {
Cypress.Commands.add("executeDbQuery", (queryName) => {
cy.get(widgetsPage.buttonOnClick)
.get(commonlocators.dropdownSelectButton)
.click({ force: true })
@ -1567,7 +1567,7 @@ Cypress.Commands.add("executeDbQuery", queryName => {
.click({ force: true });
});
Cypress.Commands.add("openPropertyPane", widgetType => {
Cypress.Commands.add("openPropertyPane", (widgetType) => {
const selector = `.t--draggable-${widgetType}`;
cy.get(selector)
.first()
@ -1585,13 +1585,13 @@ Cypress.Commands.add("closePropertyPane", () => {
Cypress.Commands.add("createAndFillApi", (url, parameters) => {
cy.NavigateToApiEditor();
cy.testCreateApiButton();
cy.get("@createNewApi").then(response => {
cy.get("@createNewApi").then((response) => {
cy.get(ApiEditor.ApiNameField).should("be.visible");
cy.expect(response.response.body.responseMeta.success).to.eq(true);
cy.get(ApiEditor.ApiNameField)
.click()
.invoke("text")
.then(text => {
.then((text) => {
const someText = text;
expect(someText).to.equal(response.response.body.data.name);
});
@ -1612,7 +1612,7 @@ Cypress.Commands.add("createAndFillApi", (url, parameters) => {
cy.get(ApiEditor.ApiRunBtn).should("not.be.disabled");
});
Cypress.Commands.add("isSelectRow", index => {
Cypress.Commands.add("isSelectRow", (index) => {
cy.get(
'.tbody .td[data-rowindex="' + index + '"][data-colindex="' + 0 + '"]',
).click({ force: true });
@ -1640,13 +1640,13 @@ Cypress.Commands.add("setDate", (date, dateFormate) => {
cy.get(sel).click();
});
Cypress.Commands.add("pageNo", index => {
Cypress.Commands.add("pageNo", (index) => {
cy.get(".page-item")
.first()
.click({ force: true });
});
Cypress.Commands.add("pageNoValidate", index => {
Cypress.Commands.add("pageNoValidate", (index) => {
const data = '.e-numericcontainer a[index="' + index + '"]';
const pageVal = cy.get(data);
return pageVal;
@ -1661,7 +1661,7 @@ Cypress.Commands.add("validateEnableWidget", (widgetCss, disableCss) => {
});
Cypress.Commands.add("validateHTMLText", (widgetCss, htmlTag, value) => {
cy.get(widgetCss + " iframe").then($iframe => {
cy.get(widgetCss + " iframe").then(($iframe) => {
const $body = $iframe.contents().find("body");
cy.wrap($body)
.find(htmlTag)
@ -1747,7 +1747,7 @@ Cypress.Commands.add("startServerAndRoutes", () => {
cy.route("DELETE", "/api/v1/organizations/*/logo").as("deleteLogo");
});
Cypress.Commands.add("alertValidate", text => {
Cypress.Commands.add("alertValidate", (text) => {
cy.get(commonlocators.success)
.should("be.visible")
.and("have.text", text);
@ -1777,7 +1777,7 @@ Cypress.Commands.add("scrollTabledataPublish", (rowNum, colNum) => {
return tabVal;
});
Cypress.Commands.add("assertEvaluatedValuePopup", expectedType => {
Cypress.Commands.add("assertEvaluatedValuePopup", (expectedType) => {
cy.get(dynamicInputLocators.evaluatedValue)
.should("be.visible")
.children("p")
@ -1787,7 +1787,7 @@ Cypress.Commands.add("assertEvaluatedValuePopup", expectedType => {
.should("have.text", expectedType);
});
Cypress.Commands.add("validateToastMessage", value => {
Cypress.Commands.add("validateToastMessage", (value) => {
cy.get(commonlocators.toastMsg).should("have.text", value);
});
@ -1802,23 +1802,23 @@ Cypress.Commands.add("NavigateToPaginationTab", () => {
.type("{enter}");
});
Cypress.Commands.add("ValidateTableData", value => {
Cypress.Commands.add("ValidateTableData", (value) => {
// cy.isSelectRow(0);
cy.readTabledata("0", "0").then(tabData => {
cy.readTabledata("0", "0").then((tabData) => {
const tableData = tabData;
expect(tableData).to.equal(value.toString());
});
});
Cypress.Commands.add("ValidatePublishTableData", value => {
Cypress.Commands.add("ValidatePublishTableData", (value) => {
cy.isSelectRow(0);
cy.readTabledataPublish("0", "0").then(tabData => {
cy.readTabledataPublish("0", "0").then((tabData) => {
const tableData = tabData;
expect(tableData).to.equal(value);
});
});
Cypress.Commands.add("ValidatePaginateResponseUrlData", runTestCss => {
Cypress.Commands.add("ValidatePaginateResponseUrlData", (runTestCss) => {
cy.SearchEntityandOpen("Api2");
cy.NavigateToPaginationTab();
cy.RunAPI();
@ -1832,7 +1832,7 @@ Cypress.Commands.add("ValidatePaginateResponseUrlData", runTestCss => {
.contains("name")
.siblings("span")
.invoke("text")
.then(tabData => {
.then((tabData) => {
const respBody = tabData.match(/"(.*)"/)[0];
localStorage.setItem("respBody", respBody);
cy.log(respBody);
@ -1840,7 +1840,7 @@ Cypress.Commands.add("ValidatePaginateResponseUrlData", runTestCss => {
// cy.openPropertyPane("tablewidget");
// cy.testJsontext("tabledata", "{{Api2.data.results}}");
cy.isSelectRow(0);
cy.readTabledata("0", "1").then(tabData => {
cy.readTabledata("0", "1").then((tabData) => {
const tableData = tabData;
expect(`\"${tableData}\"`).to.equal(respBody);
});
@ -1849,13 +1849,13 @@ Cypress.Commands.add("ValidatePaginateResponseUrlData", runTestCss => {
Cypress.Commands.add("ValidatePaginationInputData", () => {
cy.isSelectRow(0);
cy.readTabledataPublish("0", "1").then(tabData => {
cy.readTabledataPublish("0", "1").then((tabData) => {
const tableData = tabData;
expect(`\"${tableData}\"`).to.equal(localStorage.getItem("respBody"));
});
});
Cypress.Commands.add("callApi", apiname => {
Cypress.Commands.add("callApi", (apiname) => {
cy.get(commonlocators.callApi)
.first()
.click();

View File

@ -50,6 +50,7 @@
"copy-to-clipboard": "^3.3.1",
"craco-alias": "^2.1.1",
"cypress-log-to-output": "^1.1.2",
"deep-diff": "^1.0.2",
"downloadjs": "^1.4.7",
"eslint": "^7.11.0",
"fast-deep-equal": "^3.1.1",
@ -166,6 +167,7 @@
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.0.4",
"@types/codemirror": "^0.0.96",
"@types/deep-diff": "^1.0.0",
"@types/downloadjs": "^1.4.2",
"@types/jest": "^24.0.22",
"@types/react-beautiful-dnd": "^11.0.4",

View File

@ -78,7 +78,7 @@ export interface ActionApiResponse {
}
export interface ActionResponse {
body: Record<string, unknown>;
body: unknown;
headers: Record<string, string[]>;
request?: ActionApiResponseReq;
statusCode: string;

View File

@ -50,7 +50,6 @@ export const ReduxActionTypes: { [key: string]: string } = {
WIDGET_MOVE: "WIDGET_MOVE",
WIDGET_RESIZE: "WIDGET_RESIZE",
WIDGET_DELETE: "WIDGET_DELETE",
WIDGETS_LOADING: "WIDGETS_LOADING",
SHOW_PROPERTY_PANE: "SHOW_PROPERTY_PANE",
UPDATE_WIDGET_PROPERTY_REQUEST: "UPDATE_WIDGET_PROPERTY_REQUEST",
UPDATE_WIDGET_PROPERTY: "UPDATE_WIDGET_PROPERTY",
@ -307,11 +306,13 @@ export const ReduxActionTypes: { [key: string]: string } = {
CUT_SELECTED_WIDGET: "CUT_SELECTED_WIDGET",
WIDGET_ADD_CHILDREN: "WIDGET_ADD_CHILDREN",
SET_EVALUATED_TREE: "SET_EVALUATED_TREE",
SET_EVALUATION_DEPENDENCY_MAP: "SET_EVALUATION_DEPENDENCY_MAP",
BATCH_UPDATES_SUCCESS: "BATCH_UPDATES_SUCCESS",
UPDATE_CANVAS_STRUCTURE: "UPDATE_CANVAS_STRUCTURE",
SET_SELECTED_WIDGET_ANCESTORY: "SET_SELECTED_WIDGET_ANCESTORY",
START_EVALUATION: "START_EVALUATION",
CURRENT_APPLICATION_NAME_UPDATE: "CURRENT_APPLICATION_NAME_UPDATE",
SET_WIDGET_LOADING: "SET_WIDGET_LOADING",
};
export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes];

View File

@ -126,6 +126,9 @@ export class DataTreeFactory {
const derivedPropertyMap = WidgetFactory.getWidgetDerivedPropertiesMap(
widget.type,
);
const defaultProps = WidgetFactory.getWidgetDefaultPropertiesMap(
widget.type,
);
const derivedProps: any = {};
const dynamicBindingPathList = getEntityDynamicBindingPathList(widget);
dynamicBindingPathList.forEach((dynamicPath) => {
@ -137,6 +140,7 @@ export class DataTreeFactory {
}
});
Object.keys(derivedPropertyMap).forEach((propertyName) => {
// TODO regex is too greedy
derivedProps[propertyName] = derivedPropertyMap[propertyName].replace(
/this./g,
`${widget.widgetName}.`,
@ -145,11 +149,18 @@ export class DataTreeFactory {
key: propertyName,
});
});
const unInitializedDefaultProps: Record<string, undefined> = {};
Object.values(defaultProps).forEach((propertyName) => {
if (!(propertyName in widget)) {
unInitializedDefaultProps[propertyName] = undefined;
}
});
dataTree[widget.widgetName] = {
...widget,
...defaultMetaProps,
...widgetMetaProps,
...derivedProps,
...unInitializedDefaultProps,
dynamicBindingPathList,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
};

View File

@ -14,6 +14,7 @@ export type FlattenedWidgetProps = WidgetProps & {
};
const canvasWidgetsReducer = createImmerReducer(initialState, {
// TODO Rename to INIT_LAYOUT
[ReduxActionTypes.UPDATE_CANVAS]: (
state: CanvasWidgetsReduxState,
action: ReduxAction<UpdateCanvasPayload>,

View File

@ -1,21 +1,30 @@
import { createReducer } from "utils/AppsmithUtils";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { DependencyMap } from "../../utils/DynamicBindingUtils";
export type EvaluationDependencyState = {
dependencyMap: Record<string, Array<string>>;
dependencyMap: DependencyMap;
inverseDependencyMap: DependencyMap;
dependencyTree: Array<[string, string]>;
};
const initialState: EvaluationDependencyState = {
dependencyMap: {},
inverseDependencyMap: {},
dependencyTree: [],
};
const evaluationDependencyReducer = createReducer(initialState, {
[ReduxActionTypes.SET_EVALUATION_DEPENDENCIES]: (
[ReduxActionTypes.SET_EVALUATION_DEPENDENCY_MAP]: (
state: EvaluationDependencyState,
action: ReduxAction<EvaluationDependencyState>,
) => action.payload,
action: ReduxAction<{
dependencyMap: DependencyMap;
inverseDependencyMap: DependencyMap;
}>,
): EvaluationDependencyState => ({
...state,
...action.payload,
}),
});
export default evaluationDependencyReducer;

View File

@ -11,6 +11,7 @@ const evaluatedTreeReducer = createImmerReducer(initialState, {
state: EvaluatedTreeState,
action: ReduxAction<DataTree>,
) => action.payload,
[ReduxActionTypes.FETCH_PAGE_INIT]: () => initialState,
});
export default evaluatedTreeReducer;

View File

@ -81,7 +81,7 @@ import {
getAppMode,
getCurrentApplication,
} from "selectors/applicationSelectors";
import { evaluateDynamicTrigger, evaluateSingleValue } from "./evaluationsSaga";
import { evaluateDynamicTrigger, evaluateSingleValue } from "./EvaluationsSaga";
function* navigateActionSaga(
action: { pageNameOrUrl: string; params: Record<string, string> },

View File

@ -1,4 +1,11 @@
import { actionChannel, call, put, select, take } from "redux-saga/effects";
import {
actionChannel,
call,
fork,
put,
select,
take,
} from "redux-saga/effects";
import {
EvaluationReduxAction,
@ -19,7 +26,6 @@ import {
EvalErrorTypes,
} from "../utils/DynamicBindingUtils";
import log from "loglevel";
import _ from "lodash";
import { WidgetType } from "../constants/WidgetConstants";
import { WidgetProps } from "../widgets/BaseWidget";
import PerformanceTracker, {
@ -70,26 +76,29 @@ function* evaluateTreeSaga(postEvalActions?: ReduxAction<unknown>[]) {
PerformanceTracker.startAsyncTracking(
PerformanceTransactionName.DATA_TREE_EVALUATION,
);
const unEvalTree = yield select(getUnevaluatedDataTree);
log.debug({ unEvalTree });
const unevalTree = yield select(getUnevaluatedDataTree);
log.debug({ unevalTree });
const workerResponse = yield call(
worker.request,
EVAL_WORKER_ACTIONS.EVAL_TREE,
{
dataTree: unEvalTree,
unevalTree,
widgetTypeConfigMap,
},
);
const { errors, dataTree, logs } = workerResponse;
const parsedDataTree = JSON.parse(dataTree);
const { errors, dataTree, dependencies, logs } = workerResponse;
log.debug({ dataTree: dataTree });
logs.forEach((evalLog: any) => log.debug(evalLog));
log.debug({ dataTree: parsedDataTree });
evalErrorHandler(errors);
yield put({
type: ReduxActionTypes.SET_EVALUATED_TREE,
payload: parsedDataTree,
payload: dataTree,
});
yield put({
type: ReduxActionTypes.SET_EVALUATION_DEPENDENCY_MAP,
payload: dependencies,
});
PerformanceTracker.stopAsyncTracking(
PerformanceTransactionName.DATA_TREE_EVALUATION,
@ -173,6 +182,7 @@ export function* validateProperty(
props: WidgetProps,
) {
return yield call(worker.request, EVAL_WORKER_ACTIONS.VALIDATE_PROPERTY, {
widgetTypeConfigMap,
widgetType,
property,
value,
@ -223,19 +233,18 @@ const EVALUATE_REDUX_ACTIONS = [
];
function evalQueueBuffer() {
let initialised = false;
let takable = false;
let canTake = false;
let postEvalActions: any = [];
const take = () => {
if (takable) {
if (canTake) {
const resp = postEvalActions;
postEvalActions = [];
takable = false;
return { postEvalActions: resp, type: "FAKE_ACTION" };
canTake = false;
return { postEvalActions: resp, type: "BUFFERED_ACTION" };
}
};
const flush = () => {
if (takable) {
if (canTake) {
return [take() as Action];
}
@ -243,34 +252,7 @@ function evalQueueBuffer() {
};
const put = (action: EvaluationReduxAction<unknown | unknown[]>) => {
if (!initialised) {
if (
![
ReduxActionTypes.FETCH_PAGE_SUCCESS,
ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
].includes(action.type)
) {
return;
}
initialised = true;
}
// When batching success action happens, we need to only evaluate
// if the batch had any action we need to evaluate properties for
if (
action.type === ReduxActionTypes.BATCH_UPDATES_SUCCESS &&
Array.isArray(action.payload)
) {
const batchedActionTypes = action.payload.map(
(batchedAction: ReduxAction<unknown>) => batchedAction.type,
);
if (
_.intersection(EVALUATE_REDUX_ACTIONS, batchedActionTypes).length === 0
) {
return;
}
}
takable = true;
canTake = true;
// TODO: If the action is the same as before, we can send only one and ignore duplicates.
if (action.postEvalActions) {
postEvalActions.push(...action.postEvalActions);
@ -281,7 +263,7 @@ function evalQueueBuffer() {
take,
put,
isEmpty: () => {
return !takable;
return !canTake;
},
flush,
};
@ -292,6 +274,8 @@ function* evaluationChangeListenerSaga() {
yield call(worker.shutdown);
yield call(worker.start);
widgetTypeConfigMap = WidgetFactory.getWidgetTypeConfigMap();
const initAction = yield take(FIRST_EVAL_REDUX_ACTIONS);
yield fork(evaluateTreeSaga, initAction.postEvalActions);
const evtActionChannel = yield actionChannel(
EVALUATE_REDUX_ACTIONS,
evalQueueBuffer(),

View File

@ -128,7 +128,11 @@ function* listenForSuccessfullBinding() {
if (widgetProperties.invalidProps) {
bindSuccessfull =
bindSuccessfull && !("tableData" in widgetProperties.invalidProps);
bindSuccessfull &&
!(
"tableData" in widgetProperties.invalidProps &&
widgetProperties.invalidProps.tableData
);
}
if (bindSuccessfull) {

View File

@ -70,7 +70,7 @@ import {
setActionsToExecuteOnPageLoad,
} from "actions/actionActions";
import { APP_MODE, UrlDataState } from "reducers/entityReducers/appReducer";
import { clearEvalCache } from "./evaluationsSaga";
import { clearEvalCache } from "./EvaluationsSaga";
import { getQueryParams } from "utils/AppsmithUtils";
import PerformanceTracker, {
PerformanceTransactionName,

View File

@ -49,7 +49,7 @@ import {
isPathADynamicTrigger,
} from "utils/DynamicBindingUtils";
import { WidgetProps } from "widgets/BaseWidget";
import _, { isString } from "lodash";
import _ from "lodash";
import WidgetFactory from "utils/WidgetFactory";
import {
buildWidgetBlueprint,
@ -88,7 +88,7 @@ import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import {
validateProperty,
clearEvalPropertyCacheOfWidget,
} from "./evaluationsSaga";
} from "./EvaluationsSaga";
import { WidgetBlueprint } from "reducers/entityReducers/widgetConfigReducer";
import { Toaster } from "components/ads/Toast";
import { Variant } from "components/ads/common";

View File

@ -19,9 +19,8 @@ import queryPaneSagas from "./QueryPaneSagas";
import modalSagas from "./ModalSagas";
import batchSagas from "./BatchSagas";
import themeSagas from "./ThemeSaga";
import evaluationsSaga from "./evaluationsSaga";
import evaluationsSaga from "./EvaluationsSaga";
import onboardingSaga from "./OnboardingSagas";
import log from "loglevel";
import * as sentry from "@sentry/react";

View File

@ -33,6 +33,12 @@ export const getUnevaluatedDataTree = createSelector(
},
);
export const getEvaluationDependencyMap = (state: AppState) =>
state.evaluations.dependencies.dependencyMap;
export const getEvaluationInverseDependencyMap = (state: AppState) =>
state.evaluations.dependencies.inverseDependencyMap;
/**
* returns evaluation tree object
*

View File

@ -123,9 +123,9 @@ export const getCanvasWidgetDsl = createSelector(
const widgets: Record<string, DataTreeWidget> = {};
Object.keys(canvasWidgets).forEach((widgetKey) => {
const canvasWidget = canvasWidgets[widgetKey];
const evaluatedWidget = evaluatedDataTree[
canvasWidget.widgetName
] as DataTreeWidget;
const evaluatedWidget = _.find(evaluatedDataTree, {
widgetId: widgetKey,
}) as DataTreeWidget;
if (evaluatedWidget) {
widgets[widgetKey] = createCanvasWidget(canvasWidget, evaluatedWidget);
} else {

View File

@ -8,6 +8,8 @@ import moment from "moment-timezone";
import { WidgetProps } from "../widgets/BaseWidget";
import parser from "fast-xml-parser";
export type DependencyMap = Record<string, Array<string>>;
export const removeBindingsFromActionObject = (obj: Action) => {
const string = JSON.stringify(obj);
const withBindings = string.replace(DATA_BIND_REGEX_GLOBAL, "{{ }}");
@ -87,6 +89,7 @@ export enum EvalErrorTypes {
EVAL_TREE_ERROR = "EVAL_TREE_ERROR",
UNESCAPE_STRING_ERROR = "UNESCAPE_STRING_ERROR",
EVAL_ERROR = "EVAL_ERROR",
UNKNOWN_ERROR = "UNKNOWN_ERROR",
BAD_UNEVAL_TREE_ERROR = "BAD_UNEVAL_TREE_ERROR",
}

View File

@ -1,213 +1,32 @@
// import {
// mockExecute,
// mockRegisterLibrary,
// } from "../../test/__mocks__/RealmExecutorMock";
// import {
// dependencySortedEvaluateDataTree,
// getDynamicValue,
// getEntityDependencies,
// parseDynamicString,
// } from "./DynamicBindingUtils";
// import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
// import { RenderModes, WidgetTypes } from "constants/WidgetConstants";
//
// beforeAll(() => {
// mockRegisterLibrary.mockClear();
// mockExecute.mockClear();
// });
//
// it("Gets the value from the data tree", () => {
// const dynamicBinding = "{{GetUsers.data}}";
// const nameBindingsWithData: DataTree = {
// GetUsers: {
// data: { text: "correct data" },
// config: {
// pluginId: "",
// id: "id",
// name: "text",
// actionConfiguration: {},
// pageId: "",
// jsonPathKeys: [],
// datasource: { id: "" },
// pluginType: "1",
// },
// isLoading: false,
// ENTITY_TYPE: ENTITY_TYPE.ACTION,
// run: jest.fn(),
// },
// };
// const actualValue = { result: { text: "correct data" } };
//
// const value = getDynamicValue(dynamicBinding, nameBindingsWithData);
//
// expect(value).toEqual(actualValue);
// });
//
// describe.each([
// ["{{A}}", ["{{A}}"]],
// ["A {{B}}", ["A ", "{{B}}"]],
// [
// "Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
// [
// "Hello ",
// "{{Customer.Name}}",
// ", the status for your order id ",
// "{{orderId}}",
// " is ",
// "{{status}}",
// ],
// ],
// [
// "{{data.map(datum => {return {id: datum}})}}",
// ["{{data.map(datum => {return {id: datum}})}}"],
// ],
// ["{{}}{{}}}", ["{{}}", "{{}}", "}"]],
// ["{{{}}", ["{{{}}"]],
// ["{{ {{", ["{{ {{"]],
// ["}} }}", ["}} }}"]],
// ["}} {{", ["}} {{"]],
// ])("Parse the dynamic string(%s, %j)", (dynamicString, expected) => {
// test(`returns ${expected}`, () => {
// expect(parseDynamicString(dynamicString as string)).toStrictEqual(expected);
// });
// });
//
// const baseWidgetProps = {
// parentColumnSpace: 0,
// parentRowSpace: 0,
// parentId: "0",
// type: WidgetTypes.BUTTON_WIDGET,
// renderMode: RenderModes.CANVAS,
// leftColumn: 0,
// rightColumn: 0,
// topRow: 0,
// bottomRow: 0,
// isLoading: false,
// };
//
// it("evaluates the data tree", () => {
// const input: DataTree = {
// widget1: {
// ...baseWidgetProps,
// widgetId: "1",
// widgetName: "widget1",
// displayValue: "{{widget2.computedProperty}}",
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// widget2: {
// ...baseWidgetProps,
// widgetId: "2",
// widgetName: "widget2",
// computedProperty: "{{ widget2.data[widget2.index] }}",
// data: "{{ apiData.data }}",
// index: 2,
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// apiData: {
// config: {
// id: "123",
// pageId: "1234",
// datasource: {},
// name: "api",
// actionConfiguration: {},
// jsonPathKeys: [],
// pluginId: "plugin",
// },
// run: (onSuccess, onError) => ({
// type: "RUN_ACTION",
// payload: {
// actionId: "",
// onSuccess: "",
// onError: "",
// },
// }),
// isLoading: false,
// data: ["wrong value", "still wrong", "correct"],
// ENTITY_TYPE: ENTITY_TYPE.ACTION,
// },
// };
//
// const dynamicBindings = {
// "widget1.displayValue": ["widget2.computedProperty"],
// "widget2.computedProperty": ["widget2.data", "widget2.index"],
// "widget2.data": ["apiData.data"],
// };
//
// const sortedDeps = [
// "apiData.data",
// "widget2.data",
// "widget2.index",
// "widget2.computedProperty",
// "widget1.displayValue",
// ];
//
// const output: DataTree = {
// widget1: {
// ...baseWidgetProps,
// widgetId: "1",
// widgetName: "widget1",
// displayValue: "correct",
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// widget2: {
// ...baseWidgetProps,
// widgetId: "2",
// widgetName: "widget2",
// computedProperty: "correct",
// data: ["wrong value", "still wrong", "correct"],
// index: 2,
// ENTITY_TYPE: ENTITY_TYPE.WIDGET,
// },
// apiData: {
// config: {
// id: "123",
// pageId: "1234",
// datasource: {},
// name: "api",
// actionConfiguration: {},
// jsonPathKeys: [],
// pluginId: "plugin",
// },
// run: (onSuccess, onError) => ({
// type: "RUN_ACTION",
// payload: {
// actionId: "",
// onSuccess: "",
// onError: "",
// },
// }),
// isLoading: false,
// data: ["wrong value", "still wrong", "correct"],
// ENTITY_TYPE: ENTITY_TYPE.ACTION,
// },
// };
//
// const result = dependencySortedEvaluateDataTree(
// input,
// dynamicBindings,
// sortedDeps,
// );
// expect(result).toEqual(output);
// });
//
// it("finds dependencies of a entity", () => {
// const depMap: Array<[string, string]> = [
// ["Widget5.text", "Widget2.data.visible"],
// ["Widget1.options", "Action1.data"],
// ["Widget2.text", "Widget1.selectedOption"],
// ["Widget3.text", "Widget4.selectedRow.name"],
// ["Widget6.label", "Action1.data.label"],
// ];
// const entity = "Action1";
// const result = ["Widget1", "Widget2", "Widget5", "Widget6"];
//
// const actual = getEntityDependencies(depMap, entity);
//
// expect(actual).toEqual(result);
// });
import { getDynamicStringSegments } from "./DynamicBindingUtils";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
it("does nothing. needs implementing", () => {
expect(1 + 1).toEqual(2);
describe.each([
["{{A}}", ["{{A}}"]],
["A {{B}}", ["A ", "{{B}}"]],
[
"Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
[
"Hello ",
"{{Customer.Name}}",
", the status for your order id ",
"{{orderId}}",
" is ",
"{{status}}",
],
],
[
"{{data.map(datum => {return {id: datum}})}}",
["{{data.map(datum => {return {id: datum}})}}"],
],
["{{}}{{}}}", ["{{}}", "{{}}", "}"]],
["{{{}}", ["{{{}}"]],
["{{ {{", ["{{ {{"]],
["}} }}", ["}} }}"]],
["}} {{", ["}} {{"]],
])("Parse the dynamic string(%s, %j)", (dynamicString, expected) => {
test(`returns ${expected}`, () => {
expect(getDynamicStringSegments(dynamicString as string)).toStrictEqual(
expected,
);
});
});

View File

@ -7,6 +7,8 @@ import * as log from "loglevel";
export enum PerformanceTransactionName {
DEPLOY_APPLICATION = "DEPLOY_APPLICATION",
DATA_TREE_EVALUATION = "DATA_TREE_EVALUATION",
DATA_TREE_WORKER_EVALUATION = "DATA_TREE_WORKER_EVALUATION",
EVAL_REDUX_UPDATE = "EVAL_REDUX_UPDATE",
CONSTRUCT_UNEVAL_TREE = "CONSTRUCT_UNEVAL_TREE",
CONSTRUCT_CANVAS_DSL = "CONSTRUCT_CANVAS_DSL",
CREATE_DEPENDENCIES = "CREATE_DEPENDENCIES",
@ -38,6 +40,7 @@ export enum PerformanceTransactionName {
USER_ME_API = "USER_ME_API",
SIGN_UP = "SIGN_UP",
LOGIN_CLICK = "LOGIN_CLICK",
SET_EVALUATED = "SET_EVALUATED",
}
export enum PerformanceTagNames {

View File

@ -72,7 +72,7 @@ abstract class BaseWidget<
static getDefaultPropertiesMap(): Record<string, string> {
return {};
}
// TODO Find a way to enforce this, (dont let it be set)
static getMetaPropertiesMap(): Record<string, any> {
return {};
}

View File

@ -123,6 +123,8 @@
// );
// });
// });
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
it("does nothing. needs implementing", () => {
expect(1 + 1).toEqual(2);
});

View File

@ -55,7 +55,7 @@ class InputWidget extends BaseWidget<InputWidgetProps, WidgetState> {
if (this.regex) {
/*
* break up the regexp pattern into 4 parts: given regex, regex prefix , regex pattern, regex flags
* Example /appsmith/i will be split into ["/appsmith/gi", "/", "appsmith", "gi"]
* Example /test/i will be split into ["/test/gi", "/", "test", "gi"]
*/
const regexParts = this.regex.match(/(\\/?)(.+)\\1([a-z]*)/i);

View File

@ -2,7 +2,7 @@ import React from "react";
import BaseWidget, { WidgetProps } from "./BaseWidget";
import _ from "lodash";
import { EditorContext } from "../components/editorComponents/EditorContextProvider";
import { clearEvalPropertyCache } from "sagas/evaluationsSaga";
import { clearEvalPropertyCache } from "sagas/EvaluationsSaga";
import { ExecuteActionPayload } from "../constants/ActionConstants";
type DebouncedExecuteActionPayload = Omit<

View File

@ -38,6 +38,12 @@ class TabsWidget extends BaseWidget<
};
}
static getMetaPropertiesMap() {
return {
selectedTabWidgetId: undefined,
};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {};
}

View File

@ -0,0 +1,767 @@
import { DataTreeEvaluator } from "./evaluation.worker";
import {
DataTreeAction,
DataTreeWidget,
ENTITY_TYPE,
} from "../entities/DataTree/dataTreeFactory";
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
import { RenderModes, WidgetTypes } from "../constants/WidgetConstants";
import { PluginType } from "../entities/Action";
const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = {
CONTAINER_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {},
metaProperties: {},
},
TEXT_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
text: "TEXT",
textStyle: "TEXT",
shouldScroll: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {
value: "{{ this.text }}",
},
triggerProperties: {},
metaProperties: {},
},
BUTTON_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
text: "TEXT",
buttonStyle: "TEXT",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {
onClick: true,
},
metaProperties: {},
},
INPUT_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
inputType: "TEXT",
defaultText: "TEXT",
text: "TEXT",
regex: "REGEX",
errorMessage: "TEXT",
placeholderText: "TEXT",
maxChars: "NUMBER",
minNum: "NUMBER",
maxNum: "NUMBER",
label: "TEXT",
inputValidators: "ARRAY",
focusIndex: "NUMBER",
isAutoFocusEnabled: "BOOLEAN",
isRequired: "BOOLEAN",
isValid: "BOOLEAN",
},
defaultProperties: {
text: "defaultText",
},
derivedProperties: {
isValid:
'{{\n function(){\n let parsedRegex = null;\n if (this.regex) {\n /*\n * break up the regexp pattern into 4 parts: given regex, regex prefix , regex pattern, regex flags\n * Example /test/i will be split into ["/test/gi", "/", "test", "gi"]\n */\n const regexParts = this.regex.match(/(\\/?)(.+)\\1([a-z]*)/i);\n if (!regexParts) {\n parsedRegex = new RegExp(this.regex);\n } else {\n /*\n * if we don\'t have a regex flags (gmisuy), convert provided string into regexp directly\n /*\n if (regexParts[3] && !/^(?!.*?(.).*?\\1)[gmisuy]+$/.test(regexParts[3])) {\n parsedRegex = RegExp(this.regex);\n }\n /*\n * if we have a regex flags, use it to form regexp\n */\n parsedRegex = new RegExp(regexParts[2], regexParts[3]);\n }\n }\n if (this.inputType === "EMAIL") {\n const emailRegex = new RegExp(/^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$/);\n return emailRegex.test(this.text);\n }\n else if (this.inputType === "NUMBER") {\n return !isNaN(this.text)\n }\n else if (this.isRequired) {\n if(this.text && this.text.length) {\n if (parsedRegex) {\n return parsedRegex.test(this.text)\n } else {\n return true;\n }\n } else {\n return false;\n }\n } if (parsedRegex) {\n return parsedRegex.test(this.text)\n } else {\n return true;\n }\n }()\n }}',
value: "{{this.text}}",
},
triggerProperties: {
onTextChanged: true,
},
metaProperties: {
isFocused: false,
isDirty: false,
},
},
CHECKBOX_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
label: "TEXT",
defaultCheckedState: "BOOLEAN",
},
defaultProperties: {
isChecked: "defaultCheckedState",
},
derivedProperties: {
value: "{{this.isChecked}}",
},
triggerProperties: {
onCheckChange: true,
},
metaProperties: {},
},
DROP_DOWN_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
placeholderText: "TEXT",
label: "TEXT",
options: "OPTIONS_DATA",
selectionType: "TEXT",
isRequired: "BOOLEAN",
selectedOptionValues: "ARRAY",
defaultOptionValue: "DEFAULT_OPTION_VALUE",
},
defaultProperties: {
selectedOptionValue: "defaultOptionValue",
selectedOptionValueArr: "defaultOptionValue",
},
derivedProperties: {
isValid:
"{{this.isRequired ? this.selectionType === 'SINGLE_SELECT' ? !!this.selectedOption : !!this.selectedIndexArr && this.selectedIndexArr.length > 0 : true}}",
selectedOption:
"{{ this.selectionType === 'SINGLE_SELECT' ? _.find(this.options, { value: this.selectedOptionValue }) : undefined}}",
selectedOptionArr:
'{{this.selectionType === "MULTI_SELECT" ? this.options.filter(opt => _.includes(this.selectedOptionValueArr, opt.value)) : undefined}}',
selectedIndex:
"{{ _.findIndex(this.options, { value: this.selectedOption.value } ) }}",
selectedIndexArr:
"{{ this.selectedOptionValueArr.map(o => _.findIndex(this.options, { value: o })) }}",
value:
"{{ this.selectionType === 'SINGLE_SELECT' ? this.selectedOptionValue : this.selectedOptionValueArr }}",
selectedOptionValues: "{{ this.selectedOptionValueArr }}",
},
triggerProperties: {
onOptionChange: true,
},
metaProperties: {},
},
RADIO_GROUP_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
label: "TEXT",
options: "OPTIONS_DATA",
selectedOptionValue: "TEXT",
defaultOptionValue: "TEXT",
isRequired: "BOOLEAN",
},
defaultProperties: {
selectedOptionValue: "defaultOptionValue",
},
derivedProperties: {
selectedOption:
"{{_.find(this.options, { value: this.selectedOptionValue })}}",
isValid: "{{ this.isRequired ? !!this.selectedOptionValue : true }}",
value: "{{this.selectedOptionValue}}",
},
triggerProperties: {
onSelectionChange: true,
},
metaProperties: {},
},
IMAGE_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
image: "TEXT",
imageShape: "TEXT",
defaultImage: "TEXT",
maxZoomLevel: "NUMBER",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {
onClick: true,
},
metaProperties: {},
},
TABLE_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
tableData: "TABLE_DATA",
nextPageKey: "TEXT",
prevPageKey: "TEXT",
label: "TEXT",
searchText: "TEXT",
defaultSearchText: "TEXT",
defaultSelectedRow: "DEFAULT_SELECTED_ROW",
},
defaultProperties: {
searchText: "defaultSearchText",
selectedRowIndex: "defaultSelectedRow",
selectedRowIndices: "defaultSelectedRow",
},
derivedProperties: {
selectedRow: `{{ _.get(this.filteredTableData, this.selectedRowIndex, _.mapValues(this.filteredTableData[0], () => undefined)) }}`,
selectedRows: `{{ this.filteredTableData.filter((item, i) => selectedRowIndices.includes(i) }); }}`,
},
triggerProperties: {
onRowSelected: true,
onPageChange: true,
onSearchTextChanged: true,
columnActions: true,
},
metaProperties: {
pageNo: 1,
selectedRow: {},
selectedRows: [],
},
},
VIDEO_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
url: "TEXT",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {
onEnd: true,
onPlay: true,
onPause: true,
},
metaProperties: {
playState: "NOT_STARTED",
},
},
FILE_PICKER_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
label: "TEXT",
maxNumFiles: "NUMBER",
allowedFileTypes: "ARRAY",
files: "ARRAY",
isRequired: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {
isValid: "{{ this.isRequired ? this.files.length > 0 : true }}",
value: "{{this.files}}",
},
triggerProperties: {
onFilesSelected: true,
},
metaProperties: {
files: [],
uploadedFileData: {},
},
},
DATE_PICKER_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
defaultDate: "DATE",
timezone: "TEXT",
enableTimePicker: "BOOLEAN",
dateFormat: "TEXT",
label: "TEXT",
datePickerType: "TEXT",
maxDate: "DATE",
minDate: "DATE",
isRequired: "BOOLEAN",
},
defaultProperties: {
selectedDate: "defaultDate",
},
derivedProperties: {
isValid: "{{ this.isRequired ? !!this.selectedDate : true }}",
value: "{{ this.selectedDate }}",
},
triggerProperties: {
onDateSelected: true,
},
metaProperties: {},
},
TABS_WIDGET: {
validations: {
tabs: "TABS_DATA",
defaultTab: "SELECTED_TAB",
},
defaultProperties: {},
derivedProperties: {
selectedTab:
"{{_.find(this.tabs, { widgetId: this.selectedTabWidgetId }).label}}",
},
triggerProperties: {
onTabSelected: true,
},
metaProperties: {},
},
MODAL_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {},
metaProperties: {},
},
RICH_TEXT_EDITOR_WIDGET: {
validations: {
text: "TEXT",
placeholder: "TEXT",
defaultValue: "TEXT",
isDisabled: "BOOLEAN",
isVisible: "BOOLEAN",
},
defaultProperties: {
text: "defaultText",
},
derivedProperties: {
value: "{{this.text}}",
},
triggerProperties: {
onTextChange: true,
},
metaProperties: {},
},
CHART_WIDGET: {
validations: {
xAxisName: "TEXT",
yAxisName: "TEXT",
chartName: "TEXT",
isVisible: "BOOLEAN",
chartData: "CHART_DATA",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {},
metaProperties: {},
},
FORM_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {},
metaProperties: {},
},
FORM_BUTTON_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
text: "TEXT",
disabledWhenInvalid: "BOOLEAN",
buttonStyle: "TEXT",
buttonType: "TEXT",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {
onClick: true,
},
metaProperties: {},
},
MAP_WIDGET: {
validations: {
defaultMarkers: "MARKERS",
isDisabled: "BOOLEAN",
isVisible: "BOOLEAN",
enableSearch: "BOOLEAN",
enablePickLocation: "BOOLEAN",
allowZoom: "BOOLEAN",
zoomLevel: "NUMBER",
mapCenter: "OBJECT",
},
defaultProperties: {
markers: "defaultMarkers",
center: "mapCenter",
},
derivedProperties: {},
triggerProperties: {
onMarkerClick: true,
onCreateMarker: true,
},
metaProperties: {},
},
CANVAS_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {},
metaProperties: {},
},
ICON_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {
onClick: true,
},
metaProperties: {},
},
SKELETON_WIDGET: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
},
defaultProperties: {},
derivedProperties: {},
triggerProperties: {},
metaProperties: {},
},
};
const BASE_WIDGET: DataTreeWidget = {
widgetId: "randomID",
widgetName: "randomName",
bottomRow: 0,
isLoading: false,
leftColumn: 0,
parentColumnSpace: 0,
parentRowSpace: 0,
renderMode: RenderModes.CANVAS,
rightColumn: 0,
topRow: 0,
type: WidgetTypes.SKELETON_WIDGET,
parentId: "0",
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
};
const BASE_ACTION: DataTreeAction = {
actionId: "randomId",
name: "randomName",
config: {
timeoutInMillisecond: 10,
},
dynamicBindingPathList: [],
isLoading: false,
pluginType: PluginType.API,
run: {},
data: {},
ENTITY_TYPE: ENTITY_TYPE.ACTION,
};
describe("DataTreeEvaluator", () => {
const unEvalTree = {
Text1: {
...BASE_WIDGET,
widgetName: "Text1",
text: "Label",
type: WidgetTypes.TEXT_WIDGET,
},
Text2: {
...BASE_WIDGET,
widgetName: "Text2",
text: "{{Text1.text}}",
dynamicBindingPathList: [{ key: "text" }],
type: WidgetTypes.TEXT_WIDGET,
},
Text3: {
...BASE_WIDGET,
widgetName: "Text3",
text: "{{Text1.text}}",
dynamicBindingPathList: [{ key: "text" }],
type: WidgetTypes.TEXT_WIDGET,
},
Dropdown1: {
...BASE_WIDGET,
options: [
{
label: "test",
value: "valueTest",
},
{
label: "test2",
value: "valueTest2",
},
],
type: WidgetTypes.DROP_DOWN_WIDGET,
},
Table1: {
...BASE_WIDGET,
tableData: "{{Api1.data.map(datum => ({ ...datum, raw: Text1.text }) )}}",
dynamicBindingPathList: [{ key: "tableData" }],
type: WidgetTypes.TABLE_WIDGET,
},
Text4: {
...BASE_WIDGET,
text: "{{Table1.selectedRow.test}}",
dynamicBindingPathList: [{ key: "text" }],
type: WidgetTypes.TEXT_WIDGET,
},
};
const evaluator = new DataTreeEvaluator(WIDGET_CONFIG_MAP);
evaluator.createFirstTree(unEvalTree);
it("Evaluates a binding in first run", () => {
const evaluation = evaluator.evalTree;
const dependencyMap = evaluator.dependencyMap;
expect(evaluation).toHaveProperty("Text2.text", "Label");
expect(evaluation).toHaveProperty("Text3.text", "Label");
expect(dependencyMap).toStrictEqual({
Text1: ["Text1.text"],
Text2: ["Text2.text"],
Text3: ["Text3.text"],
Text4: ["Text4.text"],
Table1: [
"Table1.tableData",
"Table1.searchText",
"Table1.selectedRowIndex",
"Table1.selectedRowIndices",
],
Dropdown1: [
"Dropdown1.selectedOptionValue",
"Dropdown1.selectedOptionValueArr",
],
"Text2.text": ["Text1.text"],
"Text3.text": ["Text1.text"],
"Dropdown1.selectedOptionValue": [],
"Dropdown1.selectedOptionValueArr": [],
"Table1.tableData": ["Text1.text"],
"Table1.searchText": [],
"Table1.selectedRowIndex": [],
"Table1.selectedRowIndices": [],
"Text4.text": [],
});
});
it("Evaluates a value change in update run", () => {
const updatedUnEvalTree = {
...unEvalTree,
Text1: {
...unEvalTree.Text1,
text: "Hey there",
},
};
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
expect(updatedEvalTree).toHaveProperty("Text2.text", "Hey there");
expect(updatedEvalTree).toHaveProperty("Text3.text", "Hey there");
});
it("Evaluates a dependency change in update run", () => {
const updatedUnEvalTree = {
...unEvalTree,
Text3: {
...unEvalTree.Text3,
text: "Label 3",
},
};
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
const updatedDependencyMap = evaluator.dependencyMap;
expect(updatedEvalTree).toHaveProperty("Text2.text", "Label");
expect(updatedEvalTree).toHaveProperty("Text3.text", "Label 3");
expect(updatedDependencyMap).toStrictEqual({
Text1: ["Text1.text"],
Text2: ["Text2.text"],
Text3: ["Text3.text"],
Text4: ["Text4.text"],
Table1: [
"Table1.tableData",
"Table1.searchText",
"Table1.selectedRowIndex",
"Table1.selectedRowIndices",
],
Dropdown1: [
"Dropdown1.selectedOptionValue",
"Dropdown1.selectedOptionValueArr",
],
"Text2.text": ["Text1.text"],
"Dropdown1.selectedOptionValue": [],
"Dropdown1.selectedOptionValueArr": [],
"Table1.tableData": ["Text1.text"],
"Table1.searchText": [],
"Table1.selectedRowIndex": [],
"Table1.selectedRowIndices": [],
"Text4.text": [],
});
});
it("Overrides with default value", () => {
const updatedUnEvalTree = {
...unEvalTree,
Input1: {
...BASE_WIDGET,
text: undefined,
defaultText: "Default value",
widgetName: "Input1",
type: WidgetTypes.INPUT_WIDGET,
},
};
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
expect(updatedEvalTree).toHaveProperty("Input1.text", "Default value");
});
it("Evaluates for value changes in nested diff paths", () => {
const updatedUnEvalTree = {
...unEvalTree,
Dropdown1: {
...BASE_WIDGET,
options: [
{
label: "newValue",
value: "valueTest",
},
{
label: "test2",
value: "valueTest2",
},
],
type: WidgetTypes.DROP_DOWN_WIDGET,
},
};
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
expect(updatedEvalTree).toHaveProperty(
"Dropdown1.options.0.label",
"newValue",
);
});
it("Adds an entity with a complicated binding", () => {
const updatedUnEvalTree = {
...unEvalTree,
Api1: {
...BASE_ACTION,
data: [
{
test: "Hey",
},
{
test: "Ho",
},
],
},
};
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
const updatedDependencyMap = evaluator.dependencyMap;
expect(updatedEvalTree).toHaveProperty("Table1.tableData", [
{
test: "Hey",
raw: "Label",
},
{
test: "Ho",
raw: "Label",
},
]);
expect(updatedDependencyMap).toStrictEqual({
Api1: ["Api1.data"],
Input1: ["Input1.text"],
Text1: ["Text1.text"],
Text2: ["Text2.text"],
Text3: ["Text3.text"],
Text4: ["Text4.text"],
Table1: [
"Table1.tableData",
"Table1.searchText",
"Table1.selectedRowIndex",
"Table1.selectedRowIndices",
],
Dropdown1: [
"Dropdown1.selectedOptionValue",
"Dropdown1.selectedOptionValueArr",
],
"Text2.text": ["Text1.text"],
"Text3.text": ["Text1.text"],
"Dropdown1.selectedOptionValue": [],
"Dropdown1.selectedOptionValueArr": [],
"Table1.tableData": ["Api1.data", "Text1.text"],
"Table1.searchText": [],
"Table1.selectedRowIndex": [],
"Table1.selectedRowIndices": [],
"Text4.text": [],
"Input1.text": [],
});
});
it("Selects a row", () => {
const updatedUnEvalTree = {
...unEvalTree,
Table1: {
...unEvalTree.Table1,
selectedRowIndex: 0,
selectedRow: {
test: "Hey",
raw: "Label",
},
},
Api1: {
...BASE_ACTION,
data: [
{
test: "Hey",
},
{
test: "Ho",
},
],
},
};
const updatedEvalTree = evaluator.updateDataTree(updatedUnEvalTree);
const updatedDependencyMap = evaluator.dependencyMap;
expect(updatedEvalTree).toHaveProperty("Table1.tableData", [
{
test: "Hey",
raw: "Label",
},
{
test: "Ho",
raw: "Label",
},
]);
expect(updatedEvalTree).toHaveProperty("Text4.text", "Hey");
expect(updatedDependencyMap).toStrictEqual({
Api1: ["Api1.data"],
Text1: ["Text1.text"],
Text2: ["Text2.text"],
Text3: ["Text3.text"],
Text4: ["Text4.text"],
Table1: [
"Table1.tableData",
"Table1.selectedRowIndex",
"Table1.searchText",
"Table1.selectedRowIndices",
"Table1.selectedRow",
],
"Table1.selectedRow": ["Table1.selectedRow.test"],
Dropdown1: [
"Dropdown1.selectedOptionValue",
"Dropdown1.selectedOptionValueArr",
],
Input1: ["Input1.text"],
"Text2.text": ["Text1.text"],
"Text3.text": ["Text1.text"],
"Dropdown1.selectedOptionValue": [],
"Dropdown1.selectedOptionValueArr": [],
"Table1.tableData": ["Api1.data", "Text1.text"],
"Table1.searchText": [],
"Table1.selectedRowIndex": [],
"Table1.selectedRowIndices": [],
"Text4.text": ["Table1.selectedRow.test"],
"Input1.text": [],
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
import { DependencyMap, isDynamicValue } from "../utils/DynamicBindingUtils";
import { WidgetType } from "../constants/WidgetConstants";
import { WidgetProps } from "../widgets/BaseWidget";
import { WidgetTypeConfigMap } from "../utils/WidgetFactory";
import { VALIDATORS } from "./validations";
import { Diff } from "deep-diff";
import {
DataTree,
DataTreeEntity,
DataTreeWidget,
ENTITY_TYPE,
} from "../entities/DataTree/dataTreeFactory";
import _ from "lodash";
export enum DataTreeDiffEvent {
NEW = "NEW",
DELETE = "DELETE",
EDIT = "EDIT",
NOOP = "NOOP",
}
type DataTreeDiff = {
payload: {
propertyPath: string;
value?: string;
};
event: DataTreeDiffEvent;
};
export class CrashingError extends Error {}
export const convertPathToString = (arrPath: Array<string | number>) => {
let string = "";
arrPath.forEach((segment) => {
if (typeof segment === "string") {
if (string.length !== 0) {
string = string + ".";
}
string = string + segment;
} else {
string = string + "[" + segment + "]";
}
});
return string;
};
export const translateDiffEventToDataTreeDiffEvent = (
difference: Diff<any, any>,
): DataTreeDiff => {
const result: DataTreeDiff = {
payload: {
propertyPath: "",
value: "",
},
event: DataTreeDiffEvent.NOOP,
};
if (!difference.path) {
return result;
}
const propertyPath = convertPathToString(difference.path);
switch (difference.kind) {
case "N": {
result.event = DataTreeDiffEvent.NEW;
result.payload = {
propertyPath,
};
break;
}
case "D": {
result.event = DataTreeDiffEvent.DELETE;
result.payload = { propertyPath };
break;
}
case "E": {
const rhsChange =
typeof difference.rhs === "string" && isDynamicValue(difference.rhs);
const lhsChange =
typeof difference.lhs === "string" && isDynamicValue(difference.lhs);
if (rhsChange || lhsChange) {
result.event = DataTreeDiffEvent.EDIT;
result.payload = {
propertyPath,
value: difference.rhs,
};
} else {
// Handle static value changes that change structure that can lead to
// old bindings being eligible
if (
difference.lhs === undefined &&
typeof difference.rhs === "object"
) {
result.event = DataTreeDiffEvent.NEW;
result.payload = { propertyPath };
}
if (
difference.rhs === undefined &&
typeof difference.lhs === "object"
) {
result.event = DataTreeDiffEvent.DELETE;
result.payload = { propertyPath };
}
}
break;
}
case "A": {
break;
}
default: {
break;
}
}
return result;
};
export const isPropertyPathOrNestedPath = (
path: string,
comparePath: string,
): boolean => {
return path === comparePath || comparePath.startsWith(`${path}.`);
};
/*
Table1.selectedRow
Table1.selectedRow.email: ["Input1.defaultText"]
*/
export const addDependantsOfNestedPropertyPaths = (
parentPaths: Array<string>,
inverseMap: DependencyMap,
): Array<string> => {
const withNestedPaths: Set<string> = new Set();
const dependantNodes = Object.keys(inverseMap);
parentPaths.forEach((propertyPath) => {
withNestedPaths.add(propertyPath);
dependantNodes
.filter((dependantNodePath) =>
isPropertyPathOrNestedPath(propertyPath, dependantNodePath),
)
.forEach((dependantNodePath) => {
inverseMap[dependantNodePath].forEach((path) => {
withNestedPaths.add(path);
});
});
});
return [...withNestedPaths.values()];
};
export function isWidget(entity: DataTreeEntity): boolean {
return (
typeof entity === "object" &&
"ENTITY_TYPE" in entity &&
entity.ENTITY_TYPE === ENTITY_TYPE.WIDGET
);
}
export function isAction(entity: DataTreeEntity): boolean {
return (
typeof entity === "object" &&
"ENTITY_TYPE" in entity &&
entity.ENTITY_TYPE === ENTITY_TYPE.ACTION
);
}
// We need to remove functions from data tree to avoid any unexpected identifier while JSON parsing
// Check issue https://github.com/appsmithorg/appsmith/issues/719
export const removeFunctions = (value: any) => {
if (_.isFunction(value)) {
return "Function call";
} else if (_.isObject(value) && _.some(value, _.isFunction)) {
return JSON.parse(JSON.stringify(value));
} else {
return value;
}
};
export const removeFunctionsFromDataTree = (dataTree: DataTree) => {
dataTree.actionPaths?.forEach((functionPath) => {
_.set(dataTree, functionPath, {});
});
delete dataTree.actionPaths;
return dataTree;
};
export const makeParentsDependOnChildren = (
depMap: DependencyMap,
): DependencyMap => {
//return depMap;
// Make all parents depend on child
Object.keys(depMap).forEach((key) => {
depMap = makeParentsDependOnChild(depMap, key);
depMap[key].forEach((path) => {
depMap = makeParentsDependOnChild(depMap, path);
});
});
return depMap;
};
export const makeParentsDependOnChild = (
depMap: DependencyMap,
child: string,
): DependencyMap => {
const result: DependencyMap = depMap;
let curKey = child;
const rgx = /^(.*)\..*$/;
let matches: Array<string> | null;
// Note: The `=` is intentional
// Stops looping when match is null
while ((matches = curKey.match(rgx)) !== null) {
const parentKey = matches[1];
// Todo: switch everything to set.
const existing = new Set(result[parentKey] || []);
existing.add(curKey);
result[parentKey] = Array.from(existing);
curKey = parentKey;
}
return result;
};
export function validateWidgetProperty(
widgetConfigMap: WidgetTypeConfigMap,
widgetType: WidgetType,
property: string,
value: any,
props: WidgetProps,
dataTree?: DataTree,
) {
const propertyValidationTypes = widgetConfigMap[widgetType].validations;
const validationTypeOrValidator = propertyValidationTypes[property];
let validator;
if (typeof validationTypeOrValidator === "function") {
validator = validationTypeOrValidator;
} else {
validator = VALIDATORS[validationTypeOrValidator];
}
if (validator) {
return validator(value, props, dataTree);
} else {
return { isValid: true, parsed: value };
}
}
export function getValidatedTree(
widgetConfigMap: WidgetTypeConfigMap,
tree: DataTree,
only?: Set<string>,
) {
return Object.keys(tree).reduce((tree, entityKey: string) => {
if (only && only.size) {
if (!only.has(entityKey)) {
return tree;
}
}
const entity = tree[entityKey] as DataTreeWidget;
if (!isWidget(entity)) {
return tree;
}
const parsedEntity = { ...entity };
Object.keys(entity).forEach((property: string) => {
const validationProperties = widgetConfigMap[entity.type].validations;
if (property in validationProperties) {
const value = _.get(entity, property);
// Pass it through parse
const {
parsed,
isValid,
message,
transformed,
} = validateWidgetProperty(
widgetConfigMap,
entity.type,
property,
value,
entity,
tree,
);
parsedEntity[property] = parsed;
const evaluatedValue = isValid
? parsed
: _.isUndefined(transformed)
? value
: transformed;
const safeEvaluatedValue = removeFunctions(evaluatedValue);
_.set(parsedEntity, `evaluatedValues.${property}`, safeEvaluatedValue);
if (!isValid) {
_.set(parsedEntity, `invalidProps.${property}`, true);
_.set(parsedEntity, `validationMessages.${property}`, message);
} else {
_.set(parsedEntity, `invalidProps.${property}`, false);
_.set(parsedEntity, `validationMessages.${property}`, "");
}
}
});
return { ...tree, [entityKey]: parsedEntity };
}, tree);
}

View File

@ -0,0 +1,646 @@
import {
ISO_DATE_FORMAT,
VALIDATION_TYPES,
ValidationResponse,
ValidationType,
Validator,
} from "../constants/WidgetValidation";
import { DataTree } from "../entities/DataTree/dataTreeFactory";
import _, {
every,
isBoolean,
isNumber,
isObject,
isString,
isUndefined,
toNumber,
toString,
} from "lodash";
import { WidgetProps } from "../widgets/BaseWidget";
import { WIDGET_TYPE_VALIDATION_ERROR } from "../constants/messages";
import moment from "moment";
export const VALIDATORS: Record<ValidationType, Validator> = {
[VALIDATION_TYPES.TEXT]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value) || value === null) {
return {
isValid: true,
parsed: value,
message: "",
};
}
if (isObject(value)) {
return {
isValid: false,
parsed: JSON.stringify(value, null, 2),
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
};
}
let isValid = isString(value);
if (!isValid) {
try {
parsed = toString(value);
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to string`);
console.error(e);
return {
isValid: false,
parsed: "",
message: `${WIDGET_TYPE_VALIDATION_ERROR}: text`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.REGEX]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.TEXT](
value,
props,
dataTree,
);
if (isValid) {
try {
new RegExp(parsed);
} catch (e) {
return {
isValid: false,
parsed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: regex`,
};
}
}
return { isValid, parsed, message };
},
[VALIDATION_TYPES.NUMBER]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value)) {
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
let isValid = isNumber(value);
if (!isValid) {
try {
parsed = toNumber(value);
if (isNaN(parsed)) {
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to number`);
console.error(e);
return {
isValid: false,
parsed: 0,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: number`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.BOOLEAN]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value)) {
return {
isValid: false,
parsed: false,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
const isABoolean = isBoolean(value);
const isStringTrueFalse = value === "true" || value === "false";
const isValid = isABoolean || isStringTrueFalse;
if (isStringTrueFalse) parsed = value !== "false";
if (!isValid) {
return {
isValid: isValid,
parsed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: boolean`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.OBJECT]: (value: any): ValidationResponse => {
let parsed = value;
if (isUndefined(value)) {
return {
isValid: false,
parsed: {},
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
};
}
let isValid = isObject(value);
if (!isValid) {
try {
parsed = JSON.parse(value);
isValid = true;
} catch (e) {
console.error(`Error when parsing ${value} to object`);
console.error(e);
return {
isValid: false,
parsed: {},
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Object`,
};
}
}
return { isValid, parsed };
},
[VALIDATION_TYPES.ARRAY]: (value: any): ValidationResponse => {
let parsed = value;
try {
if (isUndefined(value)) {
return {
isValid: false,
parsed: [],
transformed: undefined,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
if (isString(value)) {
parsed = JSON.parse(parsed as string);
}
if (!Array.isArray(parsed)) {
return {
isValid: false,
parsed: [],
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
return { isValid: true, parsed, transformed: parsed };
} catch (e) {
console.error(e);
return {
isValid: false,
parsed: [],
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Array/List`,
};
}
},
[VALIDATION_TYPES.TABS_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
};
} else if (!every(parsed, (datum) => isObject(datum))) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Tabs Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.TABLE_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, transformed, parsed } = VALIDATORS.ARRAY(
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed: [],
transformed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
}
const isValidTableData = every(parsed, (datum) => {
return (
isObject(datum) &&
Object.keys(datum).filter((key) => isString(key) && key.length === 0)
.length === 0
);
});
if (!isValidTableData) {
return {
isValid: false,
parsed: [],
transformed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: [{ "Col1" : "val1", "Col2" : "val2" }]`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.CHART_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
transformed: parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Chart Data`,
};
}
let validationMessage = "";
let index = 0;
const isValidChartData = every(
parsed,
(datum: { name: string; data: any }) => {
const validatedResponse: {
isValid: boolean;
parsed: Array<unknown>;
message?: string;
} = VALIDATORS[VALIDATION_TYPES.ARRAY](datum.data, props, dataTree);
validationMessage = `${index}##${WIDGET_TYPE_VALIDATION_ERROR}: [{ "x": "val", "y": "val" }]`;
let isValidChart = validatedResponse.isValid;
if (validatedResponse.isValid) {
datum.data = validatedResponse.parsed;
isValidChart = every(
datum.data,
(chartPoint: { x: string; y: any }) => {
return (
isObject(chartPoint) &&
isString(chartPoint.x) &&
!isUndefined(chartPoint.y)
);
},
);
}
index++;
return isValidChart;
},
);
if (!isValidChartData) {
return {
isValid: false,
parsed: [],
transformed: parsed,
message: validationMessage,
};
}
return { isValid, parsed, transformed: parsed };
},
[VALIDATION_TYPES.MARKERS]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
} else if (!every(parsed, (datum) => isObject(datum))) {
return {
isValid: false,
parsed: [],
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Marker Data`,
};
}
return { isValid, parsed };
},
[VALIDATION_TYPES.OPTIONS_DATA]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
if (!isValid) {
return {
isValid,
parsed,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
};
}
try {
const isValidOption = (option: { label: any; value: any }) =>
_.isObject(option) &&
_.isString(option.label) &&
_.isString(option.value) &&
!_.isEmpty(option.label) &&
!_.isEmpty(option.value);
const hasOptions = every(parsed, isValidOption);
const validOptions = parsed.filter(isValidOption);
const uniqValidOptions = _.uniqBy(validOptions, "value");
if (!hasOptions || uniqValidOptions.length !== validOptions.length) {
return {
isValid: false,
parsed: uniqValidOptions,
message: `${WIDGET_TYPE_VALIDATION_ERROR}: Options Data`,
};
}
return { isValid, parsed };
} catch (e) {
console.error(e);
return {
isValid: false,
parsed: [],
transformed: parsed,
};
}
},
[VALIDATION_TYPES.DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const today = moment()
.hour(0)
.minute(0)
.second(0)
.millisecond(0);
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const todayDateString = today.format(dateFormat);
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
: "",
};
}
const isValid = moment(dateString, dateFormat).isValid();
const parsed = isValid ? dateString : todayDateString;
return {
isValid,
parsed,
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date`,
};
},
[VALIDATION_TYPES.DEFAULT_DATE]: (
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const today = moment()
.hour(0)
.minute(0)
.second(0)
.millisecond(0);
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const todayDateString = today.format(dateFormat);
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
: "",
};
}
const parsedCurrentDate = moment(dateString, dateFormat);
let isValid = parsedCurrentDate.isValid();
const parsedMinDate = moment(props.minDate, dateFormat);
const parsedMaxDate = moment(props.maxDate, dateFormat);
// checking for max/min date range
if (isValid) {
if (
parsedMinDate.isValid() &&
parsedCurrentDate.isBefore(parsedMinDate)
) {
isValid = false;
}
if (
isValid &&
parsedMaxDate.isValid() &&
parsedCurrentDate.isAfter(parsedMaxDate)
) {
isValid = false;
}
}
const parsed = isValid ? dateString : todayDateString;
return {
isValid,
parsed,
message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`,
};
},
[VALIDATION_TYPES.ACTION_SELECTOR]: (value: any): ValidationResponse => {
if (Array.isArray(value) && value.length) {
return {
isValid: true,
parsed: undefined,
transformed: "Function Call",
};
}
/*
if (_.isString(value)) {
if (value.indexOf("navigateTo") !== -1) {
const pageNameOrUrl = modalGetter(value);
if (dataTree) {
if (isDynamicValue(pageNameOrUrl)) {
return {
isValid: true,
parsed: value,
};
}
const isPage =
(dataTree.pageList as PageListPayload).findIndex(
page => page.pageName === pageNameOrUrl,
) !== -1;
const isValidUrl = URL_REGEX.test(pageNameOrUrl);
if (!(isValidUrl || isPage)) {
return {
isValid: false,
parsed: value,
message: `${NAVIGATE_TO_VALIDATION_ERROR}`,
};
}
}
}
}
*/
return {
isValid: false,
parsed: undefined,
transformed: "undefined",
message: "Not a function call",
};
},
[VALIDATION_TYPES.ARRAY_ACTION_SELECTOR]: (
value: any,
props: WidgetProps,
dataTree?: DataTree,
): ValidationResponse => {
const { isValid, parsed, message } = VALIDATORS[VALIDATION_TYPES.ARRAY](
value,
props,
dataTree,
);
let isValidFinal = isValid;
let finalParsed = parsed.slice();
if (isValid) {
finalParsed = parsed.map((value: any) => {
const { isValid, message } = VALIDATORS[
VALIDATION_TYPES.ACTION_SELECTOR
](value.dynamicTrigger, props, dataTree);
isValidFinal = isValidFinal && isValid;
return {
...value,
message: message,
isValid: isValid,
};
});
}
return {
isValid: isValidFinal,
parsed: finalParsed,
message: message,
};
},
[VALIDATION_TYPES.SELECTED_TAB]: (
value: any,
props: WidgetProps,
): ValidationResponse => {
const tabs =
props.tabs && isString(props.tabs)
? JSON.parse(props.tabs)
: props.tabs && Array.isArray(props.tabs)
? props.tabs
: [];
const tabNames = tabs.map((i: { label: string; id: string }) => i.label);
const isValidTabName = tabNames.includes(value);
return {
isValid: isValidTabName,
parsed: value,
message: isValidTabName
? ""
: `${WIDGET_TYPE_VALIDATION_ERROR}: Invalid tab name.`,
};
},
[VALIDATION_TYPES.DEFAULT_OPTION_VALUE]: (
value: string | string[],
props: WidgetProps,
dataTree?: DataTree,
) => {
let values = value;
if (props) {
if (props.selectionType === "SINGLE_SELECT") {
return VALIDATORS[VALIDATION_TYPES.TEXT](value, props, dataTree);
} else if (props.selectionType === "MULTI_SELECT") {
if (typeof value === "string") {
try {
values = JSON.parse(value);
if (!Array.isArray(values)) {
throw new Error();
}
} catch {
values = value.length ? value.split(",") : [];
if (values.length > 0) {
values = values.map((value) => value.trim());
}
}
}
}
}
if (Array.isArray(values)) {
values = _.uniq(values);
}
return {
isValid: true,
parsed: values,
};
},
[VALIDATION_TYPES.DEFAULT_SELECTED_ROW]: (
value: string | string[],
props: WidgetProps,
) => {
let values = value;
if (props) {
if (props.multiRowSelection) {
if (typeof value === "string") {
try {
values = JSON.parse(value);
if (!Array.isArray(values)) {
throw new Error();
}
} catch {
values = value.length ? value.split(",") : [];
if (values.length > 0) {
let numericValues = values.map((value) => {
return isNumber(value.trim()) ? -1 : Number(value.trim());
});
numericValues = _.uniq(numericValues);
return {
isValid: true,
parsed: numericValues,
};
}
}
}
} else {
try {
const parsed = toNumber(value);
return {
isValid: true,
parsed: parsed,
};
} catch (e) {
return {
isValid: true,
parsed: -1,
};
}
}
}
return {
isValid: true,
parsed: values,
};
},
};

View File

@ -3835,6 +3835,11 @@
dependencies:
"@types/tern" "*"
"@types/deep-diff@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/deep-diff/-/deep-diff-1.0.0.tgz#7eba3202a99b3a207f758f351f7f86387269fc40"
integrity sha512-ENsJcujGbCU/oXhDfQ12mSo/mCBWodT2tpARZKmatoSrf8+cGRCPi0KVj3I0FORhYZfLXkewXu7AoIWqiBLkNw==
"@types/dom4@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.1.tgz#506d5781b9bcab81bd9a878b198aec7dee2a6033"
@ -7715,6 +7720,11 @@ dedent@^0.7.0:
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
deep-diff@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26"
integrity sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==
deep-equal@^1.0.1, deep-equal@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a"