Merge branch 'release' of https://github.com/appsmithorg/appsmith into fix/rte-newline
This commit is contained in:
commit
b474442ec7
|
|
@ -297,6 +297,23 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "zegerhoogeboom",
|
||||
"name": "Zeger Hoogeboom",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/5371096?v=4",
|
||||
"profile": "https://github.com/zegerhoogeboom",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
"login": "Devedunkey",
|
||||
"name": "Young Yoo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/29448764?v=4",
|
||||
"profile": "https://github.com/Devedunkey",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ The Appsmith platform is available under the [Apache License 2.0](https://www.ap
|
|||
<td align="center"><a href="https://github.com/vicky-primathon"><img src="https://avatars2.githubusercontent.com/u/67091118?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vicky-primathon</b></sub></a><br /><a href="https://github.com/appsmithorg/appsmith/commits?author=vicky-primathon" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/devrk96"><img src="https://avatars0.githubusercontent.com/u/68607686?v=4?s=100" width="100px;" alt=""/><br /><sub><b>devrk96</b></sub></a><br /><a href="https://github.com/appsmithorg/appsmith/commits?author=devrk96" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/trdillon"><img src="https://avatars.githubusercontent.com/u/26350151?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Dillon</b></sub></a><br /><a href="https://github.com/appsmithorg/appsmith/commits?author=trdillon" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/zegerhoogeboom"><img src="https://avatars.githubusercontent.com/u/5371096?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zeger Hoogeboom</b></sub></a><br /><a href="https://github.com/appsmithorg/appsmith/commits?author=zegerhoogeboom" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Devedunkey"><img src="https://avatars.githubusercontent.com/u/29448764?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Young Yoo</b></sub></a><br /><a href="https://github.com/appsmithorg/appsmith/commits?author=Devedunkey" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
|
|
|||
129
app/client/cypress/fixtures/ModalDsl.json
Normal file
129
app/client/cypress/fixtures/ModalDsl.json
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
|
||||
{
|
||||
"dsl": {
|
||||
"widgetName": "MainContainer",
|
||||
"backgroundColor": "none",
|
||||
"rightColumn": 1224,
|
||||
"snapColumns": 16,
|
||||
"detachFromLayout": true,
|
||||
"widgetId": "0",
|
||||
"topRow": 0,
|
||||
"bottomRow": 1280,
|
||||
"containerStyle": "none",
|
||||
"snapRows": 33,
|
||||
"parentRowSpace": 1,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"canExtend": true,
|
||||
"version": 10,
|
||||
"minHeight": 1292,
|
||||
"parentColumnSpace": 1,
|
||||
"dynamicBindingPathList": [],
|
||||
"leftColumn": 0,
|
||||
"children": [
|
||||
{
|
||||
"size": "MODAL_SMALL",
|
||||
"canEscapeKeyClose": true,
|
||||
"detachFromLayout": true,
|
||||
"canOutsideClickClose": true,
|
||||
"shouldScrollContents": true,
|
||||
"widgetName": "Modal1",
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"widgetName": "Canvas1",
|
||||
"detachFromLayout": true,
|
||||
"canExtend": true,
|
||||
"isDisabled": false,
|
||||
"shouldScrollContents": false,
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"widgetName": "Icon1",
|
||||
"iconName": "cross",
|
||||
"iconSize": 24,
|
||||
"color": "#040627",
|
||||
"type": "ICON_WIDGET",
|
||||
"isLoading": false,
|
||||
"leftColumn": 14,
|
||||
"rightColumn": 16,
|
||||
"topRow": 0,
|
||||
"bottomRow": 1,
|
||||
"parentId": "yyyrxs383y",
|
||||
"widgetId": "kxdvolusyp",
|
||||
"onClick": "{{closeModal('Modal1')}}"
|
||||
},
|
||||
{
|
||||
"isVisible": true,
|
||||
"text": "Modal Title",
|
||||
"textStyle": "HEADING",
|
||||
"textAlign": "LEFT",
|
||||
"widgetName": "Text1",
|
||||
"type": "TEXT_WIDGET",
|
||||
"isLoading": false,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 10,
|
||||
"topRow": 0,
|
||||
"bottomRow": 1,
|
||||
"parentId": "yyyrxs383y",
|
||||
"widgetId": "3fugqdtg8g"
|
||||
},
|
||||
{
|
||||
"isVisible": true,
|
||||
"text": "Cancel",
|
||||
"buttonStyle": "SECONDARY_BUTTON",
|
||||
"widgetName": "Button1",
|
||||
"isDisabled": false,
|
||||
"isDefaultClickDisabled": true,
|
||||
"type": "BUTTON_WIDGET",
|
||||
"isLoading": false,
|
||||
"leftColumn": 9,
|
||||
"rightColumn": 12,
|
||||
"topRow": 4,
|
||||
"bottomRow": 5,
|
||||
"parentId": "yyyrxs383y",
|
||||
"widgetId": "6cjfbnjfa4"
|
||||
},
|
||||
{
|
||||
"isVisible": true,
|
||||
"text": "Confirm",
|
||||
"buttonStyle": "PRIMARY_BUTTON",
|
||||
"widgetName": "Button2",
|
||||
"isDisabled": false,
|
||||
"isDefaultClickDisabled": true,
|
||||
"type": "BUTTON_WIDGET",
|
||||
"isLoading": false,
|
||||
"leftColumn": 12,
|
||||
"rightColumn": 16,
|
||||
"topRow": 4,
|
||||
"bottomRow": 5,
|
||||
"parentId": "yyyrxs383y",
|
||||
"widgetId": "3tmnukkm7m"
|
||||
}
|
||||
],
|
||||
"minHeight": 240,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 1,
|
||||
"parentRowSpace": 1,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 444,
|
||||
"topRow": 0,
|
||||
"bottomRow": 240,
|
||||
"parentId": "287u6pannr",
|
||||
"widgetId": "yyyrxs383y"
|
||||
}
|
||||
],
|
||||
"type": "MODAL_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 5,
|
||||
"rightColumn": 11,
|
||||
"topRow": 15,
|
||||
"bottomRow": 21,
|
||||
"parentId": "0",
|
||||
"widgetId": "287u6pannr"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
42
app/client/cypress/fixtures/basicTabledsl.json
Normal file
42
app/client/cypress/fixtures/basicTabledsl.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"dsl": {
|
||||
"widgetName": "MainContainer",
|
||||
"backgroundColor": "none",
|
||||
"rightColumn": 1224,
|
||||
"snapColumns": 16,
|
||||
"detachFromLayout": true,
|
||||
"widgetId": "0",
|
||||
"topRow": 0,
|
||||
"bottomRow": 1280,
|
||||
"containerStyle": "none",
|
||||
"snapRows": 33,
|
||||
"parentRowSpace": 1,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"canExtend": true,
|
||||
"version": 9,
|
||||
"minHeight": 1292,
|
||||
"parentColumnSpace": 1,
|
||||
"dynamicBindingPathList": [],
|
||||
"leftColumn": 0,
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"label": "Data",
|
||||
"widgetName": "Table1",
|
||||
"searchKey": "",
|
||||
"tableData": "[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]",
|
||||
"type": "TABLE_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 1,
|
||||
"rightColumn": 9,
|
||||
"topRow": 7,
|
||||
"bottomRow": 14,
|
||||
"parentId": "0",
|
||||
"widgetId": "7miqot30xy",
|
||||
"dynamicBindingPathList": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
80
app/client/cypress/fixtures/datePicker2dsl.json
Normal file
80
app/client/cypress/fixtures/datePicker2dsl.json
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"dsl": {
|
||||
"widgetName": "MainContainer",
|
||||
"backgroundColor": "none",
|
||||
"rightColumn": 1224,
|
||||
"snapColumns": 16,
|
||||
"detachFromLayout": true,
|
||||
"widgetId": "0",
|
||||
"topRow": 0,
|
||||
"bottomRow": 1280,
|
||||
"containerStyle": "none",
|
||||
"snapRows": 33,
|
||||
"parentRowSpace": 1,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"canExtend": true,
|
||||
"version": 9,
|
||||
"minHeight": 1292,
|
||||
"parentColumnSpace": 1,
|
||||
"dynamicBindingPathList": [],
|
||||
"leftColumn": 0,
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"isDisabled": false,
|
||||
"datePickerType": "DATE_PICKER",
|
||||
"label": "",
|
||||
"dateFormat": "DD/MM/YYYY HH:mm",
|
||||
"widgetName": "DatePicker1",
|
||||
"defaultDate": "2021-02-05T10:53:12.791Z",
|
||||
"version": 2,
|
||||
"type": "DATE_PICKER_WIDGET2",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 5,
|
||||
"rightColumn": 10,
|
||||
"topRow": 0,
|
||||
"bottomRow": 1,
|
||||
"parentId": "0",
|
||||
"widgetId": "w4htilgv5t"
|
||||
},
|
||||
{
|
||||
"isVisible": true,
|
||||
"text": "Label",
|
||||
"textStyle": "LABEL",
|
||||
"textAlign": "LEFT",
|
||||
"widgetName": "Text1",
|
||||
"version": 1,
|
||||
"type": "TEXT_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 1,
|
||||
"rightColumn": 5,
|
||||
"topRow": 3,
|
||||
"bottomRow": 4,
|
||||
"parentId": "0",
|
||||
"widgetId": "voohxsv4t2"
|
||||
},
|
||||
{
|
||||
"isVisible": true,
|
||||
"text": "Label",
|
||||
"textStyle": "LABEL",
|
||||
"textAlign": "LEFT",
|
||||
"widgetName": "Text2",
|
||||
"version": 1,
|
||||
"type": "TEXT_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 8,
|
||||
"rightColumn": 12,
|
||||
"topRow": 3,
|
||||
"bottomRow": 4,
|
||||
"parentId": "0",
|
||||
"widgetId": "xif8wugzjv"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
91
app/client/cypress/fixtures/tabdsl.json
Normal file
91
app/client/cypress/fixtures/tabdsl.json
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"dsl": {
|
||||
"widgetName": "MainContainer",
|
||||
"backgroundColor": "none",
|
||||
"rightColumn": 1224,
|
||||
"snapColumns": 16,
|
||||
"detachFromLayout": true,
|
||||
"widgetId": "0",
|
||||
"topRow": 0,
|
||||
"bottomRow": 1280,
|
||||
"containerStyle": "none",
|
||||
"snapRows": 33,
|
||||
"parentRowSpace": 1,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"canExtend": true,
|
||||
"version": 8,
|
||||
"minHeight": 1292,
|
||||
"parentColumnSpace": 1,
|
||||
"dynamicBindingPathList": [],
|
||||
"leftColumn": 0,
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"shouldScrollContents": false,
|
||||
"widgetName": "Tabs1",
|
||||
"tabs": [
|
||||
{
|
||||
"label": "Tab 1",
|
||||
"id": "tab1",
|
||||
"widgetId": "uxle0mrg8t"
|
||||
},
|
||||
{
|
||||
"label": "Tab 2",
|
||||
"id": "tab2",
|
||||
"widgetId": "9hy1rmqb2f"
|
||||
}
|
||||
],
|
||||
"shouldShowTabs": true,
|
||||
"defaultTab": "Tab 1",
|
||||
"type": "TABS_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 2,
|
||||
"rightColumn": 10,
|
||||
"topRow": 8,
|
||||
"bottomRow": 15,
|
||||
"parentId": "0",
|
||||
"widgetId": "y6rla3dsd6",
|
||||
"children": [
|
||||
{
|
||||
"type": "CANVAS_WIDGET",
|
||||
"tabId": "tab1",
|
||||
"tabName": "Tab 1",
|
||||
"widgetId": "uxle0mrg8t",
|
||||
"parentId": "y6rla3dsd6",
|
||||
"detachFromLayout": true,
|
||||
"children": [],
|
||||
"parentRowSpace": 1,
|
||||
"parentColumnSpace": 1,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 592,
|
||||
"topRow": 0,
|
||||
"bottomRow": 280,
|
||||
"isLoading": false,
|
||||
"widgetName": "Canvas1",
|
||||
"renderMode": "CANVAS"
|
||||
},
|
||||
{
|
||||
"type": "CANVAS_WIDGET",
|
||||
"tabId": "tab2",
|
||||
"tabName": "Tab 2",
|
||||
"widgetId": "9hy1rmqb2f",
|
||||
"parentId": "y6rla3dsd6",
|
||||
"detachFromLayout": true,
|
||||
"children": [],
|
||||
"parentRowSpace": 1,
|
||||
"parentColumnSpace": 1,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 592,
|
||||
"topRow": 0,
|
||||
"bottomRow": 280,
|
||||
"isLoading": false,
|
||||
"widgetName": "Canvas2",
|
||||
"renderMode": "CANVAS"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -24,10 +24,10 @@
|
|||
"Post": "POST",
|
||||
"Delete": "DELETE",
|
||||
"Patch": "PATCH",
|
||||
"methodput": "api/users/2",
|
||||
"methodpost": "api/users",
|
||||
"methodpatch": "api/users/2",
|
||||
"methoddelete": "1",
|
||||
"methodput": "echo/put",
|
||||
"methodpost": "echo/post",
|
||||
"methodpatch": "echo/patch",
|
||||
"methoddelete": "echo/delete",
|
||||
"putAction": "//div[contains(@class, 't--dropdown-option')]//span[contains(text(),'PUT')]",
|
||||
"postAction": "//div[contains(@class, 't--dropdown-option')]//span[contains(text(),'POST')]",
|
||||
"patchAction": "//div[contains(@class, 't--dropdown-option')]//span[contains(text(),'PATCH')]",
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
"btoaInput": "{{btoa('A')",
|
||||
"defaultInputBinding": "{{Input2.text",
|
||||
"tabBinding": "{{Tabs1.selectedTab",
|
||||
"pageloadBinding": "{{PageLoadApi.data.data[1].id}}{{Input1.text}}",
|
||||
"pageloadBinding": "{{PageLoadApi.data.users[1].id}}{{Input1.text}}",
|
||||
"currentRowEmail": "{{currentRow.email}}",
|
||||
"currentRowOrderAmt": "{{currentRow.orderAmount}}",
|
||||
"momentDate": "{{moment()}}",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ describe("Binding the API with pageOnLoad and input Widgets", function() {
|
|||
it("Will load an api on load", function() {
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.CreateAPI("PageLoadApi");
|
||||
cy.enterDatasourceAndPath("https://reqres.in/api/", "users");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
cy.WaitAutoSave();
|
||||
cy.get(apiwidget.settings).click({ force: true });
|
||||
cy.get(apiwidget.onPageLoad).click({ force: true });
|
||||
|
|
@ -25,9 +25,7 @@ describe("Binding the API with pageOnLoad and input Widgets", function() {
|
|||
|
||||
it("Input widget updated with deafult data", function() {
|
||||
cy.SearchEntityandOpen("Input1");
|
||||
cy.get(widgetsPage.defaultInput)
|
||||
.type(testdata.command)
|
||||
.type("3");
|
||||
cy.get(widgetsPage.defaultInput).type("3");
|
||||
cy.get(commonlocators.editPropCrossButton).click();
|
||||
cy.wait("@updateLayout").should(
|
||||
"have.nested.property",
|
||||
|
|
@ -53,13 +51,13 @@ describe("Binding the API with pageOnLoad and input Widgets", function() {
|
|||
);
|
||||
cy.PublishtheApp();
|
||||
cy.get(publish.inputWidget + " " + "input")
|
||||
.last()
|
||||
.first()
|
||||
.invoke("attr", "value")
|
||||
.should("contain", "3");
|
||||
cy.get(publish.inputWidget + " " + "input")
|
||||
.last()
|
||||
.invoke("attr", "value")
|
||||
.should("contain", "2");
|
||||
.should("contain", "23");
|
||||
cy.get(publish.backToEditor)
|
||||
.first()
|
||||
.click();
|
||||
|
|
|
|||
|
|
@ -63,45 +63,45 @@ describe("DatePicker Widget Functionality", function() {
|
|||
);
|
||||
});
|
||||
|
||||
it("Datepicker min/max date validation", function() {
|
||||
cy.get(formWidgetsPage.defaultDate).click({ force: true });
|
||||
cy.SetDateToToday();
|
||||
// it("Datepicker min/max date validation", function() {
|
||||
// cy.get(formWidgetsPage.defaultDate).click({ force: true });
|
||||
// cy.SetDateToToday();
|
||||
|
||||
cy.get(formWidgetsPage.minDate)
|
||||
.first()
|
||||
.click();
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1000);
|
||||
cy.setDate(-1, "ddd MMM DD YYYY");
|
||||
// cy.get(formWidgetsPage.minDate)
|
||||
// .first()
|
||||
// .click();
|
||||
// // eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
// cy.wait(1000);
|
||||
// cy.setDate(-1, "ddd MMM DD YYYY");
|
||||
|
||||
cy.get(formWidgetsPage.maxDate)
|
||||
.first()
|
||||
.click();
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1000);
|
||||
cy.setDate(1, "ddd MMM DD YYYY");
|
||||
// cy.get(formWidgetsPage.maxDate)
|
||||
// .first()
|
||||
// .click();
|
||||
// // eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
// cy.wait(1000);
|
||||
// cy.setDate(1, "ddd MMM DD YYYY");
|
||||
|
||||
cy.PublishtheApp();
|
||||
cy.get(publishPage.datepickerWidget + " .bp3-input").click();
|
||||
// cy.PublishtheApp();
|
||||
// cy.get(publishPage.datepickerWidget + " .bp3-input").click();
|
||||
|
||||
const minDate = Cypress.moment()
|
||||
.add(2, "days")
|
||||
.format("ddd MMM DD YYYY");
|
||||
const maxDate = Cypress.moment()
|
||||
.add(2, "days")
|
||||
.format("ddd MMM DD YYYY");
|
||||
// const minDate = Cypress.moment()
|
||||
// .add(2, "days")
|
||||
// .format("ddd MMM DD YYYY");
|
||||
// const maxDate = Cypress.moment()
|
||||
// .add(2, "days")
|
||||
// .format("ddd MMM DD YYYY");
|
||||
|
||||
cy.get(`.DayPicker-Day[aria-label=\"${minDate}\"]`).should(
|
||||
"have.attr",
|
||||
"aria-disabled",
|
||||
"true",
|
||||
);
|
||||
cy.get(`.DayPicker-Day[aria-label=\"${maxDate}\"]`).should(
|
||||
"have.attr",
|
||||
"aria-disabled",
|
||||
"true",
|
||||
);
|
||||
});
|
||||
// cy.get(`.DayPicker-Day[aria-label=\"${minDate}\"]`).should(
|
||||
// "have.attr",
|
||||
// "aria-disabled",
|
||||
// "true",
|
||||
// );
|
||||
// cy.get(`.DayPicker-Day[aria-label=\"${maxDate}\"]`).should(
|
||||
// "have.attr",
|
||||
// "aria-disabled",
|
||||
// "true",
|
||||
// );
|
||||
// });
|
||||
|
||||
// it("Datepicker default date validation", function() {
|
||||
// cy.get(formWidgetsPage.defaultDate).click();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
const dsl = require("../../../fixtures/ModalDsl.json");
|
||||
const publishPage = require("../../../locators/publishWidgetspage.json");
|
||||
const explorer = require("../../../locators/explorerlocators.json");
|
||||
|
||||
describe("Modal Widget Functionality", function() {
|
||||
beforeEach(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
it("Add new Modal", () => {
|
||||
cy.get(explorer.addWidget).click();
|
||||
cy.dragAndDropToCanvas("modalwidget", { x: 300, y: -300 });
|
||||
cy.get(".t--modal-widget").should("exist");
|
||||
});
|
||||
|
||||
it("Open Existing Modal from created Widgets list", () => {
|
||||
cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Widgets)").click({
|
||||
multiple: true,
|
||||
});
|
||||
cy.get(".bp3-icon-caret-right ~ .t--entity-name:contains(Modal1)").click({
|
||||
multiple: true,
|
||||
});
|
||||
cy.get(".t--modal-widget").should("exist");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const Layoutpage = require("../../../locators/Layout.json");
|
||||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const publish = require("../../../locators/publishWidgetspage.json");
|
||||
const dsl = require("../../../fixtures/basicTabledsl.json");
|
||||
const pages = require("../../../locators/Pages.json");
|
||||
const tabname = "UpdatedTab";
|
||||
|
||||
describe("Tab widget test", function() {
|
||||
const apiName = "Table1";
|
||||
const tableName = "Table";
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
it("Rename API with table widget name validation test", function() {
|
||||
cy.log("Login Successful");
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.log("Navigation to API Panel screen successful");
|
||||
cy.CreateApiAndValidateUniqueEntityName(apiName);
|
||||
});
|
||||
|
||||
it("Rename Table widget with api name validation test", function() {
|
||||
cy.GlobalSearchEntity("Table1");
|
||||
cy.RenameEntity(tableName);
|
||||
cy.validateMessage(tableName);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const Layoutpage = require("../../../locators/Layout.json");
|
||||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const publish = require("../../../locators/publishWidgetspage.json");
|
||||
const dsl = require("../../../fixtures/tabdsl.json");
|
||||
const pages = require("../../../locators/Pages.json");
|
||||
const tabname = "UpdatedTab";
|
||||
|
||||
describe("Tab widget test", function() {
|
||||
const tabname = "UpdatedTab";
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
it("Tab Widget Functionality To rename Tabs from entity explorer", function() {
|
||||
cy.GlobalSearchEntity("Tab 1");
|
||||
cy.RenameEntity(tabname);
|
||||
});
|
||||
|
||||
it("Tab name validation in properties and widget ", function() {
|
||||
cy.openPropertyPane("tabswidget");
|
||||
cy.closePropertyPane();
|
||||
cy.get(Layoutpage.tabWidget)
|
||||
.contains(tabname)
|
||||
.click({ force: true })
|
||||
.should("be.visible");
|
||||
});
|
||||
|
||||
it("Tab Widget Functionality To delete Tabs from entity explorer", function() {
|
||||
cy.GlobalSearchEntity("Tab 2");
|
||||
cy.RenameEntity(tabname);
|
||||
cy.validateMessage(tabname);
|
||||
cy.deleteEntity();
|
||||
cy.get(commonlocators.entityExplorersearch).should("be.visible");
|
||||
cy.get(commonlocators.entityExplorersearch)
|
||||
.clear()
|
||||
.type("Tab 2");
|
||||
cy.get(
|
||||
commonlocators.entitySearchResult.concat("Tab 2").concat("')"),
|
||||
).should("not.exist");
|
||||
});
|
||||
|
||||
/* To be enabled once the bug is fixed
|
||||
it("Publish app and check for the widget name", function() {
|
||||
cy.PublishtheApp();
|
||||
cy.get(publish.tabWidget)
|
||||
.contains(tabname)
|
||||
.click({ force: true })
|
||||
.should("be.selected");
|
||||
cy.get(publish.tabWidget)
|
||||
.contains("Tab 2")
|
||||
.click({ force: true })
|
||||
.should("be.selected");
|
||||
});
|
||||
|
||||
it("Tab Widget Functionality To Unchecked Visible Widget", function() {
|
||||
cy.get(publish.backToEditor).click();
|
||||
cy.openPropertyPane("tabswidget");
|
||||
cy.closePropertyPane();
|
||||
cy.get(Layoutpage.tabWidget)
|
||||
.contains("Tab 2")
|
||||
.click({ force: true })
|
||||
.should("not.be.visible");
|
||||
});
|
||||
*/
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// put your clean up code if any
|
||||
});
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
const dsl = require("../../../fixtures/datePicker2dsl.json");
|
||||
const publishPage = require("../../../locators/publishWidgetspage.json");
|
||||
const pages = require("../../../locators/Pages.json");
|
||||
|
||||
describe("DatePicker Widget Property pane tests with js bindings", function() {
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
it("Datepicker default date validation with js binding and default date", function() {
|
||||
cy.openPropertyPane("datepickerwidget2");
|
||||
cy.get(".t--property-control-defaultdate .bp3-input").clear();
|
||||
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
|
||||
cy.testJsontext("defaultdate", "{{ moment().add(-1,'days') }}");
|
||||
});
|
||||
|
||||
it("Text widgets binding with datepicker", function() {
|
||||
cy.SearchEntityandOpen("Text1");
|
||||
cy.testJsontext("text", "{{DatePicker1.formattedDate}}");
|
||||
cy.closePropertyPane();
|
||||
cy.SearchEntityandOpen("Text2");
|
||||
cy.testJsontext("text", "{{DatePicker1.selectedDate}}");
|
||||
cy.closePropertyPane();
|
||||
});
|
||||
|
||||
it("Text widgets binding with datepicker", function() {
|
||||
cy.openPropertyPane("datepickerwidget2");
|
||||
cy.selectDateFormat("DD/MM/YYYY");
|
||||
cy.assertDateFormat();
|
||||
cy.closePropertyPane();
|
||||
cy.assertDateFormat();
|
||||
});
|
||||
it("Datepicker default date validation with js binding", function() {
|
||||
cy.PublishtheApp();
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(10000);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
const dsl = require("../../../fixtures/datePicker2dsl.json");
|
||||
const publishPage = require("../../../locators/publishWidgetspage.json");
|
||||
const pages = require("../../../locators/Pages.json");
|
||||
|
||||
describe("DatePicker Widget Property pane tests with js bindings", function() {
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
it("Datepicker default date validation with js binding", function() {
|
||||
cy.openPropertyPane("datepickerwidget2");
|
||||
cy.get(".t--property-control-defaultdate .bp3-input").clear();
|
||||
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
|
||||
cy.testJsontext("defaultdate", "{{moment().toISOString()}}");
|
||||
cy.get(formWidgetsPage.toggleJsMinDate).click();
|
||||
cy.testJsontext(
|
||||
"mindate",
|
||||
"{{moment().subtract(10, 'days').toISOString()}}",
|
||||
);
|
||||
cy.get(formWidgetsPage.toggleJsMaxDate).click();
|
||||
cy.testJsontext("maxdate", "{{moment().add(10, 'days').toISOString()}}");
|
||||
/*
|
||||
cy.get(formWidgetsPage.datepickerWidget + " .bp3-input").should(
|
||||
"contain.value",
|
||||
"14/02/2021",
|
||||
);
|
||||
cy.PublishtheApp();
|
||||
cy.get(publishPage.datepickerWidget + " .bp3-input").should(
|
||||
"contain.value",
|
||||
"14/02/2021",
|
||||
);
|
||||
*/
|
||||
});
|
||||
|
||||
it("Text widgets binding with datepicker", function() {
|
||||
cy.SearchEntityandOpen("Text1");
|
||||
cy.testJsontext("text", "{{DatePicker1.formattedDate}}");
|
||||
cy.closePropertyPane();
|
||||
cy.SearchEntityandOpen("Text2");
|
||||
cy.testJsontext("text", "{{DatePicker1.selectedDate}}");
|
||||
cy.closePropertyPane();
|
||||
});
|
||||
|
||||
it("Text widgets binding with datepicker", function() {
|
||||
cy.openPropertyPane("datepickerwidget2");
|
||||
cy.selectDateFormat("YYYY-MM-DD");
|
||||
cy.assertDateFormat();
|
||||
cy.selectDateFormat("YYYY-MM-DD HH:mm");
|
||||
cy.assertDateFormat();
|
||||
cy.selectDateFormat("YYYY-MM-DDTHH:mm:ss.sssZ");
|
||||
cy.assertDateFormat();
|
||||
cy.selectDateFormat("DD/MM/YYYY");
|
||||
cy.assertDateFormat();
|
||||
cy.selectDateFormat("DD/MM/YYYY HH:mm");
|
||||
cy.closePropertyPane();
|
||||
cy.assertDateFormat();
|
||||
});
|
||||
|
||||
it("Datepicker default date validation with strings", function() {
|
||||
cy.openPropertyPane("datepickerwidget2");
|
||||
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
|
||||
cy.get(".t--property-control-defaultdate .bp3-input").clear();
|
||||
cy.get(".t--property-control-defaultdate .bp3-input").type("2020-02-01");
|
||||
cy.closePropertyPane();
|
||||
cy.openPropertyPane("datepickerwidget2");
|
||||
cy.get(formWidgetsPage.toggleJsMinDate).click();
|
||||
cy.get(".t--property-control-mindate .bp3-input").type("2020-01-01");
|
||||
cy.get(formWidgetsPage.toggleJsMaxDate).click();
|
||||
cy.get(".t--property-control-maxdate .bp3-input").type("2020-02-10");
|
||||
cy.closePropertyPane();
|
||||
});
|
||||
|
||||
it("Datepicker default date validation with js binding", function() {
|
||||
cy.PublishtheApp();
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(10000);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const formWidgetsPage = require("../../../locators/FormWidgets.json");
|
||||
const dsl = require("../../../fixtures/newFormDsl.json");
|
||||
const publishPage = require("../../../locators/publishWidgetspage.json");
|
||||
const pages = require("../../../locators/Pages.json");
|
||||
|
||||
describe("DatePicker Widget Property pane tests with js bindings", function() {
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.openPropertyPane("datepickerwidget");
|
||||
});
|
||||
|
||||
it("Datepicker default date validation with js binding", function() {
|
||||
cy.get(".t--property-control-defaultdate .bp3-input").clear();
|
||||
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
|
||||
cy.testJsontext(
|
||||
"defaultdate",
|
||||
"{{moment('14/02/2021', 'DD/MM/YYYY').format('DD/MM/YYYY')}}",
|
||||
);
|
||||
cy.get(formWidgetsPage.toggleJsMinDate).click();
|
||||
cy.testJsontext(
|
||||
"mindate",
|
||||
"{{moment('12/02/2021', 'DD/MM/YYYY').format('DD/MM/YYYY')}}",
|
||||
);
|
||||
cy.get(formWidgetsPage.toggleJsMaxDate).click();
|
||||
cy.testJsontext(
|
||||
"maxdate",
|
||||
"{{moment('17/02/2021', 'DD/MM/YYYY').format('DD/MM/YYYY')}}",
|
||||
);
|
||||
cy.get(formWidgetsPage.datepickerWidget + " .bp3-input").should(
|
||||
"contain.value",
|
||||
"14/02/2021",
|
||||
);
|
||||
cy.PublishtheApp();
|
||||
cy.get(publishPage.datepickerWidget + " .bp3-input").should(
|
||||
"contain.value",
|
||||
"14/02/2021",
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cy.get(publishPage.backToEditor).click({ force: true });
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const dsl = require("../../../../fixtures/tableWidgetDsl.json");
|
||||
const commonlocators = require("../../../../locators/commonlocators.json");
|
||||
const testdata = require("../../../../fixtures/testdata.json");
|
||||
|
||||
describe("API Panel Test Functionality", function() {
|
||||
before(() => {
|
||||
|
|
@ -8,7 +9,7 @@ describe("API Panel Test Functionality", function() {
|
|||
it("Will load an api on load", function() {
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.CreateAPI("PageLoadApi");
|
||||
cy.enterDatasourceAndPath("https://reqres.in/api/", "users");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
cy.WaitAutoSave();
|
||||
cy.get("li:contains('Settings')").click({ force: true });
|
||||
cy.get("[data-cy=executeOnLoad]").click({ force: true });
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.log("Creation of FirstAPI Action successful");
|
||||
cy.SelectAction(testdata.putAction);
|
||||
cy.EnterSourceDetailsWithbody(
|
||||
testdata.baseUrl2,
|
||||
testdata.baseUrl,
|
||||
testdata.methodput,
|
||||
testdata.headerKey,
|
||||
testdata.headerValue,
|
||||
|
|
@ -35,7 +35,7 @@ describe("API Panel Test Functionality", function() {
|
|||
.type(json, { force: true });
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.validateRequest(testdata.baseUrl2, testdata.methodput, testdata.Put);
|
||||
cy.validateRequest(testdata.baseUrl, testdata.methodput, testdata.Put);
|
||||
});
|
||||
cy.ResponseStatusCheck("200 OK");
|
||||
cy.log("Response code check successful");
|
||||
|
|
@ -48,7 +48,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.log("Creation of FirstAPI Action successful");
|
||||
cy.SelectAction(testdata.postAction);
|
||||
cy.EnterSourceDetailsWithbody(
|
||||
testdata.baseUrl2,
|
||||
testdata.baseUrl,
|
||||
testdata.methodpost,
|
||||
testdata.headerKey,
|
||||
testdata.headerValue,
|
||||
|
|
@ -62,7 +62,7 @@ describe("API Panel Test Functionality", function() {
|
|||
.type(json, { force: true });
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.validateRequest(testdata.baseUrl2, testdata.methodpost, testdata.Post);
|
||||
cy.validateRequest(testdata.baseUrl, testdata.methodpost, testdata.Post);
|
||||
});
|
||||
cy.ResponseStatusCheck("201 CREATED");
|
||||
cy.log("Response code check successful");
|
||||
|
|
@ -75,7 +75,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.log("Creation of FirstAPI Action successful");
|
||||
cy.SelectAction(testdata.patchAction);
|
||||
cy.EnterSourceDetailsWithbody(
|
||||
testdata.baseUrl2,
|
||||
testdata.baseUrl,
|
||||
testdata.methodpatch,
|
||||
testdata.headerKey,
|
||||
testdata.headerValue,
|
||||
|
|
@ -90,7 +90,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.validateRequest(
|
||||
testdata.baseUrl2,
|
||||
testdata.baseUrl,
|
||||
testdata.methodpatch,
|
||||
testdata.Patch,
|
||||
);
|
||||
|
|
@ -106,19 +106,19 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.log("Creation of FirstAPI Action successful");
|
||||
cy.SelectAction(testdata.deleteAction);
|
||||
cy.EnterSourceDetailsWithbody(
|
||||
testdata.baseUrl2,
|
||||
testdata.methodpatch,
|
||||
testdata.baseUrl,
|
||||
testdata.methoddelete,
|
||||
testdata.headerKey,
|
||||
testdata.headerValue,
|
||||
);
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.validateRequest(
|
||||
testdata.baseUrl2,
|
||||
testdata.methodpatch,
|
||||
testdata.baseUrl,
|
||||
testdata.methoddelete,
|
||||
testdata.Delete,
|
||||
);
|
||||
cy.ResponseStatusCheck("204 NO_CONTENT");
|
||||
cy.ResponseStatusCheck("200");
|
||||
cy.log("Response code check successful");
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ describe("Test curl import flow", function() {
|
|||
cy.NavigateToApiEditor();
|
||||
cy.get(ApiEditor.curlImage).click({ force: true });
|
||||
cy.get("textarea").type(
|
||||
'curl -d \'{"name":"morpheus","job":"leader"}\' -H Content-Type:application/json -X POST https://reqres.in/api/users',
|
||||
'curl -d \'{"name":"morpheus","job":"leader"}\' -H Content-Type:application/json -X POST https://mock-api.appsmith.com/echo/post',
|
||||
{
|
||||
force: true,
|
||||
parseSpecialCharSequences: false,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ describe("Moustache test Functionality", function() {
|
|||
it("Moustache test Functionality", function() {
|
||||
cy.openPropertyPane("textwidget");
|
||||
cy.widgetText("Api", widgetsPage.textWidget, widgetsPage.textInputval);
|
||||
cy.testCodeMirror("/api/users/2");
|
||||
cy.testCodeMirror("users");
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.log("Navigation to API Panel screen successful");
|
||||
cy.CreateAPI("TestAPINew");
|
||||
cy.log("Creation of API Action successful");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl2, testdata.moustacheMethod);
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.moustacheMethod);
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(300);
|
||||
cy.RunAPI();
|
||||
cy.ResponseStatusCheck(testdata.successStatusCode);
|
||||
cy.log("Response code check successful");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
const testdata = require("../../../../fixtures/testdata.json");
|
||||
|
||||
describe("Datasource form related tests", function() {
|
||||
it("Check whether the delete button has the right color", function() {
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.CreateAPI("Testapi");
|
||||
cy.enterDatasourceAndPath("https://reqres.in/api/", "users");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
|
||||
cy.get(".t--store-as-datasource").click();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
const testdata = require("../../../../fixtures/testdata.json");
|
||||
|
||||
describe("Create a rest datasource", function() {
|
||||
it("Create a rest datasource", function() {
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.CreateAPI("Testapi");
|
||||
cy.enterDatasourceAndPath("https://reqres.in/api/", "users");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
|
||||
cy.get(".t--store-as-datasource").click();
|
||||
|
||||
cy.saveDatasource();
|
||||
cy.contains(".datasource-highlight", "https://reqres.in");
|
||||
cy.contains(".datasource-highlight", "https://mock-api.appsmith.com");
|
||||
|
||||
cy.SaveAndRunAPI();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,37 +1,36 @@
|
|||
{
|
||||
"checkboxWidget": ".t--draggable-checkboxwidget",
|
||||
"dropdownWidget": ".t--draggable-dropdownwidget",
|
||||
"dropdownSelectionType": ".t--property-control-selectiontype",
|
||||
"radioWidget": ".t--draggable-radiogroupwidget",
|
||||
"radioOnSelectionChangeDropdown": ".t--property-control-onselectionchange",
|
||||
"nextDayBtn": ".DayPicker-Day[aria-selected='true'] + div.DayPicker-Day",
|
||||
"datepickerWidget": ".t--draggable-datepickerwidget",
|
||||
"defaultDate": ".t--property-control-defaultdate input",
|
||||
"minDate": ".t--property-control-mindate input",
|
||||
"maxDate": ".t--property-control-maxdate input",
|
||||
"filepickerWidget": ".t--draggable-filepickerwidget",
|
||||
"formWidget": ".t--draggable-formwidget",
|
||||
"richTextEditorWidget": ".t--draggable-richtexteditorwidget",
|
||||
"richEditorOnTextChange": ".t--property-control-ontextchange",
|
||||
"optionvalue": ".t--property-control-defaultselectedvalue .kDwnRc",
|
||||
"defselected": ".CodeMirror textarea",
|
||||
"dropdowninner": ".bp3-button > .bp3-button-text",
|
||||
"Textinput": ".t--property-control-options .CodeMirror-code",
|
||||
"labelvalue": ".t--draggable-dropdownwidget label",
|
||||
"dropdownInput": ".bp3-tag-input-values",
|
||||
"labelradio": ".t--draggable-radiogroupwidget label",
|
||||
"deleteradiovalue": ".t--property-control-options mask",
|
||||
"inputRadio": ".t--draggable-radiogroupwidget input",
|
||||
"defaultSelect": ".t--property-control-defaultselectedvalue .CodeMirror-code",
|
||||
"formInner": ".t--draggable-formwidget span.t--widget-name",
|
||||
"radioInput": ".t--draggable-radiogroupwidget span.t--widget-name",
|
||||
"radioAddButton": ".t--property-control-options button",
|
||||
"formD": "div[type='FORM_WIDGET']",
|
||||
"datepickerFooter": ".bp3-datepicker-footer span",
|
||||
"disableJs":".t--property-control-disable input[type='checkbox']",
|
||||
"switchWidget": ".t--draggable-switchwidget",
|
||||
"toggleJsDefaultDate": ".t--property-control-defaultdate .t--js-toggle",
|
||||
"toggleJsMinDate": ".t--property-control-mindate .t--js-toggle",
|
||||
"toggleJsMaxDate": ".t--property-control-maxdate .t--js-toggle"
|
||||
|
||||
}
|
||||
"checkboxWidget": ".t--draggable-checkboxwidget",
|
||||
"dropdownWidget": ".t--draggable-dropdownwidget",
|
||||
"dropdownSelectionType": ".t--property-control-selectiontype",
|
||||
"radioWidget": ".t--draggable-radiogroupwidget",
|
||||
"radioOnSelectionChangeDropdown": ".t--property-control-onselectionchange",
|
||||
"nextDayBtn": ".DayPicker-Day[aria-selected='true'] + div.DayPicker-Day",
|
||||
"datepickerWidget": ".t--draggable-datepickerwidget",
|
||||
"defaultDate": ".t--property-control-defaultdate input",
|
||||
"minDate": ".t--property-control-mindate input",
|
||||
"maxDate": ".t--property-control-maxdate input",
|
||||
"filepickerWidget": ".t--draggable-filepickerwidget",
|
||||
"formWidget": ".t--draggable-formwidget",
|
||||
"richTextEditorWidget": ".t--draggable-richtexteditorwidget",
|
||||
"richEditorOnTextChange": ".t--property-control-ontextchange",
|
||||
"optionvalue": ".t--property-control-defaultselectedvalue .kDwnRc",
|
||||
"defselected": ".CodeMirror textarea",
|
||||
"dropdowninner": ".bp3-button > .bp3-button-text",
|
||||
"Textinput": ".t--property-control-options .CodeMirror-code",
|
||||
"labelvalue": ".t--draggable-dropdownwidget label",
|
||||
"dropdownInput": ".bp3-tag-input-values",
|
||||
"labelradio": ".t--draggable-radiogroupwidget label",
|
||||
"deleteradiovalue": ".t--property-control-options mask",
|
||||
"inputRadio": ".t--draggable-radiogroupwidget input",
|
||||
"defaultSelect": ".t--property-control-defaultselectedvalue .CodeMirror-code",
|
||||
"formInner": ".t--draggable-formwidget span.t--widget-name",
|
||||
"radioInput": ".t--draggable-radiogroupwidget span.t--widget-name",
|
||||
"radioAddButton": ".t--property-control-options button",
|
||||
"formD": "div[type='FORM_WIDGET']",
|
||||
"datepickerFooter": ".bp3-datepicker-footer span",
|
||||
"disableJs": ".t--property-control-disable input[type='checkbox']",
|
||||
"switchWidget": ".t--draggable-switchwidget",
|
||||
"toggleJsDefaultDate": ".t--property-control-defaultdate .t--js-toggle",
|
||||
"toggleJsMinDate": ".t--property-control-mindate .t--js-toggle",
|
||||
"toggleJsMaxDate": ".t--property-control-maxdate .t--js-toggle"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,5 +51,6 @@
|
|||
"propertyList": ".t--entity-property",
|
||||
"actionlist": ".action div div",
|
||||
"settings": "li:contains('Settings')",
|
||||
"onPageLoad": "[data-cy=executeOnLoad]"
|
||||
"onPageLoad": "[data-cy=executeOnLoad]",
|
||||
"renameEntity": ".single-select >div:contains('Edit Name')"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,5 +25,6 @@
|
|||
"addWidget":".widgets .t--entity-add-btn",
|
||||
"dropHere":".appsmith_widget_0",
|
||||
"closeWidgets":".t--close-widgets-sidebar",
|
||||
"addDBQueryEntity": ".dbqueries .t--entity-add-btn"
|
||||
"addDBQueryEntity": ".dbqueries .t--entity-add-btn",
|
||||
"editEntity": ".t--entity-name input"
|
||||
}
|
||||
|
|
@ -690,6 +690,37 @@ Cypress.Commands.add("switchToAPIInputTab", () => {
|
|||
.click({ force: true });
|
||||
});
|
||||
|
||||
Cypress.Commands.add("selectDateFormat", (value) => {
|
||||
cy.get(".t--property-control-dateformat button")
|
||||
.first()
|
||||
.click({ force: true });
|
||||
cy.get("ul.bp3-menu")
|
||||
.children()
|
||||
.contains(value)
|
||||
.click();
|
||||
});
|
||||
|
||||
Cypress.Commands.add("assertDateFormat", () => {
|
||||
cy.get(".t--draggable-datepickerwidget2 input")
|
||||
.first()
|
||||
.invoke("attr", "value")
|
||||
.then((text) => {
|
||||
const firstTxt = text;
|
||||
cy.log("date time : ", firstTxt);
|
||||
cy.get(commonlocators.labelTextStyle)
|
||||
.first()
|
||||
.should("contain", firstTxt);
|
||||
cy.get(commonlocators.labelTextStyle)
|
||||
.last()
|
||||
.invoke("text")
|
||||
.then((text) => {
|
||||
const secondText = text;
|
||||
cy.log("date time : ", secondText);
|
||||
expect(firstTxt).not.to.equal(secondText);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("selectPaginationType", (option) => {
|
||||
cy.xpath(option).click({ force: true });
|
||||
});
|
||||
|
|
@ -825,6 +856,40 @@ Cypress.Commands.add("CopyAPIToHome", () => {
|
|||
);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("RenameEntity", (value) => {
|
||||
cy.xpath(apiwidget.popover)
|
||||
.last()
|
||||
.click({ force: true });
|
||||
cy.get(apiwidget.renameEntity).click({ force: true });
|
||||
cy.wait(2000);
|
||||
cy.get(explorer.editEntity)
|
||||
.last()
|
||||
.type(value, { force: true });
|
||||
cy.wait(3000);
|
||||
});
|
||||
|
||||
Cypress.Commands.add("CreateApiAndValidateUniqueEntityName", (apiname) => {
|
||||
cy.get(apiwidget.createapi).click({ force: true });
|
||||
cy.wait("@createNewApi");
|
||||
cy.get(apiwidget.resourceUrl).should("be.visible");
|
||||
cy.get(apiwidget.ApiName).click({ force: true });
|
||||
cy.get(apiwidget.apiTxt)
|
||||
.clear()
|
||||
.type(apiname, { force: true })
|
||||
.should("have.value", apiname);
|
||||
cy.get(".t--nameOfApi .error-message").should(($x) => {
|
||||
console.log($x);
|
||||
expect($x).contain(apiname.concat(" is already being used."));
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("validateMessage", (value) => {
|
||||
cy.get(".bp3-popover-content").should(($x) => {
|
||||
console.log($x);
|
||||
expect($x).contain(value.concat(" is already being used."));
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add("DeleteAPIFromSideBar", () => {
|
||||
cy.deleteEntity();
|
||||
cy.wait("@deleteAction").should(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { ControlIcons } from "icons/ControlIcons";
|
|||
import { AnyStyledComponent } from "styled-components";
|
||||
import { Skin } from "constants/DefaultTheme";
|
||||
import AutoToolTipComponent from "components/designSystems/appsmith/TableComponent/AutoToolTipComponent";
|
||||
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent";
|
||||
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent2";
|
||||
import {
|
||||
OperatorTypes,
|
||||
Condition,
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ const ReactTableComponent = (props: ReactTableComponentProps) => {
|
|||
row: { original: Record<string, unknown>; index: number },
|
||||
isSelected: boolean,
|
||||
) => {
|
||||
if (!isSelected || !!props.multiRowSelection) {
|
||||
if (!isSelected || props.multiRowSelection || !props.multiRowSelection) {
|
||||
props.onRowClick(row.original, row.index);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { getBorderCSSShorthand, labelStyle } from "constants/DefaultTheme";
|
||||
import {
|
||||
getBorderCSSShorthand,
|
||||
labelStyle,
|
||||
IntentColors,
|
||||
} from "constants/DefaultTheme";
|
||||
import { ControlGroup, Classes, Label } from "@blueprintjs/core";
|
||||
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
|
||||
import { DateInput } from "@blueprintjs/datetime";
|
||||
|
|
@ -11,24 +15,30 @@ import { WIDGET_PADDING } from "constants/WidgetConstants";
|
|||
import { TimePrecision } from "@blueprintjs/datetime";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
|
||||
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
|
||||
import { DATE_WIDGET_DEFAULT_VALIDATION_ERROR } from "constants/messages";
|
||||
|
||||
const StyledControlGroup = styled(ControlGroup)`
|
||||
const StyledControlGroup = styled(ControlGroup)<{ isValid: boolean }>`
|
||||
&&& {
|
||||
.${Classes.INPUT} {
|
||||
box-shadow: none;
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
font-size: ${(props) => props.theme.fontSizes[3]}px;
|
||||
border: ${(props) => getBorderCSSShorthand(props.theme.borders[2])};
|
||||
border-color: ${(props) =>
|
||||
!props.isValid ? IntentColors.danger : Colors.GEYSER_LIGHT};
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
align-items: center;
|
||||
&:active {
|
||||
border-color: ${Colors.HIT_GRAY};
|
||||
border-color: ${(props) =>
|
||||
!props.isValid ? IntentColors.danger : Colors.HIT_GRAY};
|
||||
}
|
||||
&:focus {
|
||||
border: ${(props) => getBorderCSSShorthand(props.theme.borders[2])};
|
||||
border-color: #80bdff;
|
||||
border-color: ${(props) =>
|
||||
!props.isValid ? IntentColors.danger : "#80bdff"};
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 0.1rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
|
@ -95,10 +105,17 @@ class DatePickerComponent extends React.Component<
|
|||
.clone()
|
||||
.set({ month: 11, date: 31, year: year + 20 })
|
||||
.toDate();
|
||||
|
||||
const isValid = this.state.selectedDate
|
||||
? this.isValidDate(this.parseDate(this.state.selectedDate))
|
||||
: true;
|
||||
const value =
|
||||
isValid && this.state.selectedDate
|
||||
? this.parseDate(this.state.selectedDate)
|
||||
: null;
|
||||
return (
|
||||
<StyledControlGroup
|
||||
fill
|
||||
isValid={isValid}
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
|
|
@ -115,29 +132,58 @@ class DatePickerComponent extends React.Component<
|
|||
</Label>
|
||||
)}
|
||||
{
|
||||
<DateInput
|
||||
className={this.props.isLoading ? "bp3-skeleton" : ""}
|
||||
formatDate={this.formatDate}
|
||||
parseDate={this.parseDate}
|
||||
placeholder={"Select Date"}
|
||||
disabled={this.props.isDisabled}
|
||||
showActionsBar={true}
|
||||
timePrecision={TimePrecision.MINUTE}
|
||||
closeOnSelection
|
||||
onChange={this.onDateSelected}
|
||||
value={
|
||||
this.state.selectedDate
|
||||
? this.parseDate(this.state.selectedDate)
|
||||
: null
|
||||
}
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
/>
|
||||
<ErrorTooltip
|
||||
isOpen={!isValid}
|
||||
message={DATE_WIDGET_DEFAULT_VALIDATION_ERROR}
|
||||
>
|
||||
<DateInput
|
||||
className={this.props.isLoading ? "bp3-skeleton" : ""}
|
||||
formatDate={this.formatDate}
|
||||
parseDate={this.parseDate}
|
||||
placeholder={"Select Date"}
|
||||
disabled={this.props.isDisabled}
|
||||
showActionsBar={true}
|
||||
timePrecision={TimePrecision.MINUTE}
|
||||
closeOnSelection
|
||||
onChange={this.onDateSelected}
|
||||
value={value}
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
/>
|
||||
</ErrorTooltip>
|
||||
}
|
||||
</StyledControlGroup>
|
||||
);
|
||||
}
|
||||
|
||||
isValidDate = (date: Date): boolean => {
|
||||
let isValid = true;
|
||||
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
|
||||
const parsedCurrentDate = moment(date);
|
||||
if (this.props.minDate) {
|
||||
const parsedMinDate = moment(this.props.minDate, dateFormat);
|
||||
if (
|
||||
this.props.minDate &&
|
||||
parsedMinDate.isValid() &&
|
||||
parsedCurrentDate.isBefore(parsedMinDate)
|
||||
) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
if (this.props.maxDate) {
|
||||
const parsedMaxDate = moment(this.props.maxDate, dateFormat);
|
||||
if (
|
||||
isValid &&
|
||||
this.props.maxDate &&
|
||||
parsedMaxDate.isValid() &&
|
||||
parsedCurrentDate.isAfter(parsedMaxDate)
|
||||
) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
formatDate = (date: Date): string => {
|
||||
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
|
||||
return moment(date).format(dateFormat);
|
||||
|
|
@ -165,9 +211,6 @@ class DatePickerComponent extends React.Component<
|
|||
const date = selectedDate ? this.formatDate(selectedDate) : "";
|
||||
this.setState({ selectedDate: date });
|
||||
|
||||
// if date is null ( if date is cleared ), don't call onDateSelected
|
||||
if (!selectedDate) return false;
|
||||
|
||||
onDateSelected(date);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,236 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { labelStyle, IntentColors } from "constants/DefaultTheme";
|
||||
import { ControlGroup, Classes, Label } from "@blueprintjs/core";
|
||||
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
|
||||
import { DateInput } from "@blueprintjs/datetime";
|
||||
import moment from "moment-timezone";
|
||||
import "../../../../node_modules/@blueprintjs/datetime/lib/css/blueprint-datetime.css";
|
||||
import { DatePickerType } from "widgets/DatePickerWidget";
|
||||
import { WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
import { TimePrecision } from "@blueprintjs/datetime";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
|
||||
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
|
||||
import { DATE_WIDGET_DEFAULT_VALIDATION_ERROR } from "constants/messages";
|
||||
|
||||
const StyledControlGroup = styled(ControlGroup)<{ isValid: boolean }>`
|
||||
&&& {
|
||||
.${Classes.INPUT} {
|
||||
box-shadow: none;
|
||||
border: 1px solid;
|
||||
border-color: ${(props) =>
|
||||
!props.isValid ? IntentColors.danger : Colors.GEYSER_LIGHT};
|
||||
border-radius: ${(props) => props.theme.radii[1]}px;
|
||||
width: 100%;
|
||||
height: inherit;
|
||||
align-items: center;
|
||||
&:active {
|
||||
border-color: ${(props) =>
|
||||
!props.isValid ? IntentColors.danger : Colors.HIT_GRAY};
|
||||
}
|
||||
&:focus {
|
||||
border-color: ${(props) =>
|
||||
!props.isValid ? IntentColors.danger : Colors.MYSTIC};
|
||||
}
|
||||
}
|
||||
.${Classes.INPUT_GROUP} {
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.${Classes.CONTROL_GROUP} {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
label {
|
||||
${labelStyle}
|
||||
flex: 0 1 30%;
|
||||
margin: 7px ${WIDGET_PADDING * 2}px 0 0;
|
||||
text-align: right;
|
||||
align-self: flex-start;
|
||||
max-width: calc(30% - ${WIDGET_PADDING}px);
|
||||
}
|
||||
}
|
||||
&&& {
|
||||
input {
|
||||
border: 1px solid;
|
||||
border-color: ${(props) =>
|
||||
!props.isValid ? IntentColors.danger : Colors.HIT_GRAY};
|
||||
border-radius: ${(props) => props.theme.radii[1]}px;
|
||||
box-shadow: none;
|
||||
color: ${Colors.OXFORD_BLUE};
|
||||
font-size: ${(props) => props.theme.fontSizes[3]}px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
class DatePickerComponent extends React.Component<
|
||||
DatePickerComponentProps,
|
||||
DatePickerComponentState
|
||||
> {
|
||||
constructor(props: DatePickerComponentProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedDate: props.selectedDate,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: DatePickerComponentProps) {
|
||||
if (
|
||||
this.props.selectedDate !== this.state.selectedDate &&
|
||||
!moment(this.props.selectedDate).isSame(
|
||||
moment(prevProps.selectedDate),
|
||||
"seconds",
|
||||
)
|
||||
) {
|
||||
this.setState({ selectedDate: this.props.selectedDate });
|
||||
}
|
||||
}
|
||||
|
||||
getValidDate = (date: string, format: string) => {
|
||||
const _date = moment(date, format);
|
||||
return _date.isValid() ? _date.toDate() : undefined;
|
||||
};
|
||||
|
||||
render() {
|
||||
const now = moment();
|
||||
const year = now.get("year");
|
||||
const minDate = this.props.minDate
|
||||
? new Date(this.props.minDate)
|
||||
: now
|
||||
.clone()
|
||||
.set({ month: 0, date: 1, year: year - 100 })
|
||||
.toDate();
|
||||
const maxDate = this.props.maxDate
|
||||
? new Date(this.props.maxDate)
|
||||
: now
|
||||
.clone()
|
||||
.set({ month: 11, date: 31, year: year + 20 })
|
||||
.toDate();
|
||||
const isValid = this.state.selectedDate
|
||||
? this.isValidDate(new Date(this.state.selectedDate))
|
||||
: true;
|
||||
const value =
|
||||
isValid && this.state.selectedDate
|
||||
? new Date(this.state.selectedDate)
|
||||
: null;
|
||||
return (
|
||||
<StyledControlGroup
|
||||
fill
|
||||
isValid={isValid}
|
||||
onClick={(e: any) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{this.props.label && (
|
||||
<Label
|
||||
className={
|
||||
this.props.isLoading
|
||||
? Classes.SKELETON
|
||||
: Classes.TEXT_OVERFLOW_ELLIPSIS
|
||||
}
|
||||
>
|
||||
{this.props.label}
|
||||
</Label>
|
||||
)}
|
||||
{
|
||||
<ErrorTooltip
|
||||
isOpen={!isValid}
|
||||
message={DATE_WIDGET_DEFAULT_VALIDATION_ERROR}
|
||||
>
|
||||
<DateInput
|
||||
className={this.props.isLoading ? "bp3-skeleton" : ""}
|
||||
formatDate={this.formatDate}
|
||||
parseDate={this.parseDate}
|
||||
placeholder={"Select Date"}
|
||||
disabled={this.props.isDisabled}
|
||||
showActionsBar={true}
|
||||
timePrecision={TimePrecision.MINUTE}
|
||||
closeOnSelection
|
||||
onChange={this.onDateSelected}
|
||||
value={value}
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
/>
|
||||
</ErrorTooltip>
|
||||
}
|
||||
</StyledControlGroup>
|
||||
);
|
||||
}
|
||||
|
||||
isValidDate = (date: Date): boolean => {
|
||||
let isValid = true;
|
||||
const parsedCurrentDate = moment(date);
|
||||
if (this.props.minDate) {
|
||||
const parsedMinDate = moment(this.props.minDate);
|
||||
if (
|
||||
this.props.minDate &&
|
||||
parsedMinDate.isValid() &&
|
||||
parsedCurrentDate.isBefore(parsedMinDate)
|
||||
) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
if (this.props.maxDate) {
|
||||
const parsedMaxDate = moment(this.props.maxDate);
|
||||
if (
|
||||
isValid &&
|
||||
this.props.maxDate &&
|
||||
parsedMaxDate.isValid() &&
|
||||
parsedCurrentDate.isAfter(parsedMaxDate)
|
||||
) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
return isValid;
|
||||
};
|
||||
|
||||
formatDate = (date: Date): string => {
|
||||
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
|
||||
return moment(date).format(dateFormat);
|
||||
};
|
||||
|
||||
parseDate = (dateStr: string): Date => {
|
||||
const date = moment(dateStr);
|
||||
|
||||
if (date.isValid()) return moment(dateStr).toDate();
|
||||
else return moment().toDate();
|
||||
};
|
||||
|
||||
/**
|
||||
* checks if selelectedDate is null or not,
|
||||
* sets state and calls props onDateSelected
|
||||
* if its null, don't call onDateSelected
|
||||
*
|
||||
* @param selectedDate
|
||||
*/
|
||||
onDateSelected = (selectedDate: Date, isUserChange: boolean) => {
|
||||
if (isUserChange) {
|
||||
const { onDateSelected } = this.props;
|
||||
|
||||
const date = selectedDate ? selectedDate.toISOString() : "";
|
||||
this.setState({ selectedDate: date });
|
||||
|
||||
onDateSelected(date);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
interface DatePickerComponentProps extends ComponentProps {
|
||||
label: string;
|
||||
dateFormat: string;
|
||||
enableTimePicker?: boolean;
|
||||
selectedDate?: string;
|
||||
minDate?: string;
|
||||
maxDate?: string;
|
||||
timezone?: string;
|
||||
datePickerType: DatePickerType;
|
||||
isDisabled: boolean;
|
||||
onDateSelected: (selectedDate: string) => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
interface DatePickerComponentState {
|
||||
selectedDate?: string;
|
||||
}
|
||||
|
||||
export default DatePickerComponent;
|
||||
|
|
@ -28,7 +28,7 @@ export const HintStyles = createGlobalStyle<{
|
|||
max-height: 20em;
|
||||
overflow-y: auto;
|
||||
background: ${(props) =>
|
||||
props.theme.colors.codeMirror.background.defaultState};
|
||||
props.editorTheme === EditorTheme.LIGHT ? "#FAFAFA" : "#262626"};
|
||||
box-shadow: 0px 12px 28px -6px rgba(0, 0, 0, 0.32);
|
||||
border-radius: 0px;
|
||||
${(props) =>
|
||||
|
|
@ -37,7 +37,8 @@ export const HintStyles = createGlobalStyle<{
|
|||
|
||||
.CodeMirror-hint {
|
||||
height: 24px;
|
||||
color: ${(props) => props.theme.colors.codeMirror.text};
|
||||
color: ${(props) =>
|
||||
props.editorTheme === EditorTheme.LIGHT ? "#090707" : "#FFFFFF"};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
width: 220px;
|
||||
|
|
@ -47,7 +48,7 @@ export const HintStyles = createGlobalStyle<{
|
|||
letter-spacing: -0.24px;
|
||||
&:hover {
|
||||
background: ${(props) =>
|
||||
props.theme.colors.codeMirror.background.hoverState};
|
||||
props.editorTheme === EditorTheme.LIGHT ? "#6A86CE" : "#157A96"};
|
||||
border-radius: 0px;
|
||||
color: #fff;
|
||||
&:after {
|
||||
|
|
@ -69,7 +70,7 @@ export const HintStyles = createGlobalStyle<{
|
|||
padding-left: ${(props) => props.theme.spaces[11]}px !important;
|
||||
&:hover{
|
||||
background: ${(props) =>
|
||||
props.theme.colors.codeMirror.background.hoverState};
|
||||
props.editorTheme === EditorTheme.LIGHT ? "#6A86CE" : "#157A96"};
|
||||
}
|
||||
}
|
||||
.CodeMirror-Tern-completion:before {
|
||||
|
|
@ -281,9 +282,7 @@ export const EditorWrapper = styled.div<{
|
|||
? `border-bottom: 1px solid ${Colors.MERCURY}`
|
||||
: `border: 1px solid ${Colors.MERCURY}`};
|
||||
background: ${(props) =>
|
||||
props.isFocused || props.fill
|
||||
? Colors.MERCURY
|
||||
: props.theme.colors.codeMirror.background.defaultState};
|
||||
props.isFocused || props.fill ? Colors.MERCURY : "#FAFAFA"};
|
||||
color: ${Colors.CHARCOAL};
|
||||
& {
|
||||
span.cm-operator {
|
||||
|
|
@ -303,9 +302,7 @@ export const EditorWrapper = styled.div<{
|
|||
? `border-bottom: 1px solid ${Colors.NERO}`
|
||||
: `border: 1px solid ${Colors.NERO}`};
|
||||
background: ${(props) =>
|
||||
props.isFocused || props.fill
|
||||
? Colors.NERO
|
||||
: props.theme.colors.codeMirror.background.defaultState};
|
||||
props.isFocused || props.fill ? Colors.NERO : "#262626"};
|
||||
color: ${Colors.LIGHT_GREY};
|
||||
}
|
||||
.cm-s-duotone-light .CodeMirror-linenumber,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import moment from "moment-timezone";
|
|||
import styled from "styled-components";
|
||||
import { TimePrecision } from "@blueprintjs/datetime";
|
||||
import { WidgetProps } from "widgets/BaseWidget";
|
||||
import { Toaster } from "components/ads/Toast";
|
||||
import { Variant } from "components/ads/common";
|
||||
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
|
||||
|
||||
const DatePickerControlWrapper = styled.div<{ isValid: boolean }>`
|
||||
|
|
@ -63,18 +61,22 @@ class DatePickerControl extends BaseControl<
|
|||
}
|
||||
|
||||
render() {
|
||||
const version = this.props.widgetProperties.version;
|
||||
const dateFormat =
|
||||
this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
|
||||
version === 2
|
||||
? ISO_DATE_FORMAT
|
||||
: this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
|
||||
const isValid = this.state.selectedDate
|
||||
? this.validateDate(moment(this.state.selectedDate, dateFormat).toDate())
|
||||
: true;
|
||||
const maxDate =
|
||||
this.props.widgetProperties?.evaluatedValues?.maxDate ?? this.maxDate;
|
||||
const minDate =
|
||||
this.props.widgetProperties?.evaluatedValues?.minDate ?? this.minDate;
|
||||
|
||||
const value =
|
||||
this.props.propertyValue && isValid
|
||||
? version === 2
|
||||
? new Date(this.props.propertyValue)
|
||||
: this.parseDate(this.props.propertyValue)
|
||||
: null;
|
||||
return (
|
||||
<DatePickerControlWrapper isValid={isValid}>
|
||||
<DatePickerControlWrapper isValid={true}>
|
||||
<StyledDatePicker
|
||||
formatDate={this.formatDate}
|
||||
parseDate={this.parseDate}
|
||||
|
|
@ -83,21 +85,7 @@ class DatePickerControl extends BaseControl<
|
|||
timePrecision={TimePrecision.MINUTE}
|
||||
closeOnSelection
|
||||
onChange={this.onDateSelected}
|
||||
maxDate={
|
||||
this.props.propertyName === "defaultDate"
|
||||
? this.getValidDate(maxDate, dateFormat)
|
||||
: undefined
|
||||
}
|
||||
minDate={
|
||||
this.props.propertyName === "defaultDate"
|
||||
? this.getValidDate(minDate, dateFormat)
|
||||
: undefined
|
||||
}
|
||||
value={
|
||||
this.props.propertyValue && isValid
|
||||
? this.parseDate(this.props.propertyValue)
|
||||
: null
|
||||
}
|
||||
value={value}
|
||||
/>
|
||||
</DatePickerControlWrapper>
|
||||
);
|
||||
|
|
@ -117,10 +105,13 @@ class DatePickerControl extends BaseControl<
|
|||
*/
|
||||
onDateSelected = (date: Date, isUserChange: boolean): void => {
|
||||
if (isUserChange) {
|
||||
const selectedDate = date ? this.formatDate(date) : undefined;
|
||||
const selectedDate = date
|
||||
? this.props.widgetProperties.version === 2
|
||||
? date.toISOString()
|
||||
: this.formatDate(date)
|
||||
: undefined;
|
||||
const isValid = this.validateDate(date);
|
||||
if (!isValid) return;
|
||||
|
||||
// if everything is ok, put date in state
|
||||
this.setState({ selectedDate: selectedDate });
|
||||
this.updateProperty(this.props.propertyName, selectedDate);
|
||||
|
|
@ -128,73 +119,14 @@ class DatePickerControl extends BaseControl<
|
|||
};
|
||||
|
||||
/**
|
||||
* checks:
|
||||
* 1. if max date is greater than the default date
|
||||
* 2. if default date is in range of min and max date
|
||||
* checks if date is of valid date format
|
||||
*/
|
||||
validateDate = (date: Date): boolean => {
|
||||
const dateFormat =
|
||||
this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
|
||||
const parsedSelectedDate = moment(date, dateFormat);
|
||||
//validate defaultDate if both minDate and maxDate is already selected
|
||||
if (this.props.propertyName === "defaultDate") {
|
||||
if (
|
||||
parsedSelectedDate.isValid() &&
|
||||
this.props.widgetProperties?.evaluatedValues?.minDate &&
|
||||
this.props.widgetProperties?.evaluatedValues?.maxDate
|
||||
) {
|
||||
const parsedMinDate = moment(
|
||||
this.props.widgetProperties.evaluatedValues.minDate,
|
||||
dateFormat,
|
||||
);
|
||||
const parsedMaxDate = moment(
|
||||
this.props.widgetProperties.evaluatedValues.maxDate,
|
||||
dateFormat,
|
||||
);
|
||||
if (
|
||||
parsedSelectedDate.isBefore(parsedMinDate) ||
|
||||
parsedSelectedDate.isAfter(parsedMaxDate)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.props.widgetProperties?.evaluatedValues?.value) {
|
||||
const parsedWidgetDate = moment(
|
||||
this.props.widgetProperties.evaluatedValues.value,
|
||||
dateFormat,
|
||||
);
|
||||
// checking if widget date is after min date
|
||||
if (this.props.propertyName === "minDate") {
|
||||
if (
|
||||
parsedSelectedDate.isValid() &&
|
||||
parsedWidgetDate.isBefore(parsedSelectedDate)
|
||||
) {
|
||||
Toaster.show({
|
||||
text: "Min date cannot be greater than current widget value.",
|
||||
variant: Variant.danger,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// checking if widget date is before max date
|
||||
if (this.props.propertyName === "maxDate") {
|
||||
if (
|
||||
parsedSelectedDate.isValid() &&
|
||||
parsedWidgetDate.isAfter(parsedSelectedDate)
|
||||
) {
|
||||
Toaster.show({
|
||||
text: "Max date cannot be less than current widget value.",
|
||||
variant: Variant.danger,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
this.props.widgetProperties.version === 2
|
||||
? ISO_DATE_FORMAT
|
||||
: this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
|
||||
return date ? moment(date, dateFormat).isValid() : true;
|
||||
};
|
||||
|
||||
formatDate = (date: Date): string => {
|
||||
|
|
@ -205,7 +137,9 @@ class DatePickerControl extends BaseControl<
|
|||
|
||||
parseDate = (dateStr: string): Date => {
|
||||
const dateFormat =
|
||||
this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
|
||||
this.props.widgetProperties.version === 2
|
||||
? ISO_DATE_FORMAT
|
||||
: this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
|
||||
const date = moment(dateStr, dateFormat);
|
||||
|
||||
if (date.isValid()) return moment(dateStr, dateFormat).toDate();
|
||||
|
|
|
|||
|
|
@ -24,6 +24,13 @@ const FIELD_VALUES: Record<
|
|||
isDisabled: "boolean",
|
||||
// onDateSelected: "Function Call",
|
||||
},
|
||||
DATE_PICKER_WIDGET2: {
|
||||
defaultDate: "string", //TODO:Vicky validate this property
|
||||
isRequired: "boolean",
|
||||
isVisible: "boolean",
|
||||
isDisabled: "boolean",
|
||||
// onDateSelected: "Function Call",
|
||||
},
|
||||
TABLE_WIDGET: {
|
||||
tableData: "Array<Object>",
|
||||
serverSidePaginationEnabled: "boolean",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ export const HelpMap = {
|
|||
path: "/widget-reference/datepicker",
|
||||
searchKey: "DatePicker",
|
||||
},
|
||||
DATE_PICKER_WIDGET2: {
|
||||
path: "/widget-reference/datepicker",
|
||||
searchKey: "DatePicker",
|
||||
},
|
||||
TABLE_WIDGET: {
|
||||
path: "/widget-reference/table",
|
||||
searchKey: "Table",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
SAVE_PAGE_SUCCESS: "SAVE_PAGE_SUCCESS",
|
||||
UPDATE_LAYOUT: "UPDATE_LAYOUT",
|
||||
WIDGET_ADD_CHILD: "WIDGET_ADD_CHILD",
|
||||
WIDGET_CHILD_ADDED: "WIDGET_CHILD_ADDED",
|
||||
WIDGET_REMOVE_CHILD: "WIDGET_REMOVE_CHILD",
|
||||
WIDGET_MOVE: "WIDGET_MOVE",
|
||||
WIDGET_RESIZE: "WIDGET_RESIZE",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export enum WidgetTypes {
|
|||
INPUT_WIDGET = "INPUT_WIDGET",
|
||||
CONTAINER_WIDGET = "CONTAINER_WIDGET",
|
||||
DATE_PICKER_WIDGET = "DATE_PICKER_WIDGET",
|
||||
DATE_PICKER_WIDGET2 = "DATE_PICKER_WIDGET2",
|
||||
TABLE_WIDGET = "TABLE_WIDGET",
|
||||
DROP_DOWN_WIDGET = "DROP_DOWN_WIDGET",
|
||||
CHECKBOX_WIDGET = "CHECKBOX_WIDGET",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export type Validator = (
|
|||
dataTree?: DataTree,
|
||||
) => ValidationResponse;
|
||||
|
||||
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.Z";
|
||||
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.sssZ";
|
||||
|
||||
export const JAVASCRIPT_KEYWORDS = {
|
||||
true: "true",
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ export const FORGOT_PASSWORD_PAGE_LOGIN_LINK = "Back to Sign In";
|
|||
export const ADD_API_TO_PAGE_SUCCESS_MESSAGE = "Api added to page.";
|
||||
export const INPUT_WIDGET_DEFAULT_VALIDATION_ERROR = "Invalid input";
|
||||
|
||||
export const DATE_WIDGET_DEFAULT_VALIDATION_ERROR = "Date out of range";
|
||||
|
||||
export const AUTOFIT_ALL_COLUMNS = "Autofit all columns";
|
||||
export const AUTOFIT_THIS_COLUMN = "Autofit this column";
|
||||
export const AUTOFIT_COLUMN = "Autofit column";
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ describe("getAllPathsFromPropertyConfig", () => {
|
|||
isLoading: false,
|
||||
horizontalAlignment: "LEFT",
|
||||
parentColumnSpace: 74,
|
||||
version: 1,
|
||||
dynamicTriggerPathList: [
|
||||
{
|
||||
key: "primaryColumns.status.onClick",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export const WidgetIcons: {
|
|||
<ContainerIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
DATE_PICKER_WIDGET: (props: IconProps) => (
|
||||
DATE_PICKER_WIDGET2: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<DatePickerIcon />
|
||||
</IconWrapper>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
isDisabled: false,
|
||||
isVisible: true,
|
||||
isDefaultClickDisabled: true,
|
||||
version: 1,
|
||||
},
|
||||
TEXT_WIDGET: {
|
||||
text: "Label",
|
||||
|
|
@ -22,6 +23,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
rows: 1,
|
||||
columns: 4,
|
||||
widgetName: "Text",
|
||||
version: 1,
|
||||
},
|
||||
RICH_TEXT_EDITOR_WIDGET: {
|
||||
defaultText: "This is the initial <b>content</b> of the editor",
|
||||
|
|
@ -32,6 +34,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
widgetName: "RichTextEditor",
|
||||
isDefaultClickDisabled: true,
|
||||
inputType: "html",
|
||||
version: 1,
|
||||
},
|
||||
IMAGE_WIDGET: {
|
||||
defaultImage:
|
||||
|
|
@ -42,6 +45,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
rows: 3,
|
||||
columns: 4,
|
||||
widgetName: "Image",
|
||||
version: 1,
|
||||
},
|
||||
INPUT_WIDGET: {
|
||||
inputType: "TEXT",
|
||||
|
|
@ -49,6 +53,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
label: "",
|
||||
columns: 5,
|
||||
widgetName: "Input",
|
||||
version: 1,
|
||||
resetOnSubmit: true,
|
||||
},
|
||||
SWITCH_WIDGET: {
|
||||
|
|
@ -58,11 +63,13 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
defaultSwitchState: true,
|
||||
widgetName: "Switch",
|
||||
alignWidget: "LEFT",
|
||||
version: 1,
|
||||
},
|
||||
ICON_WIDGET: {
|
||||
widgetName: "Icon",
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
version: 1,
|
||||
},
|
||||
CONTAINER_WIDGET: {
|
||||
backgroundColor: "#FFFFFF",
|
||||
|
|
@ -85,6 +92,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
},
|
||||
],
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
DATE_PICKER_WIDGET: {
|
||||
isDisabled: false,
|
||||
|
|
@ -95,14 +103,26 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
columns: 5,
|
||||
widgetName: "DatePicker",
|
||||
defaultDate: moment().format("DD/MM/YYYY HH:mm"),
|
||||
version: 1,
|
||||
},
|
||||
DATE_PICKER_WIDGET2: {
|
||||
isDisabled: false,
|
||||
datePickerType: "DATE_PICKER",
|
||||
rows: 1,
|
||||
label: "",
|
||||
dateFormat: "DD/MM/YYYY HH:mm",
|
||||
columns: 5,
|
||||
widgetName: "DatePicker",
|
||||
defaultDate: moment().toISOString(),
|
||||
version: 2,
|
||||
},
|
||||
|
||||
VIDEO_WIDGET: {
|
||||
rows: 7,
|
||||
columns: 7,
|
||||
widgetName: "Video",
|
||||
url: "https://www.youtube.com/watch?v=mzqK0QIZRLs",
|
||||
autoPlay: false,
|
||||
version: 1,
|
||||
},
|
||||
TABLE_WIDGET: {
|
||||
rows: 7,
|
||||
|
|
@ -138,6 +158,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
orderAmount: 19.99,
|
||||
},
|
||||
],
|
||||
version: 1,
|
||||
},
|
||||
DROP_DOWN_WIDGET: {
|
||||
rows: 1,
|
||||
|
|
@ -151,6 +172,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
],
|
||||
widgetName: "Dropdown",
|
||||
defaultOptionValue: "VEG",
|
||||
version: 1,
|
||||
},
|
||||
CHECKBOX_WIDGET: {
|
||||
rows: 1,
|
||||
|
|
@ -158,6 +180,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
label: "Label",
|
||||
defaultCheckedState: true,
|
||||
widgetName: "Checkbox",
|
||||
version: 1,
|
||||
alignWidget: "LEFT",
|
||||
},
|
||||
RADIO_GROUP_WIDGET: {
|
||||
|
|
@ -170,6 +193,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
],
|
||||
defaultOptionValue: "M",
|
||||
widgetName: "RadioGroup",
|
||||
version: 1,
|
||||
},
|
||||
ALERT_WIDGET: {
|
||||
alertType: "NOTIFICATION",
|
||||
|
|
@ -179,6 +203,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
header: "",
|
||||
message: "",
|
||||
widgetName: "Alert",
|
||||
version: 1,
|
||||
},
|
||||
FILE_PICKER_WIDGET: {
|
||||
rows: 1,
|
||||
|
|
@ -189,6 +214,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
maxFileSize: 5,
|
||||
widgetName: "FilePicker",
|
||||
isDefaultClickDisabled: true,
|
||||
version: 1,
|
||||
},
|
||||
TABS_WIDGET: {
|
||||
rows: 7,
|
||||
|
|
@ -225,10 +251,11 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
},
|
||||
],
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
MODAL_WIDGET: {
|
||||
rows: 456,
|
||||
columns: 456,
|
||||
rows: 6,
|
||||
columns: 6,
|
||||
size: "MODAL_SMALL",
|
||||
canEscapeKeyClose: true,
|
||||
detachFromLayout: true,
|
||||
|
|
@ -236,6 +263,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
shouldScrollContents: true,
|
||||
widgetName: "Modal",
|
||||
children: [],
|
||||
version: 1,
|
||||
blueprint: {
|
||||
view: [
|
||||
{
|
||||
|
|
@ -248,6 +276,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
isDisabled: false,
|
||||
shouldScrollContents: false,
|
||||
children: [],
|
||||
version: 1,
|
||||
blueprint: {
|
||||
view: [
|
||||
{
|
||||
|
|
@ -258,6 +287,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
iconName: "cross",
|
||||
iconSize: 24,
|
||||
color: "#040627",
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -267,6 +297,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
props: {
|
||||
text: "Modal Title",
|
||||
textStyle: "HEADING",
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -276,6 +307,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
props: {
|
||||
text: "Cancel",
|
||||
buttonStyle: "SECONDARY_BUTTON",
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -285,6 +317,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
props: {
|
||||
text: "Confirm",
|
||||
buttonStyle: "PRIMARY_BUTTON",
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -323,6 +356,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
rows: 0,
|
||||
columns: 0,
|
||||
widgetName: "Canvas",
|
||||
version: 1,
|
||||
},
|
||||
CHART_WIDGET: {
|
||||
rows: 8,
|
||||
|
|
@ -331,6 +365,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
chartType: "LINE_CHART",
|
||||
chartName: "Sales on working days",
|
||||
allowHorizontalScroll: false,
|
||||
version: 1,
|
||||
chartData: [
|
||||
{
|
||||
seriesName: "Sales",
|
||||
|
|
@ -375,6 +410,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
widgetName: "FormButton",
|
||||
text: "Submit",
|
||||
isDefaultClickDisabled: true,
|
||||
version: 1,
|
||||
},
|
||||
FORM_WIDGET: {
|
||||
rows: 13,
|
||||
|
|
@ -392,6 +428,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
canExtend: false,
|
||||
detachFromLayout: true,
|
||||
children: [],
|
||||
version: 1,
|
||||
blueprint: {
|
||||
view: [
|
||||
{
|
||||
|
|
@ -401,6 +438,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
props: {
|
||||
text: "Form",
|
||||
textStyle: "HEADING",
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -412,6 +450,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
buttonStyle: "PRIMARY_BUTTON",
|
||||
disabledWhenInvalid: true,
|
||||
resetFormOnClick: true,
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -423,6 +462,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
buttonStyle: "SECONDARY_BUTTON",
|
||||
disabledWhenInvalid: false,
|
||||
resetFormOnClick: true,
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
@ -444,12 +484,14 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
|
|||
allowZoom: true,
|
||||
mapCenter: { lat: -34.397, long: 150.644 },
|
||||
defaultMarkers: [{ lat: -34.397, long: 150.644, title: "Test A" }],
|
||||
version: 1,
|
||||
},
|
||||
SKELETON_WIDGET: {
|
||||
isLoading: true,
|
||||
rows: 1,
|
||||
columns: 1,
|
||||
widgetName: "Skeleton",
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
configVersion: 1,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const WidgetSidebarResponse: WidgetCardProps[] = [
|
|||
key: generateReactKey(),
|
||||
},
|
||||
{
|
||||
type: "DATE_PICKER_WIDGET",
|
||||
type: "DATE_PICKER_WIDGET2",
|
||||
widgetCardName: "DatePicker",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
|
|
@ -93,6 +93,11 @@ const WidgetSidebarResponse: WidgetCardProps[] = [
|
|||
widgetCardName: "Video",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
{
|
||||
type: "MODAL_WIDGET",
|
||||
widgetCardName: "Modal",
|
||||
key: generateReactKey(),
|
||||
},
|
||||
];
|
||||
|
||||
export default WidgetSidebarResponse;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState } from "react";
|
||||
import { connect, useSelector } from "react-redux";
|
||||
import { reduxForm, InjectedFormProps, formValueSelector } from "redux-form";
|
||||
import { formValueSelector, InjectedFormProps, reduxForm } from "redux-form";
|
||||
import {
|
||||
HTTP_METHOD_OPTIONS,
|
||||
HTTP_METHODS,
|
||||
|
|
@ -11,8 +11,8 @@ import FormRow from "components/editorComponents/FormRow";
|
|||
import { PaginationField } from "api/ActionAPI";
|
||||
import { API_EDITOR_FORM_NAME } from "constants/forms";
|
||||
import Pagination from "./Pagination";
|
||||
import { PaginationType, Action } from "entities/Action";
|
||||
import { HelpMap, HelpBaseURL } from "constants/HelpConstants";
|
||||
import { Action, PaginationType } from "entities/Action";
|
||||
import { HelpBaseURL, HelpMap } from "constants/HelpConstants";
|
||||
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
|
||||
import PostBodyData from "./PostBodyData";
|
||||
import ApiResponseView from "components/editorComponents/ApiResponseView";
|
||||
|
|
@ -255,8 +255,6 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
|
|||
e.stopPropagation();
|
||||
history.replace(BUILDER_PAGE_URL(applicationId, pageId));
|
||||
};
|
||||
|
||||
// Enforcing the light theme
|
||||
const theme = EditorTheme.LIGHT;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { Plugin } from "api/PluginApi";
|
|||
import { Action, PaginationType, RapidApiAction } from "entities/Action";
|
||||
import { getApiName } from "selectors/formSelectors";
|
||||
import Spinner from "components/editorComponents/Spinner";
|
||||
import styled, { ThemeProvider } from "styled-components";
|
||||
import styled from "styled-components";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import { changeApi } from "actions/apiPaneActions";
|
||||
import PerformanceTracker, {
|
||||
|
|
@ -178,55 +178,53 @@ class ApiEditor extends React.Component<Props> {
|
|||
/>
|
||||
);
|
||||
return (
|
||||
<ThemeProvider theme={this.props.lightTheme}>
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{apiId ? (
|
||||
<>
|
||||
{formUiComponent === "ApiEditorForm" && (
|
||||
<ApiEditorForm
|
||||
pluginId={pluginId}
|
||||
paginationType={paginationType}
|
||||
isRunning={isRunning}
|
||||
isDeleting={isDeleting}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
apiName={this.props.apiName}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
position: "relative",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{apiId ? (
|
||||
<>
|
||||
{formUiComponent === "ApiEditorForm" && (
|
||||
<ApiEditorForm
|
||||
pluginId={pluginId}
|
||||
paginationType={paginationType}
|
||||
isRunning={isRunning}
|
||||
isDeleting={isDeleting}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
apiName={this.props.apiName}
|
||||
/>
|
||||
)}
|
||||
|
||||
{formUiComponent === "RapidApiEditorForm" && (
|
||||
<RapidApiEditorForm
|
||||
apiName={this.props.apiName}
|
||||
apiId={this.props.match.params.apiId}
|
||||
paginationType={paginationType}
|
||||
isRunning={isRunning}
|
||||
isDeleting={isDeleting}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
location={this.props.location}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
apiHomeScreen
|
||||
)}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
{formUiComponent === "RapidApiEditorForm" && (
|
||||
<RapidApiEditorForm
|
||||
apiName={this.props.apiName}
|
||||
apiId={this.props.match.params.apiId}
|
||||
paginationType={paginationType}
|
||||
isRunning={isRunning}
|
||||
isDeleting={isDeleting}
|
||||
onDeleteClick={this.handleDeleteClick}
|
||||
onRunClick={this.handleRunClick}
|
||||
appName={
|
||||
this.props.currentApplication
|
||||
? this.props.currentApplication.name
|
||||
: ""
|
||||
}
|
||||
location={this.props.location}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
apiHomeScreen
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import {
|
|||
import { ControlProps } from "components/formControls/BaseControl";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import ActionSettings from "pages/Editor/ActionSettings";
|
||||
import { queryActionSettingsConfig } from "mockResponses/ActionSettings";
|
||||
import { addTableWidgetFromQuery } from "actions/widgetActions";
|
||||
import { OnboardingStep } from "constants/OnboardingConstants";
|
||||
import Boxed from "components/editorComponents/Onboarding/Boxed";
|
||||
|
|
@ -279,6 +278,7 @@ type QueryFormProps = {
|
|||
state: any;
|
||||
};
|
||||
editorConfig?: any;
|
||||
settingConfig: any;
|
||||
loadingFormConfigs: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -579,7 +579,7 @@ const QueryEditorForm: React.FC<Props> = (props: Props) => {
|
|||
panelComponent: (
|
||||
<SettingsWrapper>
|
||||
<ActionSettings
|
||||
actionSettingsConfig={queryActionSettingsConfig}
|
||||
actionSettingsConfig={props.settingConfig}
|
||||
formName={QUERY_EDITOR_FORM_NAME}
|
||||
/>
|
||||
</SettingsWrapper>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import PerformanceTracker, {
|
|||
PerformanceTransactionName,
|
||||
} from "utils/PerformanceTracker";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { queryActionSettingsConfig } from "mockResponses/ActionSettings";
|
||||
|
||||
const EmptyStateContainer = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -58,6 +59,7 @@ type ReduxStateProps = {
|
|||
isCreating: boolean;
|
||||
pluginImages: Record<string, string>;
|
||||
editorConfig: any;
|
||||
settingConfig: any;
|
||||
loadingFormConfigs: boolean;
|
||||
isEditorInitialized: boolean;
|
||||
};
|
||||
|
|
@ -118,6 +120,7 @@ class QueryEditor extends React.Component<Props> {
|
|||
runErrorMessage,
|
||||
loadingFormConfigs,
|
||||
editorConfig,
|
||||
settingConfig,
|
||||
isEditorInitialized,
|
||||
} = this.props;
|
||||
const { applicationId, pageId } = this.props.match.params;
|
||||
|
|
@ -154,6 +157,7 @@ class QueryEditor extends React.Component<Props> {
|
|||
onRunClick={this.handleRunClick}
|
||||
dataSources={dataSources}
|
||||
editorConfig={editorConfig}
|
||||
settingConfig={settingConfig}
|
||||
loadingFormConfigs={loadingFormConfigs}
|
||||
DATASOURCES_OPTIONS={DATASOURCES_OPTIONS}
|
||||
executedQueryData={responses[queryId]}
|
||||
|
|
@ -178,7 +182,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
|||
const { runErrorMessage } = state.ui.queryPane;
|
||||
const { plugins } = state.entities;
|
||||
|
||||
const { editorConfigs, loadingFormConfigs } = plugins;
|
||||
const { editorConfigs, loadingFormConfigs, settingConfigs } = plugins;
|
||||
const formData = getFormValues(QUERY_EDITOR_FORM_NAME)(state) as QueryAction;
|
||||
const queryAction = getAction(
|
||||
state,
|
||||
|
|
@ -194,6 +198,15 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
|||
editorConfig = editorConfigs[pluginId];
|
||||
}
|
||||
|
||||
let settingConfig: any;
|
||||
|
||||
if (settingConfigs && pluginId) {
|
||||
settingConfig = settingConfigs[pluginId];
|
||||
}
|
||||
if (!settingConfig) {
|
||||
settingConfig = queryActionSettingsConfig;
|
||||
}
|
||||
|
||||
return {
|
||||
pluginImages: getPluginImages(state),
|
||||
plugins: getPlugins(state),
|
||||
|
|
@ -205,6 +218,7 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
|
|||
isDeleting: state.ui.queryPane.isDeleting[props.match.params.queryId],
|
||||
formData,
|
||||
editorConfig,
|
||||
settingConfig,
|
||||
loadingFormConfigs,
|
||||
isCreating: state.ui.apiPane.isCreating,
|
||||
isEditorInitialized: getIsEditorInitialized(state),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ import { isMac } from "utils/helpers";
|
|||
import { getSelectedWidget } from "selectors/ui";
|
||||
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
|
||||
import Welcome from "./Welcome";
|
||||
import { getThemeDetails, ThemeMode } from "selectors/themeSelectors";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
import { Theme } from "constants/DefaultTheme";
|
||||
|
||||
type EditorProps = {
|
||||
currentApplicationId?: string;
|
||||
|
|
@ -55,6 +58,7 @@ type EditorProps = {
|
|||
cutSelectedWidget: () => void;
|
||||
user?: User;
|
||||
selectedWidget?: string;
|
||||
lightTheme: Theme;
|
||||
};
|
||||
|
||||
type Props = EditorProps & RouteComponentProps<BuilderRouteParams>;
|
||||
|
|
@ -200,21 +204,23 @@ class Editor extends Component<Props> {
|
|||
);
|
||||
}
|
||||
return (
|
||||
<DndProvider
|
||||
backend={TouchBackend}
|
||||
options={{
|
||||
enableMouseEvents: true,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Helmet>
|
||||
<meta charSet="utf-8" />
|
||||
<title>Editor | Appsmith</title>
|
||||
</Helmet>
|
||||
<MainContainer />
|
||||
</div>
|
||||
<ConfirmRunModal />
|
||||
</DndProvider>
|
||||
<ThemeProvider theme={this.props.lightTheme}>
|
||||
<DndProvider
|
||||
backend={TouchBackend}
|
||||
options={{
|
||||
enableMouseEvents: true,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Helmet>
|
||||
<meta charSet="utf-8" />
|
||||
<title>Editor | Appsmith</title>
|
||||
</Helmet>
|
||||
<MainContainer />
|
||||
</div>
|
||||
<ConfirmRunModal />
|
||||
</DndProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -229,6 +235,7 @@ const mapStateToProps = (state: AppState) => ({
|
|||
user: getCurrentUser(state),
|
||||
selectedWidget: getSelectedWidget(state),
|
||||
creatingOnboardingDatabase: state.ui.onBoarding.showOnboardingLoader,
|
||||
lightTheme: getThemeDetails(state, ThemeMode.LIGHT),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: any) => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export interface PluginFormPayload {
|
|||
id: string;
|
||||
form: any[];
|
||||
editor: any[];
|
||||
setting: any[];
|
||||
}
|
||||
|
||||
export interface PluginDataState {
|
||||
|
|
@ -17,6 +18,7 @@ export interface PluginDataState {
|
|||
loading: boolean;
|
||||
formConfigs: Record<string, any[]>;
|
||||
editorConfigs: Record<string, any[]>;
|
||||
settingConfigs: Record<string, any[]>;
|
||||
loadingFormConfigs: boolean;
|
||||
loadingDBFormConfigs: boolean;
|
||||
}
|
||||
|
|
@ -26,6 +28,7 @@ const initialState: PluginDataState = {
|
|||
loading: false,
|
||||
formConfigs: {},
|
||||
editorConfigs: {},
|
||||
settingConfigs: {},
|
||||
loadingFormConfigs: false,
|
||||
loadingDBFormConfigs: false,
|
||||
};
|
||||
|
|
@ -71,6 +74,10 @@ const pluginsReducer = createReducer(initialState, {
|
|||
...state.editorConfigs,
|
||||
[action.payload.id]: action.payload.editor,
|
||||
},
|
||||
settingConfigs: {
|
||||
...state.settingConfigs,
|
||||
[action.payload.id]: action.payload.setting,
|
||||
},
|
||||
};
|
||||
},
|
||||
[ReduxActionErrorTypes.FETCH_PLUGIN_FORM_ERROR]: (state: PluginDataState) => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { ImageWidgetProps } from "widgets/ImageWidget";
|
|||
import { InputWidgetProps } from "widgets/InputWidget";
|
||||
import { RichTextEditorWidgetProps } from "widgets/RichTextEditorWidget";
|
||||
import { DatePickerWidgetProps } from "../../widgets/DatePickerWidget";
|
||||
import { DatePickerWidget2Props } from "../../widgets/DatePickerWidget2";
|
||||
import { TableWidgetProps } from "../../widgets/TableWidget/TableWidgetConstants";
|
||||
import { DropdownWidgetProps } from "../../widgets/DropdownWidget";
|
||||
import { CheckboxWidgetProps } from "../../widgets/CheckboxWidget";
|
||||
|
|
@ -59,6 +60,7 @@ export interface WidgetConfigReducerState {
|
|||
CONTAINER_WIDGET: Partial<ContainerWidgetProps<WidgetProps>> &
|
||||
WidgetConfigProps;
|
||||
DATE_PICKER_WIDGET: Partial<DatePickerWidgetProps> & WidgetConfigProps;
|
||||
DATE_PICKER_WIDGET2: Partial<DatePickerWidget2Props> & WidgetConfigProps;
|
||||
TABLE_WIDGET: Partial<TableWidgetProps> & WidgetConfigProps;
|
||||
VIDEO_WIDGET: Partial<VideoWidgetProps> & WidgetConfigProps;
|
||||
DROP_DOWN_WIDGET: Partial<DropdownWidgetProps> & WidgetConfigProps;
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import {
|
|||
getAction,
|
||||
getCurrentPageNameByActionId,
|
||||
getPageNameByPageId,
|
||||
getSettingConfig,
|
||||
} from "selectors/entitiesSelector";
|
||||
import { getDataSources } from "selectors/editorSelectors";
|
||||
import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
|
||||
|
|
@ -77,13 +78,13 @@ export function* createActionSaga(
|
|||
try {
|
||||
let payload = actionPayload.payload;
|
||||
if (actionPayload.payload.pluginId) {
|
||||
let formConfig;
|
||||
formConfig = yield select(
|
||||
let editorConfig;
|
||||
editorConfig = yield select(
|
||||
getEditorConfig,
|
||||
actionPayload.payload.pluginId,
|
||||
);
|
||||
|
||||
if (!formConfig) {
|
||||
if (!editorConfig) {
|
||||
const formConfigResponse: GenericApiResponse<any> = yield PluginsApi.fetchFormConfig(
|
||||
actionPayload.payload.pluginId,
|
||||
);
|
||||
|
|
@ -96,13 +97,24 @@ export function* createActionSaga(
|
|||
},
|
||||
});
|
||||
|
||||
formConfig = yield select(
|
||||
editorConfig = yield select(
|
||||
getEditorConfig,
|
||||
actionPayload.payload.pluginId,
|
||||
);
|
||||
}
|
||||
const settingConfig = yield select(
|
||||
getSettingConfig,
|
||||
actionPayload.payload.pluginId,
|
||||
);
|
||||
|
||||
const initialValues = yield call(getConfigInitialValues, formConfig);
|
||||
let initialValues = yield call(getConfigInitialValues, editorConfig);
|
||||
if (settingConfig) {
|
||||
const settingInitialValues = yield call(
|
||||
getConfigInitialValues,
|
||||
settingConfig,
|
||||
);
|
||||
initialValues = merge(initialValues, settingInitialValues);
|
||||
}
|
||||
payload = merge(initialValues, actionPayload.payload);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,17 @@ export function* showModalByNameSaga(
|
|||
}
|
||||
}
|
||||
|
||||
export function* showIfModalSaga(
|
||||
action: ReduxAction<{ widgetId: string; type: string }>,
|
||||
) {
|
||||
if (action.payload.type === "MODAL_WIDGET") {
|
||||
yield put({
|
||||
type: ReduxActionTypes.SHOW_MODAL,
|
||||
payload: { modalId: action.payload.widgetId },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function* showModalSaga(action: ReduxAction<{ modalId: string }>) {
|
||||
// First we close the currently open modals (if any)
|
||||
// Notice the empty payload.
|
||||
|
|
@ -184,5 +195,6 @@ export default function* modalSagas() {
|
|||
takeLatest(ReduxActionTypes.CREATE_MODAL_INIT, createModalSaga),
|
||||
takeLatest(ReduxActionTypes.SHOW_MODAL, showModalSaga),
|
||||
takeLatest(ReduxActionTypes.SHOW_MODAL_BY_NAME, showModalByNameSaga),
|
||||
takeLatest(ReduxActionTypes.WIDGET_CHILD_ADDED, showIfModalSaga),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
const { id } = actionPayload.payload;
|
||||
const state = yield select();
|
||||
const editorConfigs = state.entities.plugins.editorConfigs;
|
||||
const settingConfigs = state.entities.plugins.settingConfigs;
|
||||
let configInitialValues = {};
|
||||
// // Typescript says Element does not have blur function but it does;
|
||||
// document.activeElement &&
|
||||
|
|
@ -82,6 +83,7 @@ function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
currentEditorConfig = success.payload.editor;
|
||||
}
|
||||
}
|
||||
const currentSettingConfig = settingConfigs[action.datasource.pluginId];
|
||||
|
||||
// If config exists
|
||||
if (currentEditorConfig) {
|
||||
|
|
@ -92,6 +94,14 @@ function* changeQuerySaga(actionPayload: ReduxAction<{ id: string }>) {
|
|||
);
|
||||
}
|
||||
|
||||
if (currentSettingConfig) {
|
||||
const settingInitialValues = yield call(
|
||||
getConfigInitialValues,
|
||||
currentSettingConfig,
|
||||
);
|
||||
configInitialValues = merge(configInitialValues, settingInitialValues);
|
||||
}
|
||||
|
||||
// Merge the initial values and action.
|
||||
const formInitialValues = merge(configInitialValues, action);
|
||||
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ function* getChildWidgetProps(
|
|||
parentColumnSpace,
|
||||
widgetName,
|
||||
widgetProps,
|
||||
restDefaultConfig.version,
|
||||
);
|
||||
|
||||
widget.widgetId = newWidgetId;
|
||||
|
|
@ -239,7 +240,6 @@ export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) {
|
|||
const start = performance.now();
|
||||
Toaster.clear();
|
||||
const { widgetId } = addChildAction.payload;
|
||||
|
||||
// Get the current parent widget whose child will be the new widget.
|
||||
const stateParent: FlattenedWidgetProps = yield select(getWidget, widgetId);
|
||||
// const parent = Object.assign({}, stateParent);
|
||||
|
|
@ -262,6 +262,13 @@ export function* addChildSaga(addChildAction: ReduxAction<WidgetAddChild>) {
|
|||
|
||||
widgets[parent.widgetId] = parent;
|
||||
log.debug("add child computations took", performance.now() - start, "ms");
|
||||
yield put({
|
||||
type: ReduxActionTypes.WIDGET_CHILD_ADDED,
|
||||
payload: {
|
||||
widgetId: childWidgetPayload.widgetId,
|
||||
type: addChildAction.payload.type,
|
||||
},
|
||||
});
|
||||
yield put(updateAndSaveLayout(widgets));
|
||||
} catch (error) {
|
||||
yield put({
|
||||
|
|
@ -1405,6 +1412,7 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction<string>) {
|
|||
parentRowSpace: GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
|
||||
parentColumnSpace: 1,
|
||||
isLoading: false,
|
||||
version: 1,
|
||||
props: {
|
||||
tableData: `{{${queryName}.data}}`,
|
||||
dynamicBindingPathList: [{ key: "tableData" }],
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ export const getEditorConfig = (state: AppState, pluginId: string): any[] => {
|
|||
return state.entities.plugins.editorConfigs[pluginId];
|
||||
};
|
||||
|
||||
export const getSettingConfig = (state: AppState, pluginId: string): any[] => {
|
||||
return state.entities.plugins.settingConfigs[pluginId];
|
||||
};
|
||||
|
||||
export const getActions = (state: AppState): ActionDataState =>
|
||||
state.entities.actions;
|
||||
|
||||
|
|
|
|||
|
|
@ -259,6 +259,18 @@ const dynamicPathListMigration = (
|
|||
return currentDSL;
|
||||
};
|
||||
|
||||
const addVersionNumberMigration = (
|
||||
currentDSL: ContainerWidgetProps<WidgetProps>,
|
||||
) => {
|
||||
if (currentDSL.children && currentDSL.children.length) {
|
||||
currentDSL.children = currentDSL.children.map(addVersionNumberMigration);
|
||||
}
|
||||
if (currentDSL.version === undefined) {
|
||||
currentDSL.version = 1;
|
||||
}
|
||||
return currentDSL;
|
||||
};
|
||||
|
||||
const canvasNameConflictMigration = (
|
||||
currentDSL: ContainerWidgetProps<WidgetProps>,
|
||||
props = { counter: 1 },
|
||||
|
|
@ -381,10 +393,15 @@ const transformDSL = (currentDSL: ContainerWidgetProps<WidgetProps>) => {
|
|||
}
|
||||
|
||||
if (currentDSL.version === 10) {
|
||||
currentDSL = rteDefaultValueMigration(currentDSL);
|
||||
currentDSL = addVersionNumberMigration(currentDSL);
|
||||
currentDSL.version = 11;
|
||||
}
|
||||
|
||||
if (currentDSL.version === 11) {
|
||||
currentDSL = rteDefaultValueMigration(currentDSL);
|
||||
currentDSL.version = 12;
|
||||
}
|
||||
|
||||
return currentDSL;
|
||||
};
|
||||
|
||||
|
|
@ -608,6 +625,7 @@ export const generateWidgetProps = (
|
|||
widgetId: string;
|
||||
renderMode: RenderMode;
|
||||
} & Partial<WidgetProps>,
|
||||
version: number,
|
||||
): ContainerWidgetProps<WidgetProps> => {
|
||||
if (parent) {
|
||||
const sizes = {
|
||||
|
|
@ -629,6 +647,7 @@ export const generateWidgetProps = (
|
|||
...sizes,
|
||||
...others,
|
||||
parentId: parent.widgetId,
|
||||
version,
|
||||
};
|
||||
delete props.rows;
|
||||
delete props.columns;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ import DatePickerWidget, {
|
|||
DatePickerWidgetProps,
|
||||
ProfiledDatePickerWidget,
|
||||
} from "widgets/DatePickerWidget";
|
||||
import DatePickerWidget2, {
|
||||
DatePickerWidget2Props,
|
||||
ProfiledDatePickerWidget2,
|
||||
} from "widgets/DatePickerWidget2";
|
||||
import FormWidget, { ProfiledFormWidget } from "widgets/FormWidget";
|
||||
import FormButtonWidget, {
|
||||
FormButtonWidgetProps,
|
||||
|
|
@ -286,6 +290,20 @@ export default class WidgetBuilderRegistry {
|
|||
DatePickerWidget.getMetaPropertiesMap(),
|
||||
DatePickerWidget.getPropertyPaneConfig(),
|
||||
);
|
||||
WidgetFactory.registerWidgetBuilder(
|
||||
"DATE_PICKER_WIDGET2",
|
||||
{
|
||||
buildWidget(widgetData: DatePickerWidget2Props): JSX.Element {
|
||||
return <ProfiledDatePickerWidget2 {...widgetData} />;
|
||||
},
|
||||
},
|
||||
DatePickerWidget2.getPropertyValidationMap(),
|
||||
DatePickerWidget2.getDerivedPropertiesMap(),
|
||||
DatePickerWidget2.getTriggerPropertyMap(),
|
||||
DatePickerWidget2.getDefaultPropertiesMap(),
|
||||
DatePickerWidget2.getMetaPropertiesMap(),
|
||||
DatePickerWidget.getPropertyPaneConfig(),
|
||||
);
|
||||
WidgetFactory.registerWidgetBuilder(
|
||||
"TABS_WIDGET",
|
||||
{
|
||||
|
|
@ -435,5 +453,20 @@ export default class WidgetBuilderRegistry {
|
|||
SkeletonWidget.getDefaultPropertiesMap(),
|
||||
SkeletonWidget.getMetaPropertiesMap(),
|
||||
);
|
||||
|
||||
WidgetFactory.registerWidgetBuilder(
|
||||
WidgetTypes.MODAL_WIDGET,
|
||||
{
|
||||
buildWidget(widgetData: ModalWidgetProps): JSX.Element {
|
||||
return <ProfiledModalWidget {...widgetData} />;
|
||||
},
|
||||
},
|
||||
ModalWidget.getPropertyValidationMap(),
|
||||
ModalWidget.getDerivedPropertiesMap(),
|
||||
ModalWidget.getTriggerPropertyMap(),
|
||||
ModalWidget.getDefaultPropertiesMap(),
|
||||
ModalWidget.getMetaPropertiesMap(),
|
||||
ModalWidget.getPropertyPaneConfig(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,15 @@ export const entityDefinitions = {
|
|||
selectedDate: "string",
|
||||
isDisabled: "bool",
|
||||
},
|
||||
DATE_PICKER_WIDGET2: {
|
||||
"!doc":
|
||||
"Datepicker is used to capture the date and time from a user. It can be used to filter data base on the input date range as well as to capture personal information such as date of birth",
|
||||
"!url": "https://docs.appsmith.com/widget-reference/datepicker",
|
||||
isVisible: isVisible,
|
||||
selectedDate: "string",
|
||||
formattedDate: "string",
|
||||
isDisabled: "bool",
|
||||
},
|
||||
CHECKBOX_WIDGET: {
|
||||
"!doc":
|
||||
"Checkbox is a simple UI widget you can use when you want users to make a binary choice",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ describe("dataTreeTypeDefCreator", () => {
|
|||
topRow: 1,
|
||||
bottomRow: 2,
|
||||
isLoading: false,
|
||||
version: 1,
|
||||
bindingPaths: {
|
||||
defaultText: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ const input1: ContainerWidgetProps<WidgetProps> = {
|
|||
widgetId: "fs785w9gcy",
|
||||
dynamicBindingPathList: [],
|
||||
renderMode: "CANVAS",
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -105,6 +106,7 @@ const input2: ContainerWidgetProps<WidgetProps> = {
|
|||
},
|
||||
],
|
||||
renderMode: "CANVAS",
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -157,6 +159,7 @@ const input3: ContainerWidgetProps<WidgetProps> = {
|
|||
onRowSelected: "{{showAlert('test','success')}}",
|
||||
onSearchTextChanged: "{{showAlert('fail','error')}}",
|
||||
renderMode: "CANVAS",
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -293,6 +296,7 @@ const output1 = {
|
|||
horizontalAlignment: "LEFT",
|
||||
verticalAlignment: "CENTER",
|
||||
renderMode: "CANVAS",
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -474,6 +478,7 @@ const output2 = {
|
|||
horizontalAlignment: "LEFT",
|
||||
verticalAlignment: "CENTER",
|
||||
renderMode: "CANVAS",
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -616,6 +621,7 @@ const output3 = {
|
|||
horizontalAlignment: "LEFT",
|
||||
verticalAlignment: "CENTER",
|
||||
renderMode: "CANVAS",
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -319,6 +319,7 @@ export interface WidgetBaseProps {
|
|||
widgetName: string;
|
||||
parentId: string;
|
||||
renderMode: RenderMode;
|
||||
version: number;
|
||||
}
|
||||
|
||||
export type WidgetRowCols = {
|
||||
|
|
|
|||
223
app/client/src/widgets/DatePickerWidget2.tsx
Normal file
223
app/client/src/widgets/DatePickerWidget2.tsx
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
import React from "react";
|
||||
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
|
||||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import { EventType } from "constants/ActionConstants";
|
||||
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent2";
|
||||
import {
|
||||
WidgetPropertyValidationType,
|
||||
BASE_WIDGET_VALIDATION,
|
||||
} from "utils/WidgetValidation";
|
||||
import { VALIDATION_TYPES } from "constants/WidgetValidation";
|
||||
import {
|
||||
DerivedPropertiesMap,
|
||||
TriggerPropertiesMap,
|
||||
} from "utils/WidgetFactory";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import withMeta, { WithMeta } from "./MetaHOC";
|
||||
|
||||
class DatePickerWidget extends BaseWidget<DatePickerWidget2Props, WidgetState> {
|
||||
static getPropertyPaneConfig() {
|
||||
return [
|
||||
{
|
||||
sectionName: "General",
|
||||
children: [
|
||||
{
|
||||
propertyName: "defaultDate",
|
||||
label: "Default Date",
|
||||
helpText:
|
||||
"Sets the default date of the widget. The date is updated if the default date changes",
|
||||
controlType: "DATE_PICKER",
|
||||
placeholderText: "Enter Default Date",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
helpText: "Sets the format of the selected date",
|
||||
propertyName: "dateFormat",
|
||||
label: "Date Format",
|
||||
controlType: "DROP_DOWN",
|
||||
isJSConvertible: true,
|
||||
options: [
|
||||
{
|
||||
label: "YYYY-MM-DD",
|
||||
value: "YYYY-MM-DD",
|
||||
},
|
||||
{
|
||||
label: "YYYY-MM-DD HH:mm",
|
||||
value: "YYYY-MM-DD HH:mm",
|
||||
},
|
||||
{
|
||||
label: "YYYY-MM-DDTHH:mm:ss.sssZ",
|
||||
value: "YYYY-MM-DDTHH:mm:ss.sssZ",
|
||||
},
|
||||
{
|
||||
label: "DD/MM/YYYY",
|
||||
value: "DD/MM/YYYY",
|
||||
},
|
||||
{
|
||||
label: "DD/MM/YYYY HH:mm",
|
||||
value: "DD/MM/YYYY HH:mm",
|
||||
},
|
||||
],
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
propertyName: "isRequired",
|
||||
label: "Required",
|
||||
helpText: "Makes input to the widget mandatory",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
propertyName: "isVisible",
|
||||
label: "Visible",
|
||||
helpText: "Controls the visibility of the widget",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
propertyName: "isDisabled",
|
||||
label: "Disabled",
|
||||
helpText: "Disables input to this widget",
|
||||
controlType: "SWITCH",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
propertyName: "minDate",
|
||||
label: "Min Date",
|
||||
helpText: "Defines the min date for this widget",
|
||||
controlType: "DATE_PICKER",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
{
|
||||
propertyName: "maxDate",
|
||||
label: "Max Date",
|
||||
helpText: "Defines the max date for this widget",
|
||||
controlType: "DATE_PICKER",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
sectionName: "Actions",
|
||||
children: [
|
||||
{
|
||||
propertyName: "onDateSelected",
|
||||
label: "onDateSelected",
|
||||
controlType: "ACTION_SELECTOR",
|
||||
isJSConvertible: true,
|
||||
isBindProperty: true,
|
||||
isTriggerProperty: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
static getPropertyValidationMap(): WidgetPropertyValidationType {
|
||||
return {
|
||||
...BASE_WIDGET_VALIDATION,
|
||||
defaultDate: VALIDATION_TYPES.DEFAULT_DATE,
|
||||
timezone: VALIDATION_TYPES.TEXT,
|
||||
enableTimePicker: VALIDATION_TYPES.BOOLEAN,
|
||||
dateFormat: VALIDATION_TYPES.TEXT,
|
||||
label: VALIDATION_TYPES.TEXT,
|
||||
datePickerType: VALIDATION_TYPES.TEXT,
|
||||
maxDate: VALIDATION_TYPES.DATE,
|
||||
minDate: VALIDATION_TYPES.DATE,
|
||||
isRequired: VALIDATION_TYPES.BOOLEAN,
|
||||
// onDateSelected: VALIDATION_TYPES.ACTION_SELECTOR,
|
||||
// onDateRangeSelected: VALIDATION_TYPES.ACTION_SELECTOR,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedPropertiesMap(): DerivedPropertiesMap {
|
||||
return {
|
||||
isValid: `{{ this.isRequired ? !!this.selectedDate : true }}`,
|
||||
selectedDate: `{{ this.value ? moment(this.value).toISOString() : (this.defaultDate ? moment(this.defaultDate).toISOString() : "")}}`,
|
||||
formattedDate: `{{ this.value ? moment(this.value).format(this.dateFormat) : (this.defaultDate ? moment(this.defaultDate).format(this.dateFormat) : "")}}`,
|
||||
};
|
||||
}
|
||||
|
||||
static getTriggerPropertyMap(): TriggerPropertiesMap {
|
||||
return {
|
||||
onDateSelected: true,
|
||||
};
|
||||
}
|
||||
|
||||
static getDefaultPropertiesMap(): Record<string, string> {
|
||||
return {
|
||||
value: "defaultDate",
|
||||
};
|
||||
}
|
||||
|
||||
static getMetaPropertiesMap(): Record<string, any> {
|
||||
return {
|
||||
value: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
getPageView() {
|
||||
return (
|
||||
<DatePickerComponent
|
||||
label={`${this.props.label}`}
|
||||
dateFormat={this.props.dateFormat}
|
||||
widgetId={this.props.widgetId}
|
||||
isDisabled={this.props.isDisabled}
|
||||
datePickerType={"DATE_PICKER"}
|
||||
onDateSelected={this.onDateSelected}
|
||||
selectedDate={this.props.value}
|
||||
isLoading={this.props.isLoading}
|
||||
minDate={this.props.minDate}
|
||||
maxDate={this.props.maxDate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onDateSelected = (selectedDate: string) => {
|
||||
this.props.updateWidgetMetaProperty("value", selectedDate, {
|
||||
dynamicString: this.props.onDateSelected,
|
||||
event: {
|
||||
type: EventType.ON_DATE_SELECTED,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
getWidgetType(): WidgetType {
|
||||
return "DATE_PICKER_WIDGET2";
|
||||
}
|
||||
}
|
||||
|
||||
export type DatePickerType = "DATE_PICKER" | "DATE_RANGE_PICKER";
|
||||
|
||||
export interface DatePickerWidget2Props extends WidgetProps, WithMeta {
|
||||
defaultDate: string;
|
||||
selectedDate: string;
|
||||
formattedDate: string;
|
||||
isDisabled: boolean;
|
||||
dateFormat: string;
|
||||
label: string;
|
||||
datePickerType: DatePickerType;
|
||||
onDateSelected?: string;
|
||||
onDateRangeSelected?: string;
|
||||
maxDate: string;
|
||||
minDate: string;
|
||||
isRequired?: boolean;
|
||||
}
|
||||
|
||||
export default DatePickerWidget;
|
||||
export const ProfiledDatePickerWidget2 = Sentry.withProfiler(
|
||||
withMeta(DatePickerWidget),
|
||||
);
|
||||
|
|
@ -950,17 +950,24 @@ class TableWidget extends BaseWidget<TableWidgetProps, WidgetState> {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
this.props.updateWidgetMetaProperty("selectedRowIndex", index);
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"selectedRow",
|
||||
this.props.filteredTableData[index],
|
||||
{
|
||||
dynamicString: this.props.onRowSelected,
|
||||
event: {
|
||||
type: EventType.ON_ROW_SELECTED,
|
||||
const selectedRowIndex = isNumber(this.props.selectedRowIndex)
|
||||
? this.props.selectedRowIndex
|
||||
: -1;
|
||||
if (selectedRowIndex === index) {
|
||||
index = -1;
|
||||
} else {
|
||||
this.props.updateWidgetMetaProperty(
|
||||
"selectedRow",
|
||||
this.props.filteredTableData[index],
|
||||
{
|
||||
dynamicString: this.props.onRowSelected,
|
||||
event: {
|
||||
type: EventType.ON_ROW_SELECTED,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
);
|
||||
}
|
||||
this.props.updateWidgetMetaProperty("selectedRowIndex", index);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -289,6 +289,33 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = {
|
|||
},
|
||||
metaProperties: {},
|
||||
},
|
||||
DATE_PICKER_WIDGET2: {
|
||||
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",
|
||||
|
|
@ -447,6 +474,7 @@ const BASE_WIDGET: DataTreeWidget = {
|
|||
topRow: 0,
|
||||
type: WidgetTypes.SKELETON_WIDGET,
|
||||
parentId: "0",
|
||||
version: 1,
|
||||
bindingPaths: {},
|
||||
triggerPaths: {},
|
||||
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
|
||||
|
|
|
|||
|
|
@ -391,7 +391,10 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
|
|||
dateString: string,
|
||||
props: WidgetProps,
|
||||
): ValidationResponse => {
|
||||
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
|
||||
const dateFormat =
|
||||
props.version === 2
|
||||
? ISO_DATE_FORMAT
|
||||
: props.dateFormat || ISO_DATE_FORMAT;
|
||||
|
||||
if (dateString === undefined) {
|
||||
return {
|
||||
|
|
@ -421,39 +424,22 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
|
|||
dateString: string,
|
||||
props: WidgetProps,
|
||||
): ValidationResponse => {
|
||||
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
|
||||
const dateFormat =
|
||||
props.version === 2
|
||||
? ISO_DATE_FORMAT
|
||||
: props.dateFormat || ISO_DATE_FORMAT;
|
||||
if (dateString === undefined) {
|
||||
return {
|
||||
isValid: false,
|
||||
parsed: "",
|
||||
message:
|
||||
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
|
||||
? props.dateFormat
|
||||
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
|
||||
? 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 isValid = parsedCurrentDate.isValid();
|
||||
if (!isValid) {
|
||||
return {
|
||||
isValid: isValid,
|
||||
|
|
@ -471,14 +457,17 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
|
|||
dateString: string,
|
||||
props: WidgetProps,
|
||||
): ValidationResponse => {
|
||||
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
|
||||
const dateFormat =
|
||||
props.version === 2
|
||||
? ISO_DATE_FORMAT
|
||||
: props.dateFormat || ISO_DATE_FORMAT;
|
||||
if (dateString === undefined) {
|
||||
return {
|
||||
isValid: false,
|
||||
parsed: "",
|
||||
message:
|
||||
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
|
||||
? props.dateFormat
|
||||
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
|
||||
? dateFormat
|
||||
: "",
|
||||
};
|
||||
}
|
||||
|
|
@ -517,14 +506,17 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
|
|||
dateString: string,
|
||||
props: WidgetProps,
|
||||
): ValidationResponse => {
|
||||
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
|
||||
const dateFormat =
|
||||
props.version === 2
|
||||
? ISO_DATE_FORMAT
|
||||
: props.dateFormat || ISO_DATE_FORMAT;
|
||||
if (dateString === undefined) {
|
||||
return {
|
||||
isValid: false,
|
||||
parsed: "",
|
||||
message:
|
||||
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
|
||||
? props.dateFormat
|
||||
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
|
||||
? dateFormat
|
||||
: "",
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import java.io.IOException;
|
|||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -47,9 +49,33 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class DynamoPlugin extends BasePlugin {
|
||||
|
||||
private static final String SCAN_ACTION_VALUE = "Scan";
|
||||
private static final String GET_ITEM_ACTION_VALUE = "GetItem";
|
||||
private static final String BATCH_GET_ITEM_ACTION_VALUE = "BatchGetItem";
|
||||
private static final String TRANSACT_GET_ITEMS_ACTION_VALUE = "TransactGetItems";
|
||||
private static final String PUT_ITEM_ACTION_VALUE = "PutItem";
|
||||
private static final String UPDATE_ITEM_ACTION_VALUE = "UpdateItem";
|
||||
private static final String DELETE_ITEM_ACTION_VALUE = "DeleteItem";
|
||||
private static final String ITEMS_KEY = "Items";
|
||||
private static final String ITEM_KEY = "Item";
|
||||
private static final String ATTRIBUTES_KEY = "Attributes";
|
||||
private static final String RESPONSES_KEY = "Responses";
|
||||
private static final String DYNAMO_TYPE_STRING_LABEL = "S";
|
||||
private static final String DYNAMO_TYPE_NUMBER_LABEL = "N";
|
||||
private static final String DYNAMO_TYPE_BINARY_LABEL = "B";
|
||||
private static final String DYNAMO_TYPE_BOOLEAN_LABEL = "BOOL";
|
||||
private static final String DYNAMO_TYPE_NULL_LABEL = "NUL";
|
||||
private static final String DYNAMO_TYPE_STRING_SET_LABEL = "SS";
|
||||
private static final String DYNAMO_TYPE_NUMBER_SET_LABEL = "NS";
|
||||
private static final String DYNAMO_TYPE_BINARY_SET_LABEL = "BS";
|
||||
private static final String DYNAMO_TYPE_MAP_LABEL = "M";
|
||||
private static final String DYNAMO_TYPE_LIST_LABEL = "L";
|
||||
private static final String RAW_RESPONSE_LABEL = "raw";
|
||||
|
||||
public DynamoPlugin(PluginWrapper wrapper) {
|
||||
super(wrapper);
|
||||
}
|
||||
|
|
@ -71,6 +97,187 @@ public class DynamoPlugin extends BasePlugin {
|
|||
|
||||
private final Scheduler scheduler = Schedulers.elastic();
|
||||
|
||||
public Object extractValue(Object rawItem) {
|
||||
|
||||
if(!(rawItem instanceof List)
|
||||
&& !(rawItem instanceof Map)) {
|
||||
return rawItem;
|
||||
}
|
||||
|
||||
if (rawItem instanceof List) {
|
||||
return ((List<Object>) rawItem)
|
||||
.stream()
|
||||
.map(item -> extractValue(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
else { /* map type */
|
||||
Map<String, Object> extractedValueMap = new HashMap<>();
|
||||
Map<String, Object> rawItemAsMap = (Map<String, Object>) rawItem;
|
||||
for (Map.Entry<String, Object> entry: rawItemAsMap.entrySet()) {
|
||||
switch (entry.getKey()) {
|
||||
case DYNAMO_TYPE_NUMBER_LABEL:
|
||||
case DYNAMO_TYPE_STRING_LABEL:
|
||||
case DYNAMO_TYPE_BINARY_LABEL:
|
||||
case DYNAMO_TYPE_BOOLEAN_LABEL:
|
||||
case DYNAMO_TYPE_NULL_LABEL:
|
||||
if (entry.getValue() != null) {
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
break;
|
||||
case DYNAMO_TYPE_STRING_SET_LABEL:
|
||||
case DYNAMO_TYPE_NUMBER_SET_LABEL:
|
||||
case DYNAMO_TYPE_BINARY_SET_LABEL:
|
||||
if (entry.getValue() != null && ((List<Object>)entry.getValue()).size() > 0) {
|
||||
return entry.getValue();
|
||||
}
|
||||
|
||||
break;
|
||||
case DYNAMO_TYPE_LIST_LABEL:
|
||||
/*
|
||||
* - If size of rawValueAsList is 0, then we don't want to return.
|
||||
*/
|
||||
List<Object> rawValueAsList = (List<Object>) entry.getValue();
|
||||
if (rawValueAsList.size() > 0) {
|
||||
return rawValueAsList
|
||||
.stream()
|
||||
.map(listItem -> extractValue(listItem))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
break;
|
||||
case DYNAMO_TYPE_MAP_LABEL:
|
||||
/*
|
||||
* - If size of rawValueAsMap is 0, then we don't want to return.
|
||||
*/
|
||||
Map<String, Object> rawValueAsMap = (Map<String, Object>) entry.getValue();
|
||||
if (rawValueAsMap.size() > 0) {
|
||||
return rawValueAsMap
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(item -> Map.entry(item.getKey(), extractValue(item.getValue())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
extractedValueMap.put(entry.getKey(), extractValue(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return extractedValueMap;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* - Transform response for easy consumption. For details please visit
|
||||
* https://github.com/appsmithorg/appsmith/issues/3010
|
||||
*/
|
||||
public Object getTransformedResponse(Map<String, Object> rawResponse,
|
||||
String action) throws AppsmithPluginException {
|
||||
|
||||
Map<String, Object> transformedResponse = new HashMap<>();
|
||||
|
||||
/*
|
||||
* - Any action other than the following do not return transformed response:
|
||||
* - Scan
|
||||
* - GetItem
|
||||
* - PutItem
|
||||
* - UpdateItem
|
||||
* - DeleteItem
|
||||
* - BatchGetItem
|
||||
* - TransactGetItem
|
||||
*/
|
||||
if (!SCAN_ACTION_VALUE.equals(action)
|
||||
&& !GET_ITEM_ACTION_VALUE.equals(action)
|
||||
&& !PUT_ITEM_ACTION_VALUE.equals(action)
|
||||
&& !UPDATE_ITEM_ACTION_VALUE.equals(action)
|
||||
&& !DELETE_ITEM_ACTION_VALUE.equals(action)
|
||||
&& !BATCH_GET_ITEM_ACTION_VALUE.equals(action)
|
||||
&& !TRANSACT_GET_ITEMS_ACTION_VALUE.equals(action)) {
|
||||
return rawResponse;
|
||||
}
|
||||
|
||||
/*
|
||||
* - Transformed response has section "raw", under which raw response appears.
|
||||
*/
|
||||
transformedResponse.put(RAW_RESPONSE_LABEL, rawResponse);
|
||||
|
||||
/*
|
||||
* - Transform response based on action.
|
||||
*/
|
||||
String topLevelKey;
|
||||
switch (action) {
|
||||
case SCAN_ACTION_VALUE:
|
||||
topLevelKey = ITEMS_KEY;
|
||||
break;
|
||||
case GET_ITEM_ACTION_VALUE:
|
||||
topLevelKey = ITEM_KEY;
|
||||
break;
|
||||
case PUT_ITEM_ACTION_VALUE:
|
||||
case UPDATE_ITEM_ACTION_VALUE:
|
||||
case DELETE_ITEM_ACTION_VALUE:
|
||||
topLevelKey = ATTRIBUTES_KEY;
|
||||
break;
|
||||
case BATCH_GET_ITEM_ACTION_VALUE:
|
||||
case TRANSACT_GET_ITEMS_ACTION_VALUE:
|
||||
topLevelKey = RESPONSES_KEY;
|
||||
break;
|
||||
default:
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
"Appsmith has encountered an unexpected error when transforming raw DynamoDb response" +
|
||||
". Please reach out to Appsmith customer support to resolve this."
|
||||
);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> responseEntry: rawResponse.entrySet()) {
|
||||
if (!responseEntry.getKey().equals(topLevelKey)) {
|
||||
transformedResponse.put(responseEntry.getKey(), responseEntry.getValue());
|
||||
}
|
||||
else {
|
||||
if (rawResponse.get(topLevelKey) instanceof Collection) {
|
||||
/*
|
||||
* - Need to have an empty list if rawItems is null.
|
||||
*/
|
||||
List<Object> extractedResponse = new ArrayList<>();
|
||||
Collection<Object> rawItems = (Collection<Object>) (rawResponse.get(topLevelKey));
|
||||
if (rawItems != null) {
|
||||
/*
|
||||
* - Insert transformed values into extractedResponse list.
|
||||
*/
|
||||
extractedResponse = rawItems
|
||||
.stream()
|
||||
.map(item -> extractValue(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
transformedResponse.put(topLevelKey, extractedResponse);
|
||||
}
|
||||
else if (rawResponse.get(topLevelKey) instanceof Map) {
|
||||
/*
|
||||
* - Need to have an empty map if rawItems is null.
|
||||
*/
|
||||
Map<Object, Object> extractedResponse = new HashMap<>();
|
||||
HashMap<String, Object> rawItems = (HashMap<String, Object>) rawResponse.get(topLevelKey);
|
||||
if (rawItems != null) {
|
||||
/*
|
||||
* - Insert transformed values into extractedResponse map.
|
||||
*/
|
||||
extractedResponse = rawItems
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(item -> Map.entry(item.getKey(), extractValue(item.getValue())))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
transformedResponse.put(topLevelKey, extractedResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transformedResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ActionExecutionResult> execute(DynamoDbClient ddb,
|
||||
DatasourceConfiguration datasourceConfiguration,
|
||||
|
|
@ -115,8 +322,11 @@ public class DynamoPlugin extends BasePlugin {
|
|||
toLowerCamelCase(action),
|
||||
requestClass
|
||||
);
|
||||
final DynamoDbResponse response = (DynamoDbResponse) actionExecuteMethod.invoke(ddb, plainToSdk(parameters, requestClass));
|
||||
result.setBody(sdkToPlain(response));
|
||||
final Object sdkValue = plainToSdk(parameters, requestClass);
|
||||
final DynamoDbResponse response = (DynamoDbResponse) actionExecuteMethod.invoke(ddb, sdkValue);
|
||||
Object rawResponse = sdkToPlain(response);
|
||||
Object transformedResponse = getTransformedResponse((Map<String, Object>)rawResponse, action);
|
||||
result.setBody(transformedResponse);
|
||||
} catch (AppsmithPluginException | InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) {
|
||||
final String message = "Error executing the DynamoDB Action: " + (e.getCause() == null ? e : e.getCause()).getMessage();
|
||||
log.warn(message, e);
|
||||
|
|
@ -290,34 +500,43 @@ public class DynamoPlugin extends BasePlugin {
|
|||
// For maps, we go recursive, applying this transformation to each value, and replacing with the
|
||||
// result in the map. Generic types in the setter method's signature are used to convert the values.
|
||||
final Method setterMethod = findMethod(builderType, m -> m.getName().equals(setterName));
|
||||
final ParameterizedType valueType = (ParameterizedType) setterMethod.getGenericParameterTypes()[0];
|
||||
final Map<String, Object> transformedMap = new HashMap<>();
|
||||
for (final Map.Entry<String, Object> innerEntry : ((Map<String, Object>) value).entrySet()) {
|
||||
Object innerValue = innerEntry.getValue();
|
||||
if (innerValue instanceof Map) {
|
||||
innerValue = plainToSdk((Map) innerValue, (Class<?>) valueType.getActualTypeArguments()[1]);
|
||||
final Type parameterType = setterMethod.getGenericParameterTypes()[0];
|
||||
if (parameterType instanceof ParameterizedType) {
|
||||
final ParameterizedType valueType = (ParameterizedType) parameterType;
|
||||
final Map<String, Object> transformedMap = new HashMap<>();
|
||||
for (final Map.Entry<String, Object> innerEntry : ((Map<String, Object>) value).entrySet()) {
|
||||
Object innerValue = innerEntry.getValue();
|
||||
if (innerValue instanceof Map) {
|
||||
innerValue = plainToSdk((Map) innerValue, (Class<?>) valueType.getActualTypeArguments()[1]);
|
||||
}
|
||||
transformedMap.put(innerEntry.getKey(), innerValue);
|
||||
}
|
||||
transformedMap.put(innerEntry.getKey(), innerValue);
|
||||
value = transformedMap;
|
||||
if (!Map.class.isAssignableFrom((Class<?>) valueType.getRawType())) {
|
||||
// Some setters don't take a plain map. For example, some require an `AttributeValue` instance
|
||||
// for objects that are just maps in JSON. So, we make that conversion here.
|
||||
value = plainToSdk((Map) value, (Class<T>) valueType.getRawType());
|
||||
}
|
||||
setterMethod.invoke(builder, value);
|
||||
} else if (parameterType instanceof Class) {
|
||||
setterMethod.invoke(builder, plainToSdk((Map) value, (Class) parameterType));
|
||||
}
|
||||
value = transformedMap;
|
||||
if (!Map.class.isAssignableFrom((Class<?>) valueType.getRawType())) {
|
||||
// Some setters don't take a plain map. For example, some require an `AttributeValue` instance
|
||||
// for objects that are just maps in JSON. So, we make that conversion here.
|
||||
value = plainToSdk((Map) value, (Class<T>) valueType.getRawType());
|
||||
}
|
||||
setterMethod.invoke(builder, value);
|
||||
|
||||
} else if (value instanceof Collection) {
|
||||
// For linear collections, the process is similar to that of maps.
|
||||
final Collection<Object> valueAsCollection = (Collection) value;
|
||||
// Find method by name and exclude the varargs version of the method.
|
||||
final Method setterMethod = findMethod(builderType, m -> m.getName().equals(setterName) && !m.getParameterTypes()[0].getName().startsWith("[L"));
|
||||
final ParameterizedType valueType = (ParameterizedType) setterMethod.getGenericParameterTypes()[0];
|
||||
Type valueType = ((ParameterizedType) setterMethod.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
|
||||
if (valueType instanceof WildcardType) {
|
||||
// This occurs when the method's parameter is typed as `Collection<? extends Map<...>>`. Example op: `BatchGetItem`.
|
||||
valueType = ((WildcardType) valueType).getUpperBounds()[0];
|
||||
}
|
||||
final Collection<Object> reTypedList = new ArrayList<>();
|
||||
for (final Object innerValue : valueAsCollection) {
|
||||
if (innerValue instanceof Map) {
|
||||
reTypedList.add(plainToSdk((Map) innerValue, (Class<?>) valueType.getActualTypeArguments()[0]));
|
||||
} else if (innerValue instanceof String && SdkBytes.class.isAssignableFrom((Class<?>) valueType.getActualTypeArguments()[0])) {
|
||||
reTypedList.add(plainToSdk((Map) innerValue, valueType));
|
||||
} else if (innerValue instanceof String && SdkBytes.class.isAssignableFrom((Class<?>) valueType)) {
|
||||
reTypedList.add(SdkBytes.fromUtf8String((String) innerValue));
|
||||
} else {
|
||||
reTypedList.add(innerValue);
|
||||
|
|
@ -338,6 +557,34 @@ public class DynamoPlugin extends BasePlugin {
|
|||
return (T) builderType.getMethod("build").invoke(builder);
|
||||
}
|
||||
|
||||
public static Object plainToSdk(Map<String, Object> mapping, Type type)
|
||||
throws InvocationTargetException, NoSuchMethodException, ClassNotFoundException, AppsmithPluginException,
|
||||
IllegalAccessException {
|
||||
|
||||
if (mapping == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(type instanceof ParameterizedType)) {
|
||||
return plainToSdk(mapping, (Class) type);
|
||||
}
|
||||
|
||||
final ParameterizedType ptype = (ParameterizedType) type;
|
||||
|
||||
if (Map.class.equals(ptype.getRawType())) {
|
||||
final Map<String, Object> convertedMap = new HashMap<>();
|
||||
for (final Map.Entry<String, Object> entry : mapping.entrySet()) {
|
||||
convertedMap.put(entry.getKey(), plainToSdk((Map) entry.getValue(), (Class<?>) ptype.getActualTypeArguments()[1]));
|
||||
}
|
||||
return convertedMap;
|
||||
}
|
||||
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_ERROR,
|
||||
"Unknown type to convert to SDK style " + type.getTypeName()
|
||||
);
|
||||
}
|
||||
|
||||
private static Method findMethod(Class<?> builderType, Predicate<Method> predicate) {
|
||||
return Arrays.stream(builderType.getMethods())
|
||||
.filter(predicate)
|
||||
|
|
@ -363,7 +610,7 @@ public class DynamoPlugin extends BasePlugin {
|
|||
|
||||
private static Object sdkToPlain(Object valueObj) {
|
||||
if (valueObj instanceof SdkPojo) {
|
||||
final SdkPojo response = (SdkPojo) valueObj;
|
||||
SdkPojo response = (SdkPojo) valueObj;
|
||||
final Map<String, Object> plain = new HashMap<>();
|
||||
|
||||
for (final SdkField<?> field : response.sdkFields()) {
|
||||
|
|
@ -373,6 +620,10 @@ public class DynamoPlugin extends BasePlugin {
|
|||
|
||||
return plain;
|
||||
|
||||
} else if (valueObj instanceof SdkBytes) {
|
||||
SdkBytes response = (SdkBytes) valueObj;
|
||||
|
||||
return new String(response.asByteArray());
|
||||
} else if (valueObj instanceof Map) {
|
||||
final Map<?, ?> valueAsMap = (Map<?, ?>) valueObj;
|
||||
final Map<Object, Object> plainMap = new HashMap<>();
|
||||
|
|
@ -392,7 +643,6 @@ public class DynamoPlugin extends BasePlugin {
|
|||
}
|
||||
|
||||
return plainList;
|
||||
|
||||
}
|
||||
|
||||
return valueObj;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.test.StepVerifier;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.core.SdkBytes;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||
|
|
@ -27,10 +28,13 @@ import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
|
|||
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
|
@ -93,6 +97,42 @@ public class DynamoPluginTest {
|
|||
))
|
||||
.build());
|
||||
|
||||
ddb.createTable(CreateTableRequest.builder()
|
||||
.tableName("allTypes")
|
||||
.attributeDefinitions(
|
||||
AttributeDefinition.builder().attributeName("Id").attributeType(ScalarAttributeType.N).build()
|
||||
)
|
||||
.keySchema(
|
||||
KeySchemaElement.builder().attributeName("Id").keyType(KeyType.HASH).build()
|
||||
)
|
||||
.provisionedThroughput(
|
||||
ProvisionedThroughput.builder().readCapacityUnits(5L).writeCapacityUnits(5L).build()
|
||||
)
|
||||
.build());
|
||||
|
||||
String testPayload1 = "payload1";
|
||||
SdkBytes bytesValue1 = SdkBytes.fromByteArray(testPayload1.getBytes());
|
||||
String testPayload2 = "payload2";
|
||||
SdkBytes bytesValue2 = SdkBytes.fromByteArray(testPayload2.getBytes());
|
||||
AttributeValue mapValue = AttributeValue.builder().s("mapValue").build();
|
||||
AttributeValue listValue1 = AttributeValue.builder().s("listValue1").build();
|
||||
AttributeValue listValue2 = AttributeValue.builder().s("listValue2").build();
|
||||
ddb.putItem(PutItemRequest.builder()
|
||||
.tableName("allTypes")
|
||||
.item(Map.of(
|
||||
"Id", AttributeValue.builder().n("1").build(),
|
||||
"StringType", AttributeValue.builder().s("str").build(),
|
||||
"BooleanType", AttributeValue.builder().bool(true).build(),
|
||||
"BinaryType", AttributeValue.builder().b(bytesValue1).build(),
|
||||
"NullType", AttributeValue.builder().nul(true).build(),
|
||||
"StringSetType", AttributeValue.builder().ss("str1", "str2").build(),
|
||||
"NumberSetType", AttributeValue.builder().ns("1", "2").build(),
|
||||
"BinarySetType", AttributeValue.builder().bs(bytesValue1, bytesValue2).build(),
|
||||
"MapType", AttributeValue.builder().m(Map.of("mapKey", mapValue)).build(),
|
||||
"ListType", AttributeValue.builder().l(listValue1, listValue2).build()
|
||||
))
|
||||
.build());
|
||||
|
||||
Endpoint endpoint = new Endpoint();
|
||||
endpoint.setHost(host);
|
||||
endpoint.setPort(port.longValue());
|
||||
|
|
@ -121,10 +161,17 @@ public class DynamoPluginTest {
|
|||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
assertArrayEquals(
|
||||
new String[]{"cities"},
|
||||
((Map<String, List<String>>) result.getBody()).get("TableNames").toArray()
|
||||
);
|
||||
|
||||
HashSet<String> expectedTables = new HashSet<>();
|
||||
expectedTables.add("cities");
|
||||
expectedTables.add("allTypes");
|
||||
|
||||
HashSet<String> actualTables = new HashSet<>();
|
||||
actualTables.add(((Map<String, ArrayList<String>>) result.getBody()).get("TableNames").get(0));
|
||||
actualTables.add(((Map<String, ArrayList<String>>) result.getBody()).get("TableNames").get(1));
|
||||
|
||||
assertTrue(expectedTables.equals(actualTables));
|
||||
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -162,8 +209,13 @@ public class DynamoPluginTest {
|
|||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
final Map<String, Map<String, Object>> item = ((Map<String, Map<String, Map<String, Object>>>) result.getBody()).get("Item");
|
||||
assertEquals("New Delhi", item.get("City").get("S"));
|
||||
Map<String, Object> resultBody = (Map<String, Object>) result.getBody();
|
||||
Map<String, Object> rawResponse = (Map<String, Object>) resultBody.get("raw");
|
||||
Map<String, Map<String, String>> rawItem = (Map<String, Map<String, String>>) rawResponse.get("Item");
|
||||
assertEquals("New Delhi", rawItem.get("City").get("S"));
|
||||
|
||||
Map<String, String> transformedItem = (Map<String, String>) resultBody.get("Item");
|
||||
assertEquals("New Delhi", transformedItem.get("City"));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -215,8 +267,14 @@ public class DynamoPluginTest {
|
|||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
final Map<String, Map<String, Object>> attributes = ((Map<String, Map<String, Map<String, Object>>>) result.getBody()).get("Attributes");
|
||||
assertEquals("Bengaluru", attributes.get("City").get("S"));
|
||||
Map<String, Object> resultBody = (Map<String, Object>) result.getBody();
|
||||
Map<String, Object> rawResponse = (Map<String, Object>) resultBody.get("raw");
|
||||
Map<String, Map<String, Object>> rawItem = (Map<String, Map<String, Object>>) rawResponse.get(
|
||||
"Attributes");
|
||||
assertEquals("Bengaluru", rawItem.get("City").get("S"));
|
||||
|
||||
Map<String, String> transformedItem = (Map<String, String>) resultBody.get("Attributes");
|
||||
assertEquals("Bengaluru", transformedItem.get("City"));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -232,8 +290,111 @@ public class DynamoPluginTest {
|
|||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
final List<Object> items = (List<Object>) ((Map<String, Object>) result.getBody()).get("Items");
|
||||
|
||||
List<Map<String, Object>> items = (List<Map<String, Object>>)
|
||||
((Map<String, Object>)((Map<String, Object>) result.getBody()).get("raw")).get("Items");
|
||||
assertEquals(2, items.size());
|
||||
|
||||
for (int i=0; i<items.size(); i++) {
|
||||
Map<String, Object> item = items.get(i);
|
||||
for (Map.Entry<String, Object> entry: item.entrySet()) {
|
||||
Object rawValue = ((Map<String, Object>) entry.getValue()).get("S");
|
||||
Object transformedValue =
|
||||
((List<Map<String, Object>>)((Map<String, Object>) result.getBody()).get("Items"))
|
||||
.get(i).get(entry.getKey());
|
||||
assertTrue(rawValue.equals(transformedValue));
|
||||
}
|
||||
}
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBatchGetItem() {
|
||||
final String body = "{\n" +
|
||||
" \"RequestItems\": {\n" +
|
||||
" \"cities\": {\n" +
|
||||
" \"Keys\": [\n" +
|
||||
" {\n" +
|
||||
" \"Id\": {\n" +
|
||||
" \"S\": \"1\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"Id\": {\n" +
|
||||
" \"S\": \"2\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"ProjectionExpression\":\"City\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"ReturnConsumedCapacity\": \"TOTAL\"\n" +
|
||||
"}";
|
||||
|
||||
StepVerifier.create(execute("BatchGetItem", body))
|
||||
.assertNext(result -> {
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
final Map<String, ?> response = (Map) result.getBody();
|
||||
assertEquals(
|
||||
Collections.emptyMap(),
|
||||
response.remove("UnprocessedKeys")
|
||||
);
|
||||
|
||||
// Test raw response
|
||||
Map<String, Object> rawResponse =
|
||||
(Map<String, Object>) ((Map<String, Object>) response.get("raw")).get(
|
||||
"Responses");
|
||||
ArrayList<Map<String, Object>> rawCitiesList = (ArrayList<Map<String, Object>>) rawResponse.get("cities");
|
||||
assertEquals("New Delhi", ((Map<String, Object>)(rawCitiesList.get(0)).get("City")).get("S"));
|
||||
|
||||
// Test transformed response
|
||||
Map<String, Object> transformedResponse = (Map<String, Object>) response.get("Responses");
|
||||
ArrayList<Map<String, Object>> transformedCitiesList = (ArrayList<Map<String, Object>>) transformedResponse.get("cities");
|
||||
assertEquals("New Delhi", transformedCitiesList.get(0).get("City"));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransactGetItems() {
|
||||
final String body =
|
||||
"{\n" +
|
||||
" \"ReturnConsumedCapacity\": \"NONE\",\n" +
|
||||
" \"TransactItems\": [\n" +
|
||||
" {\n" +
|
||||
" \"Get\": {\n" +
|
||||
" \"Key\": {\n" +
|
||||
" \"Id\": {\n" +
|
||||
" \"S\": \"1\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"TableName\": \"cities\"\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}";
|
||||
|
||||
StepVerifier.create(execute("TransactGetItems", body))
|
||||
.assertNext(result -> {
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
|
||||
final Map<String, ?> response = (Map) result.getBody();
|
||||
|
||||
// Test raw response
|
||||
ArrayList<Map<String, Object>> rawResponse =
|
||||
(ArrayList<Map<String, Object>>) ((Map<String, Object>) response.get("raw")).get(
|
||||
"Responses");
|
||||
assertEquals("New Delhi",
|
||||
((Map<String, Map<String, Object>>)rawResponse.get(0).get("Item")).get("City").get("S"));
|
||||
|
||||
// Test transformed response
|
||||
ArrayList<Map<String, Object>> transformedResponse = (ArrayList<Map<String, Object>>) response.get("Responses");
|
||||
assertEquals("New Delhi",
|
||||
((Map<String, Object>)transformedResponse.get(0).get("Item")).get("City"));
|
||||
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -248,14 +409,85 @@ public class DynamoPluginTest {
|
|||
.assertNext(structure -> {
|
||||
assertNotNull(structure);
|
||||
assertNotNull(structure.getTables());
|
||||
assertEquals(
|
||||
List.of("cities"),
|
||||
structure.getTables().stream()
|
||||
.map(DatasourceStructure.Table::getName)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
HashSet<String> expectedTables = new HashSet<>();
|
||||
expectedTables.add("cities");
|
||||
expectedTables.add("allTypes");
|
||||
|
||||
HashSet<String> actualTables = new HashSet<>();
|
||||
actualTables.add(structure.getTables().get(0).getName());
|
||||
actualTables.add(structure.getTables().get(1).getName());
|
||||
|
||||
assertTrue(expectedTables.equals(actualTables));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
/*
|
||||
* - "allTypes" table contains data of all type supported by DynamoDb.
|
||||
* - This test aims to test the data type handling capability of the plugin.
|
||||
*/
|
||||
@Test
|
||||
public void testParsingCapabilityForAllTypes() {
|
||||
final String body = "{\n" +
|
||||
" \"TableName\": \"allTypes\"\n" +
|
||||
"}\n";
|
||||
|
||||
StepVerifier.create(execute("Scan", body))
|
||||
.assertNext(result -> {
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getIsExecutionSuccess());
|
||||
assertNotNull(result.getBody());
|
||||
|
||||
Map<String, Object> resultBody = (Map<String, Object>) result.getBody();
|
||||
Map<String, Object> rawResponse = (Map<String, Object>) resultBody.get("raw");
|
||||
|
||||
/*
|
||||
* - Check if data under the "raw" section i.e. non-transformed data is correct.
|
||||
*/
|
||||
ArrayList<Map<String, Object>> rawItems = (ArrayList<Map<String, Object>>) rawResponse.get(
|
||||
"Items");
|
||||
Map<String, Object> rawItemMap = rawItems.get(0);
|
||||
assertEquals("1", ((Map<String, Object>)rawItemMap.get("Id")).get("N"));
|
||||
assertEquals("str", ((Map<String, Object>)rawItemMap.get("StringType")).get("S"));
|
||||
assertEquals("true", ((Map<String, Object>)rawItemMap.get("BooleanType")).get("BOOL").toString());
|
||||
assertEquals("payload1", ((Map<String, Object>)rawItemMap.get("BinaryType")).get("B"));
|
||||
assertEquals("true", ((Map<String, Object>)rawItemMap.get("NullType")).get("NUL").toString());
|
||||
assertArrayEquals(new String[]{"str1", "str2"},
|
||||
((ArrayList<String>)((Map<String, Object>)rawItemMap.get("StringSetType")).get("SS")).toArray());
|
||||
assertArrayEquals(new String[]{"payload1", "payload2"},
|
||||
((ArrayList<String>)((Map<String, Object>)rawItemMap.get("BinarySetType")).get("BS")).toArray());
|
||||
assertArrayEquals(new String[]{"1", "2"},
|
||||
((ArrayList<String>)((Map<String, Object>)rawItemMap.get("NumberSetType")).get("NS")).toArray());
|
||||
assertEquals("mapValue",
|
||||
((Map<String, Map<String, Map<String, Object>>>)rawItemMap.get("MapType")).get("M")
|
||||
.get("mapKey").get("S").toString());
|
||||
assertEquals("listValue1",
|
||||
((HashMap<String, ArrayList<HashMap<String, Object>>>)rawItemMap.get("ListType")).get("L").get(0).get("S"));
|
||||
assertEquals("listValue2",
|
||||
((HashMap<String, ArrayList<HashMap<String, Object>>>)rawItemMap.get("ListType")).get("L").get(1).get("S"));
|
||||
|
||||
/*
|
||||
* - Check if the transformed data is correct.
|
||||
*/
|
||||
ArrayList<Map<String, Object>> transformedItems = (ArrayList<Map<String, Object>>) resultBody.get("Items");
|
||||
Map<String, Object> transformedItemMap = transformedItems.get(0);
|
||||
assertEquals("1", transformedItemMap.get("Id"));
|
||||
assertEquals("str", transformedItemMap.get("StringType"));
|
||||
assertEquals("true", transformedItemMap.get("BooleanType").toString());
|
||||
assertEquals("payload1", transformedItemMap.get("BinaryType"));
|
||||
assertEquals("true", transformedItemMap.get("NullType").toString());
|
||||
assertArrayEquals(new String[]{"str1", "str2"},
|
||||
((ArrayList<String>)transformedItemMap.get("StringSetType")).toArray());
|
||||
assertArrayEquals(new String[]{"payload1", "payload2"},
|
||||
((ArrayList<String>)transformedItemMap.get("BinarySetType")).toArray());
|
||||
assertArrayEquals(new String[]{"1", "2"},
|
||||
((ArrayList<String>)transformedItemMap.get("NumberSetType")).toArray());
|
||||
assertEquals("mapValue",
|
||||
((Map<String, Object>)transformedItemMap.get("MapType")).get("mapKey").toString());
|
||||
assertEquals("listValue1", ((ArrayList<String>)transformedItemMap.get("ListType")).get(0));
|
||||
assertEquals("listValue2", ((ArrayList<String>)transformedItemMap.get("ListType")).get(1));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -242,11 +242,17 @@ public class MongoPlugin extends BasePlugin {
|
|||
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication != null) {
|
||||
builder
|
||||
.append(urlEncode(authentication.getUsername()))
|
||||
.append(':')
|
||||
.append(urlEncode(authentication.getPassword()))
|
||||
.append('@');
|
||||
final boolean hasUsername = StringUtils.hasText(authentication.getUsername());
|
||||
final boolean hasPassword = StringUtils.hasText(authentication.getPassword());
|
||||
if (hasUsername) {
|
||||
builder.append(urlEncode(authentication.getUsername()));
|
||||
}
|
||||
if (hasPassword) {
|
||||
builder.append(':').append(urlEncode(authentication.getPassword()));
|
||||
}
|
||||
if (hasUsername || hasPassword) {
|
||||
builder.append('@');
|
||||
}
|
||||
}
|
||||
|
||||
for (Endpoint endpoint : endpoints) {
|
||||
|
|
@ -313,25 +319,11 @@ public class MongoPlugin extends BasePlugin {
|
|||
}
|
||||
|
||||
DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication();
|
||||
if (authentication == null) {
|
||||
invalids.add("Missing authentication details.");
|
||||
|
||||
} else {
|
||||
if (authentication != null) {
|
||||
DBAuth.Type authType = authentication.getAuthType();
|
||||
|
||||
if (authType != null && VALID_AUTH_TYPES.contains(authType)) {
|
||||
|
||||
if (StringUtils.isEmpty(authentication.getUsername())) {
|
||||
invalids.add("Missing username for authentication. Needed because authType is " + authType + ".");
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(authentication.getPassword())) {
|
||||
invalids.add("Missing password for authentication. Needed because authType is " + authType + ".");
|
||||
}
|
||||
|
||||
} else {
|
||||
if (authType == null || !VALID_AUTH_TYPES.contains(authType)) {
|
||||
invalids.add("Invalid authType. Must be one of " + VALID_AUTH_TYPES_STR);
|
||||
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(authentication.getDatabaseName())) {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,8 @@
|
|||
"configProperty": "datasourceConfiguration.endpoints[*].host",
|
||||
"controlType": "KEYVALUE_ARRAY",
|
||||
"validationMessage": "Please enter a valid host",
|
||||
"validationRegex": "^((?![/:]).)*$"
|
||||
"validationRegex": "^((?![/:]).)*$",
|
||||
"placeholder": "myapp.abcde.mongodb.net"
|
||||
},
|
||||
{
|
||||
"label": "Port",
|
||||
|
|
|
|||
|
|
@ -150,10 +150,13 @@ public class PostgresPlugin extends BasePlugin {
|
|||
Boolean isPreparedStatement;
|
||||
|
||||
final List<Property> properties = actionConfiguration.getPluginSpecifiedTemplates();
|
||||
if (properties.get(PREPARED_STATEMENT_INDEX) == null) {
|
||||
// If the configuration does not exist, default to true
|
||||
// Note this is not possible today since the query editor sets a default value for this field.
|
||||
isPreparedStatement = true;
|
||||
if (properties == null || properties.get(PREPARED_STATEMENT_INDEX) == null) {
|
||||
/**
|
||||
* TODO :
|
||||
* In case the prepared statement configuration is missing, default to true once PreparedStatement
|
||||
* is no longer in beta.
|
||||
*/
|
||||
isPreparedStatement = false;
|
||||
} else {
|
||||
isPreparedStatement = Boolean.parseBoolean(properties.get(PREPARED_STATEMENT_INDEX).getValue());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,6 @@
|
|||
"sectionName": "",
|
||||
"id": 1,
|
||||
"children": [
|
||||
{
|
||||
"label": "Use Prepared Statement",
|
||||
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
"controlType": "SWITCH",
|
||||
"isRequired": true,
|
||||
"initialValue": true
|
||||
},
|
||||
{
|
||||
"label": "",
|
||||
"configProperty": "actionConfiguration.body",
|
||||
|
|
@ -19,4 +12,4 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"setting": [
|
||||
{
|
||||
"sectionName": "",
|
||||
"id": 1,
|
||||
"children": [
|
||||
{
|
||||
"label": "Run query on page load",
|
||||
"configProperty": "executeOnLoad",
|
||||
"controlType": "SWITCH",
|
||||
"info": "Will refresh data each time the page is loaded"
|
||||
},
|
||||
{
|
||||
"label": "Request confirmation before running query",
|
||||
"configProperty": "confirmBeforeExecute",
|
||||
"controlType": "SWITCH",
|
||||
"info": "Ask confirmation from the user each time before refreshing data"
|
||||
},
|
||||
{
|
||||
"label": "[Beta]Use Prepared Statement",
|
||||
"info": "Turning on Prepared Statement makes the query parametrized. This in turn makes it resilient against SQL injections",
|
||||
"configProperty": "actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
"controlType": "SWITCH",
|
||||
"initialValue": false
|
||||
},
|
||||
{
|
||||
"label": "Query timeout (in milliseconds)",
|
||||
"info": "Maximum time after which the query will return",
|
||||
"configProperty": "actionConfiguration.timeoutInMillisecond",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"dataType": "NUMBER"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -74,6 +74,7 @@ public class Application extends BaseDomain {
|
|||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class AppLayout implements Serializable {
|
||||
Type type;
|
||||
|
|
|
|||
|
|
@ -313,12 +313,21 @@ public class PluginServiceImpl extends BaseService<PluginRepository, Plugin, Str
|
|||
)
|
||||
.onErrorReturn(new HashMap())
|
||||
.cache();
|
||||
final Mono<Map> settingMono = loadPluginResource(pluginId, "setting.json")
|
||||
.doOnError(throwable ->
|
||||
// Remove this pluginId from the cache so it is tried again next time.
|
||||
formCache.remove(pluginId)
|
||||
)
|
||||
.onErrorReturn(new HashMap())
|
||||
.cache();
|
||||
|
||||
Mono<Map> resourceMono = Mono.zip(formMono, editorMono)
|
||||
Mono<Map> resourceMono = Mono.zip(formMono, editorMono, settingMono)
|
||||
.map(tuple -> {
|
||||
Map formMap = tuple.getT1();
|
||||
Map editorMap = tuple.getT2();
|
||||
Map settingMap = tuple.getT3();
|
||||
formMap.putAll(editorMap);
|
||||
formMap.putAll(settingMap);
|
||||
return formMap;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public class AuthenticationService {
|
|||
// The state is used internally to calculate the redirect url when returning control to the client
|
||||
.queryParam(STATE, String.join(",", pageId, datasourceId, redirectUri));
|
||||
// Adding optional scope parameter
|
||||
if (!oAuth2.getScope().isEmpty()) {
|
||||
if (oAuth2.getScope() != null && !oAuth2.getScope().isEmpty()) {
|
||||
uriComponentsBuilder
|
||||
.queryParam(SCOPE, String.join(",", oAuth2.getScope()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ public class PluginServiceTest {
|
|||
.thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL)));
|
||||
Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("editor.json")))
|
||||
.thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL)));
|
||||
Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("setting.json")))
|
||||
.thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL)));
|
||||
|
||||
Mono<Map> formConfig = pluginService.getFormConfig("random-plugin-id");
|
||||
|
||||
|
|
@ -86,6 +88,8 @@ public class PluginServiceTest {
|
|||
.thenReturn(Mono.just(formMap));
|
||||
Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("editor.json")))
|
||||
.thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL)));
|
||||
Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("setting.json")))
|
||||
.thenReturn(Mono.error(new AppsmithException(AppsmithError.PLUGIN_LOAD_FORM_JSON_FAIL)));
|
||||
|
||||
Mono<Map> formConfig = pluginService.getFormConfig("random-plugin-id");
|
||||
StepVerifier.create(formConfig)
|
||||
|
|
@ -93,6 +97,7 @@ public class PluginServiceTest {
|
|||
assertThat(form).isNotNull();
|
||||
assertThat(form.get("form")).isNotNull();
|
||||
assertThat(form.get("editor")).isNull();
|
||||
assertThat(form.get("setting")).isNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
@ -105,10 +110,15 @@ public class PluginServiceTest {
|
|||
Map editorMap = new HashMap();
|
||||
editorMap.put("editor", new Object());
|
||||
|
||||
Map settingMap = new HashMap();
|
||||
settingMap.put("setting", new Object());
|
||||
|
||||
Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("form.json")))
|
||||
.thenReturn(Mono.just(formMap));
|
||||
Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("editor.json")))
|
||||
.thenReturn(Mono.just(editorMap));
|
||||
Mockito.when(pluginService.loadPluginResource(Mockito.anyString(), eq("setting.json")))
|
||||
.thenReturn(Mono.just(settingMap));
|
||||
|
||||
Mono<Map> formConfig = pluginService.getFormConfig("random-plugin-id");
|
||||
StepVerifier.create(formConfig)
|
||||
|
|
@ -116,6 +126,7 @@ public class PluginServiceTest {
|
|||
assertThat(form).isNotNull();
|
||||
assertThat(form.get("form")).isNotNull();
|
||||
assertThat(form.get("editor")).isNotNull();
|
||||
assertThat(form.get("setting")).isNotNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user