Merge branch 'feature/acl' into fix/invite-users

This commit is contained in:
Tejaaswini 2020-06-05 18:58:21 +05:30
commit 7c3d18b7f6
111 changed files with 3979 additions and 715 deletions

View File

@ -10,5 +10,7 @@
"overwrite": false, "overwrite": false,
"html": true, "html": true,
"json": false "json": false
} },
"viewportHeight": 900,
"viewportWidth": 1400
} }

View File

@ -0,0 +1,186 @@
{
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 1224,
"snapColumns": 16,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 1292,
"containerStyle": "none",
"snapRows": 33,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"dynamicBindings": {},
"version": 4,
"minHeight": 1292,
"parentColumnSpace": 1,
"leftColumn": 0,
"children": [
{
"backgroundColor": "#FFFFFF",
"widgetName": "Container1",
"type": "CONTAINER_WIDGET",
"containerStyle": "card",
"isVisible": true,
"isLoading": false,
"parentColumnSpace": 75.25,
"parentRowSpace": 38,
"dynamicBindings": {},
"leftColumn": 0,
"rightColumn": 8,
"topRow": 0,
"bottomRow": 9,
"snapColumns": 16,
"orientation": "VERTICAL",
"children": [
{
"backgroundColor": "transparent",
"widgetName": "kydabisaxj",
"type": "CANVAS_WIDGET",
"containerStyle": "none",
"isVisible": true,
"isLoading": false,
"parentColumnSpace": 1,
"parentRowSpace": 1,
"leftColumn": 0,
"rightColumn": 602,
"topRow": 0,
"bottomRow": 342,
"snapColumns": 16,
"orientation": "VERTICAL",
"children": [
{
"isVisible": true,
"widgetName": "Chart1",
"chartType": "LINE_CHART",
"chartName": "Sales on working days",
"allowHorizontalScroll": false,
"chartData": "[{\"seriesName\":\"\",\"data\":\"\"}]",
"xAxisName": "Last Week",
"yAxisName": "Total Order Revenue $",
"type": "CHART_WIDGET",
"isLoading": false,
"parentColumnSpace": 34.6875,
"parentRowSpace": 38,
"leftColumn": 5,
"rightColumn": 11,
"topRow": 0,
"bottomRow": 8,
"parentId": "56c5odk5ic",
"widgetId": "64jukpgbzh",
"dynamicBindings": {}
}
],
"widgetId": "56c5odk5ic",
"detachFromLayout": true,
"canExtend": false
}
],
"widgetId": "kzlk5ductp"
},
{
"backgroundColor": "#FFFFFF",
"widgetName": "Container3",
"type": "CONTAINER_WIDGET",
"containerStyle": "card",
"isVisible": true,
"isLoading": false,
"parentColumnSpace": 75.25,
"parentRowSpace": 38,
"dynamicBindings": {},
"leftColumn": 0,
"rightColumn": 16,
"topRow": 9,
"bottomRow": 23,
"snapColumns": 16,
"orientation": "VERTICAL",
"children": [
{
"backgroundColor": "transparent",
"widgetName": "za6o0unktq",
"type": "CANVAS_WIDGET",
"containerStyle": "none",
"isVisible": true,
"isLoading": false,
"parentColumnSpace": 1,
"parentRowSpace": 1,
"leftColumn": 0,
"rightColumn": 1204,
"topRow": 0,
"bottomRow": 532,
"snapColumns": 16,
"orientation": "VERTICAL",
"children": [
{
"isVisible": true,
"text": "Label",
"textStyle": "LABEL",
"textAlign": "LEFT",
"widgetName": "Text1",
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 71.75,
"parentRowSpace": 38,
"leftColumn": 3,
"rightColumn": 7,
"topRow": 2,
"bottomRow": 3,
"parentId": "d2i9xsy2fk",
"widgetId": "u210slvpsz",
"dynamicBindings": {}
}
],
"widgetId": "d2i9xsy2fk",
"detachFromLayout": true,
"canExtend": false
}
],
"widgetId": "t2a7se9pxe"
},
{
"backgroundColor": "#FFFFFF",
"widgetName": "Container4",
"type": "CONTAINER_WIDGET",
"containerStyle": "card",
"isVisible": true,
"isLoading": false,
"parentColumnSpace": 75.25,
"parentRowSpace": 38,
"dynamicBindings": {},
"leftColumn": 8,
"rightColumn": 16,
"topRow": 0,
"bottomRow": 9,
"snapColumns": 16,
"orientation": "VERTICAL",
"children": [
{
"backgroundColor": "transparent",
"widgetName": "cli9vgw4yj",
"type": "CANVAS_WIDGET",
"containerStyle": "none",
"isVisible": true,
"isLoading": false,
"parentColumnSpace": 1,
"parentRowSpace": 1,
"leftColumn": 0,
"rightColumn": 602,
"topRow": 0,
"bottomRow": 342,
"snapColumns": 16,
"orientation": "VERTICAL",
"children": [],
"widgetId": "qzniae78ab",
"detachFromLayout": true,
"canExtend": false
}
],
"widgetId": "gtsbf2q08n"
}
]
},
"layoutOnLoadActions": []
}

View File

@ -152,5 +152,20 @@
"productName": "Avocado Panini", "productName": "Avocado Panini",
"orderAmount": 7.99 "orderAmount": 7.99
} }
] ],
"chartInputValidate": [
{
"x": "Test1",
"y": 5400
},
{
"x": "Test2",
"y": 10000
},
{
"x": "Test3",
"y": 1000
}
],
"Chartval":["Test1", "Test2", "Test3"]
} }

View File

@ -17,6 +17,7 @@
"responsetext2": "qui est esse", "responsetext2": "qui est esse",
"baseUrl3": "https://reqres.in", "baseUrl3": "https://reqres.in",
"methods2": "api/users/2", "methods2": "api/users/2",
"invalidPath": "api/users/a",
"responsetext3": "Josh M Krantz", "responsetext3": "Josh M Krantz",
"postUrl": "https://reqres.in", "postUrl": "https://reqres.in",
"deleteUrl": "", "deleteUrl": "",

View File

@ -148,39 +148,34 @@
"chartType": "BAR_CHART", "chartType": "BAR_CHART",
"chartName": "App Sign Up", "chartName": "App Sign Up",
"allowHorizontalScroll": false, "allowHorizontalScroll": false,
"chartData": [ "singleChartData": [
{ {
"seriesName": "", "x": "Mon",
"data": [ "y": 10000
{ },
"x": "Mon", {
"y": 10000 "x": "Tue",
}, "y": 12000
{ },
"x": "Tue", {
"y": 12000 "x": "Wed",
}, "y": 32000
{ },
"x": "Wed", {
"y": 32000 "x": "Thu",
}, "y": 28000
{ },
"x": "Thu", {
"y": 28000 "x": "Fri",
}, "y": 14000
{ },
"x": "Fri", {
"y": 14000 "x": "Sat",
}, "y": 19000
{ },
"x": "Sat", {
"y": 19000 "x": "Sun",
}, "y": 36000
{
"x": "Sun",
"y": 36000
}
]
} }
], ],
"xAxisName": "Last Week", "xAxisName": "Last Week",
@ -265,4 +260,4 @@
] ]
}, },
"layoutOnLoadActions": [] "layoutOnLoadActions": []
} }

View File

@ -23,7 +23,7 @@ describe("API Panel Test Functionality", function() {
cy.ClearSearch(); cy.ClearSearch();
cy.SearchAPIandClick("SecondAPI"); cy.SearchAPIandClick("SecondAPI");
//invalid api end point check //invalid api end point check
cy.EditSourceDetail(testdata.baseUrl3, testdata.methods2); cy.EditSourceDetail(testdata.baseUrl3, testdata.invalidPath);
cy.ResponseStatusCheck("404 NOT_FOUND"); cy.ResponseStatusCheck("404 NOT_FOUND");
cy.DeleteAPI(); cy.DeleteAPI();
}); });

View File

@ -3,7 +3,6 @@ const testdata = require("../../../fixtures/testdata.json");
describe("API Panel Test Functionality ", function() { describe("API Panel Test Functionality ", function() {
it("Test Search API fetaure", function() { it("Test Search API fetaure", function() {
cy.log("Login Successful"); cy.log("Login Successful");
cy.viewport("macbook-15"); //To avoid screen Resize issues
cy.NavigateToAPI_Panel(); cy.NavigateToAPI_Panel();
cy.log("Navigation to API Panel screen successful"); cy.log("Navigation to API Panel screen successful");
cy.CreateAPI("FirstAPI"); cy.CreateAPI("FirstAPI");

View File

@ -3,7 +3,6 @@ const testdata = require("../../../fixtures/testdata.json");
describe("API Panel Test Functionality ", function() { describe("API Panel Test Functionality ", function() {
it("Test API copy/Move/delete feature", function() { it("Test API copy/Move/delete feature", function() {
cy.log("Login Successful"); cy.log("Login Successful");
cy.viewport("macbook-15"); //To avoid screen Resize issues
cy.NavigateToAPI_Panel(); cy.NavigateToAPI_Panel();
cy.log("Navigation to API Panel screen successful"); cy.log("Navigation to API Panel screen successful");
cy.CreateAPI("FirstAPI"); cy.CreateAPI("FirstAPI");

View File

@ -0,0 +1,71 @@
const commonlocators = require("../../../locators/commonlocators.json");
const viewWidgetsPage = require("../../../locators/ViewWidgets.json");
const publish = require("../../../locators/publishWidgetspage.json");
const dsl = require("../../../fixtures/ChartTextDsl.json");
describe("Text-Chart Binding Functionality", function() {
before(() => {
cy.addDsl(dsl);
});
it("Text-Chart Binding Functionality View", function() {
cy.openPropertyPane("textwidget");
cy.testJsontext("text", JSON.stringify(this.data.chartInputValidate));
cy.get(commonlocators.TextInside).should(
"have.text",
JSON.stringify(this.data.chartInputValidate),
);
cy.closePropertyPane();
cy.openPropertyPane("chartwidget");
cy.get(viewWidgetsPage.chartType)
.find(commonlocators.dropdownbuttonclick)
.click({ force: true })
.get(commonlocators.dropdownmenu)
.children()
.contains("Column Chart")
.click();
cy.get(viewWidgetsPage.chartType)
.find(commonlocators.menuSelection)
.should("have.text", "Column Chart");
cy.testJsontext("chartdata", "{{Text1.text}}");
cy.closePropertyPane();
const labels = [
this.data.Chartval[0],
this.data.Chartval[1],
this.data.Chartval[2],
];
[0, 1, 2].forEach(k => {
cy.get(viewWidgetsPage.rectangleChart)
.eq(k)
.trigger("mousemove", { force: true });
cy.get(viewWidgetsPage.Chartlabel)
.eq(k)
.should("have.text", labels[k]);
});
cy.PublishtheApp();
});
it("Text-Chart Binding Functionality Publish", function() {
cy.get(publish.chartCanvasVal).should("be.visible");
cy.get(publish.chartWidget).should("have.css", "opacity", "1");
const labels = [
this.data.Chartval[0],
this.data.Chartval[1],
this.data.Chartval[2],
];
[0, 1, 2].forEach(k => {
cy.get(publish.rectChart)
.eq(k)
.trigger("mousemove", { force: true });
cy.get(publish.chartLab)
.eq(k)
.should("have.text", labels[k]);
});
cy.get(commonlocators.TextInside).should(
"have.text",
JSON.stringify(this.data.chartInputValidate),
);
});
});
afterEach(() => {
// put your clean up code if any
});

View File

@ -6,13 +6,85 @@ describe("Text-Table Binding Functionality", function() {
before(() => { before(() => {
cy.addDsl(dsl); cy.addDsl(dsl);
}); });
it("Text-Table Binding Functionality For Username", function() { it("Text-Table Binding Functionality For Id", function() {
cy.openPropertyPane("tablewidget"); cy.openPropertyPane("tablewidget");
/** /**
* @param(Index) Provide index value to select the row. * @param(Index) Provide index value to select the row.
*/ */
cy.isSelectRow(1); cy.isSelectRow(1);
cy.openPropertyPane("textwidget"); cy.openPropertyPane("textwidget");
cy.testJsontext("text", "{{Table1.selectedRow.id}}");
/**
* @param{Row Index} Provide the row index
* @param(Column Index) Provide column index
*/
cy.readTabledata("1", "0").then(tabData => {
const tabValue = tabData;
cy.get(commonlocators.TextInside).should("have.text", tabValue);
cy.PublishtheApp();
cy.isSelectRow(1);
cy.readTabledataPublish("1", "0").then(tabDataP => {
const tabValueP = tabDataP;
cy.get(commonlocators.TextInside).should("have.text", tabValueP);
});
});
});
it("Text-Table Binding Functionality For Email", function() {
cy.get(publish.backToEditor)
.first()
.click();
cy.isSelectRow(2);
cy.openPropertyPane("textwidget");
cy.testJsontext("text", "{{Table1.selectedRow.email}}");
/**
* @param{Row Index} Provide the row index
* @param(Column Index) Provide column index
*/
cy.readTabledata("2", "1").then(tabData => {
const tabValue = tabData;
cy.get(commonlocators.TextInside).should("have.text", tabValue);
cy.PublishtheApp();
cy.isSelectRow(2);
cy.readTabledataPublish("2", "1").then(tabDataP => {
const tabValueP = tabDataP;
cy.get(commonlocators.TextInside).should("have.text", tabValueP);
});
});
});
it("Text-Table Binding Functionality For Total Length", function() {
cy.get(publish.backToEditor)
.first()
.click();
cy.pageNo();
cy.openPropertyPane("textwidget");
cy.testJsontext("text", "{{Table1.pageSize}}");
cy.get(commonlocators.TableRow)
.find(".tr")
.then(listing => {
const listingCount = listing.length.toString();
cy.get(commonlocators.TextInside).should("have.text", listingCount);
cy.PublishtheApp();
cy.pageNo();
cy.get(publish.tableLength)
.find(".tr")
.then(listing => {
const listingCountP = listing.length.toString();
cy.get(commonlocators.TextInside).should(
"have.text",
listingCountP,
);
});
});
});
it("Text-Table Binding Functionality For Username", function() {
cy.get(publish.backToEditor)
.first()
.click();
/**
* @param(Index) Provide index value to select the row.
*/
cy.isSelectRow(1);
cy.openPropertyPane("textwidget");
cy.testJsontext("text", JSON.stringify(this.data.textfun)); cy.testJsontext("text", JSON.stringify(this.data.textfun));
/** /**
* @param{Row Index} Provide the row index * @param{Row Index} Provide the row index

View File

@ -25,14 +25,14 @@ describe("Input Widget Functionality", function() {
cy.get(widgetsPage.innertext) cy.get(widgetsPage.innertext)
.click({ force: true }) .click({ force: true })
.type(this.data.para); .type(this.data.para);
cy.get(publish.inputWidget + " " + "input") cy.get(widgetsPage.inputWidget + " " + "input")
.invoke("attr", "value") .invoke("attr", "value")
.should("contain", this.data.para); .should("contain", this.data.para);
cy.openPropertyPane("inputwidget"); cy.openPropertyPane("inputwidget");
cy.get(widgetsPage.defaultInput) cy.get(widgetsPage.defaultInput)
.type(this.data.command) .type(this.data.command)
.type(this.data.defaultdata); .type(this.data.defaultdata);
cy.get(publish.inputWidget + " " + "input") cy.get(widgetsPage.inputWidget + " " + "input")
.invoke("attr", "value") .invoke("attr", "value")
.should("contain", this.data.defaultdata); .should("contain", this.data.defaultdata);
cy.get(widgetsPage.placeholder) cy.get(widgetsPage.placeholder)

View File

@ -19,16 +19,16 @@ describe("Table Widget Functionality", function() {
cy.widgetText("Table1", widgetsPage.tableWidget, commonlocators.tableInner); cy.widgetText("Table1", widgetsPage.tableWidget, commonlocators.tableInner);
cy.testJsontext("tabledata", JSON.stringify(this.data.TableInput)); cy.testJsontext("tabledata", JSON.stringify(this.data.TableInput));
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.ExportVerify(commonlocators.pdfSupport, "PDF Export"); // cy.ExportVerify(commonlocators.pdfSupport, "PDF Export");
cy.ExportVerify(commonlocators.ExcelSupport, "Excel Export"); // cy.ExportVerify(commonlocators.ExcelSupport, "Excel Export");
cy.ExportVerify(commonlocators.csvSupport, "CSV Export"); // cy.ExportVerify(commonlocators.csvSupport, "CSV Export");
cy.get(widgetsPage.ColumnAction).click({ force: true }); cy.get(widgetsPage.ColumnAction).click({ force: true });
cy.readTabledata("1", "5").then(tabData => { // cy.readTabledata("1", "5").then(tabData => {
const tabValue = tabData; // const tabValue = tabData;
expect(tabValue).to.be.equal("Action"); // expect(tabValue).to.be.equal("Action");
cy.log("the value is" + tabValue); // cy.log("the value is" + tabValue);
}); // });
cy.pageNo(2).should("be.visible"); cy.pageNo();
cy.openPropertyPane("tablewidget"); cy.openPropertyPane("tablewidget");
cy.get(widgetsPage.tableOnRowSelected) cy.get(widgetsPage.tableOnRowSelected)
.get(commonlocators.dropdownSelectButton) .get(commonlocators.dropdownSelectButton)
@ -56,31 +56,33 @@ describe("Table Widget Functionality", function() {
}); });
}); });
it("Table Widget Functionality To Verify The PageNo", function() { it("Table Widget Functionality To Verify The PageNo", function() {
cy.pageNo(2).should("be.visible"); cy.pageNo();
cy.get(publish.backToEditor).click(); cy.get(publish.backToEditor)
}); .first()
it("Table Widget Functionality To Verify The Extension Support", function() { .click();
cy.openPropertyPane("tablewidget");
cy.togglebar(commonlocators.pdfSupport);
cy.PublishtheApp();
cy.get(publish.tableWidget + " " + "button").should(
"contain",
"PDF Export",
);
cy.get(publish.backToEditor).click();
cy.openPropertyPane("tablewidget");
cy.togglebarDisable(commonlocators.pdfSupport);
cy.togglebar(commonlocators.ExcelSupport);
cy.PublishtheApp();
cy.get(publish.tableWidget + " " + "button").should(
"not.contain",
"PDF Export",
);
cy.get(publish.tableWidget + " " + "button").should(
"contain",
"Excel Export",
);
}); });
// it("Table Widget Functionality To Verify The Extension Support", function() {
// cy.openPropertyPane("tablewidget");
// cy.togglebar(commonlocators.pdfSupport);
// cy.PublishtheApp();
// cy.get(publish.tableWidget + " " + "button").should(
// "contain",
// "PDF Export",
// );
// cy.get(publish.backToEditor).click();
// cy.openPropertyPane("tablewidget");
// cy.togglebarDisable(commonlocators.pdfSupport);
// cy.togglebar(commonlocators.ExcelSupport);
// cy.PublishtheApp();
// cy.get(publish.tableWidget + " " + "button").should(
// "not.contain",
// "PDF Export",
// );
// cy.get(publish.tableWidget + " " + "button").should(
// "contain",
// "Excel Export",
// );
// });
}); });
Cypress.on("test:after:run", attributes => { Cypress.on("test:after:run", attributes => {
/* eslint-disable no-console */ /* eslint-disable no-console */

View File

@ -1,6 +1,7 @@
const dsl = require("../../../fixtures/commondsl.json"); const dsl = require("../../../fixtures/commondsl.json");
const pages = require("../../../locators/Pages.json"); const pages = require("../../../locators/Pages.json");
const dynamicInputLocators = require("../../../locators/DynamicInput.json"); const dynamicInputLocators = require("../../../locators/DynamicInput.json");
const apiwidget = require("../../../locators/apiWidgetslocator.json");
describe("Dynamic input autocomplete", () => { describe("Dynamic input autocomplete", () => {
beforeEach(() => { beforeEach(() => {
@ -52,4 +53,22 @@ describe("Dynamic input autocomplete", () => {
}); });
}); });
}); });
it("opens current value popup", () => {
// Test on widgets pane
cy.get(pages.widgetsEditor).click();
cy.openPropertyPane("buttonwidget");
cy.get(dynamicInputLocators.input)
.first()
.focus();
cy.assertEvaluatedValuePopup("string");
// Test on api pane
cy.NavigateToAPI_Panel();
cy.get(apiwidget.createapi).click({ force: true });
cy.wait("@createNewApi");
cy.xpath(apiwidget.headerValue)
.first()
.focus();
cy.assertEvaluatedValuePopup("string");
});
}); });

View File

@ -2,7 +2,6 @@ describe("Create and Delete App Functionality", function() {
it("Delete App Functionality", function() { it("Delete App Functionality", function() {
cy.log("appname: " + localStorage.getItem("AppName")); cy.log("appname: " + localStorage.getItem("AppName"));
const appname = localStorage.getItem("AppName"); const appname = localStorage.getItem("AppName");
cy.openPropertyPane("textwidget");
cy.DeleteApp(appname); cy.DeleteApp(appname);
cy.wait("@deleteApplication"); cy.wait("@deleteApplication");
cy.get("@deleteApplication").should("have.property", "status", 200); cy.get("@deleteApplication").should("have.property", "status", 200);

View File

@ -1,13 +1,17 @@
const commonlocators = require("../../../locators/commonlocators.json"); const commonlocators = require("../../../locators/commonlocators.json");
const viewWidgetsPage = require("../../../locators/ViewWidgets.json"); const viewWidgetsPage = require("../../../locators/ViewWidgets.json");
const dsl = require("../../../fixtures/Mapdsl.json"); const dsl = require("../../../fixtures/Mapdsl.json");
const publishPage = require("../../../locators/publishWidgetspage.json");
describe("Map Widget Functionality", function() { describe("Map Widget Functionality", function() {
beforeEach(() => { before(() => {
cy.addDsl(dsl); cy.addDsl(dsl);
}); });
it("Map Widget Functionality", function() {
this.beforeEach(() => {
cy.openPropertyPane("mapwidget"); cy.openPropertyPane("mapwidget");
});
it("Map Widget Functionality", function() {
/** /**
* @param{Text} Random Text * @param{Text} Random Text
* @param{MapWidget}Mouseover * @param{MapWidget}Mouseover
@ -35,18 +39,88 @@ describe("Map Widget Functionality", function() {
cy.get(viewWidgetsPage.zoomLevel) cy.get(viewWidgetsPage.zoomLevel)
.eq(1) .eq(1)
.click({ force: true }); .click({ force: true });
cy.get(viewWidgetsPage.mapSearch).should("be.visible");
cy.get(viewWidgetsPage.mapSearch)
.invoke("attr", "placeholder")
.should("contain", "Enter location to search");
cy.get(viewWidgetsPage.mapSearch) cy.get(viewWidgetsPage.mapSearch)
.click({ force: true }) .click({ force: true })
.clear() .clear()
.type(this.data.location2) .type(this.data.location2)
.type("{enter}"); .type("{enter}");
}); });
afterEach(() => { it("Map-Enable Location,Map search and Create Marker Property Validation", function() {
// put your clean up code if any /**
* Enable the Search Location checkbox and Validate the same in editor mode
*/
cy.CheckWidgetProperties(commonlocators.enableSearchLocCheckbox);
cy.get(viewWidgetsPage.mapSearch).should("be.visible");
cy.get(viewWidgetsPage.mapSearch)
.invoke("attr", "placeholder")
.should("contain", "Enter location to search");
/**
* Enable the Pick Location checkbox and Validate the same in editor mode
*/
cy.CheckWidgetProperties(commonlocators.enablePickLocCheckbox);
cy.get(viewWidgetsPage.pickMyLocation).should("exist");
/**
* Enable the Createnew Marker checkbox and Validate the same in editor mode
*/
cy.CheckWidgetProperties(commonlocators.enableCreateMarkerCheckbox);
/**
* Validation will be added when create marker fun is working fine
*/
cy.PublishtheApp();
/**
* Publish mode Validation
*/
cy.get(publishPage.mapSearch).should("be.visible");
cy.get(publishPage.mapSearch)
.invoke("attr", "placeholder")
.should("contain", "Enter location to search");
cy.get(publishPage.pickMyLocation).should("exist");
cy.get(publishPage.backToEditor).click();
});
it("Map-Disable Location, Mapsearch and Create Marker Property Validation", function() {
/**
* Disable the Search Location checkbox and Validate the same in editor mode
*/
cy.UncheckWidgetProperties(commonlocators.enableSearchLocCheckbox);
cy.get(viewWidgetsPage.mapSearch).should("not.be.visible");
/**
* Disable the Pick Location checkbox and Validate the same in editor mode
*/
cy.UncheckWidgetProperties(commonlocators.enablePickLocCheckbox);
cy.get(viewWidgetsPage.pickMyLocation).should("not.exist");
/**
* Disable the Createnew Marker checkbox and Validate the same in editor mode
*/
cy.UncheckWidgetProperties(commonlocators.enableCreateMarkerCheckbox);
/**
* Validation will be added when create marker fun is working fine
*/
cy.PublishtheApp();
/**
* Publish mode Validation
*/
cy.get(publishPage.mapSearch).should("not.be.visible");
cy.get(publishPage.pickMyLocation).should("not.exist");
cy.get(publishPage.backToEditor).click();
});
it("Map-Check Visible field Validation", function() {
//Check the disableed checkbox and Validate
cy.CheckWidgetProperties(commonlocators.visibleCheckbox);
cy.PublishtheApp();
cy.get(publishPage.mapWidget).should("be.visible");
cy.get(publishPage.backToEditor).click();
});
it("Map-Unckeck Visible field Validation", function() {
//Uncheck the disabled checkbox and validate
cy.UncheckWidgetProperties(commonlocators.visibleCheckbox);
cy.PublishtheApp();
cy.get(publishPage.mapWidget).should("not.be.visible");
}); });
}); });

View File

@ -1,4 +1,5 @@
{ {
"input": ".CodeMirror textarea", "input": ".CodeMirror textarea",
"hints": "ul.CodeMirror-hints" "hints": "ul.CodeMirror-hints",
"evaluatedValue": ".t--CodeEditor-evaluatedValue"
} }

View File

@ -27,5 +27,6 @@
"createMarker": ".t--property-control-createnewmarker [type='checkbox']", "createMarker": ".t--property-control-createnewmarker [type='checkbox']",
"zoomLevel": ".t--property-control-zoomlevel svg", "zoomLevel": ".t--property-control-zoomlevel svg",
"defaultImage": ".t--property-control-defaultimage .CodeMirror-code", "defaultImage": ".t--property-control-defaultimage .CodeMirror-code",
"Chartlabel": ".t--draggable-chartwidget g:nth-child(5) text" "Chartlabel": ".t--draggable-chartwidget g:nth-child(5) text",
"pickMyLocation": ".t--draggable-mapwidget div[title='Pick My Location']"
} }

View File

@ -6,7 +6,7 @@
"popover": ".bp3-popover-target >div>svg", "popover": ".bp3-popover-target >div>svg",
"moveTo": ".single-select >div:contains('Move to')", "moveTo": ".single-select >div:contains('Move to')",
"copyTo": ".single-select >div:contains('Copy to')", "copyTo": ".single-select >div:contains('Copy to')",
"home": ".single-select >div:contains('Home')", "home": ".single-select >div:contains('Page1')",
"delete": ".single-select >div:contains('Delete')", "delete": ".single-select >div:contains('Delete')",
"path": ".t--path >div textarea", "path": ".t--path >div textarea",
"editResourceUrl": ".t--dataSourceField input", "editResourceUrl": ".t--dataSourceField input",
@ -33,4 +33,4 @@
"panigationPrevUrl": ".t--apiFormPaginationPrev div>textarea", "panigationPrevUrl": ".t--apiFormPaginationPrev div>textarea",
"TestNextUrl": ".t--apiFormPaginationNextTest", "TestNextUrl": ".t--apiFormPaginationNextTest",
"TestPreUrl": ".t--apiFormPaginationPrevTest" "TestPreUrl": ".t--apiFormPaginationPrevTest"
} }

View File

@ -33,7 +33,7 @@
"editWidgetName": ".bp3-editable-text", "editWidgetName": ".bp3-editable-text",
"dropDownIcon": ".t--property-control-textstyle span.bp3-icon-chevron-down", "dropDownIcon": ".t--property-control-textstyle span.bp3-icon-chevron-down",
"onDateSelectedField": ".t--property-control-ondateselected", "onDateSelectedField": ".t--property-control-ondateselected",
"TableRow": ".t--draggable-tablewidget .e-gridcontent.e-lib.e-droppable", "TableRow": ".t--draggable-tablewidget .tbody",
"Disablejs": ".t--property-control-disabled", "Disablejs": ".t--property-control-disabled",
"requiredjs": ".t--property-control-required", "requiredjs": ".t--property-control-required",
"horizontalScroll": ".t--property-control-allowhorizontalscroll input", "horizontalScroll": ".t--property-control-allowhorizontalscroll input",
@ -45,5 +45,8 @@
"disabledBtn": " button[disabled='disabled']", "disabledBtn": " button[disabled='disabled']",
"inputField": " .bp3-input", "inputField": " .bp3-input",
"csvSupport": ".t--property-control-csvexport input", "csvSupport": ".t--property-control-csvexport input",
"backToEditor": ".bp3-icon.bp3-icon-chevron-left + span.bp3-button-text" "backToEditor": ".t--back-to-editor",
} "enableSearchLocCheckbox": ".t--property-control-enablesearchlocation input",
"enablePickLocCheckbox": ".t--property-control-enablepicklocation input",
"enableCreateMarkerCheckbox": ".t--property-control-createnewmarker input"
}

View File

@ -3,7 +3,7 @@
"textWidget": ".t--widget-textwidget", "textWidget": ".t--widget-textwidget",
"richTextEditorWidget": ".t--widget-richtexteditorwidget", "richTextEditorWidget": ".t--widget-richtexteditorwidget",
"datepickerWidget": ".t--widget-datepickerwidget", "datepickerWidget": ".t--widget-datepickerwidget",
"backToEditor": ".bp3-icon.bp3-icon-chevron-left + span.bp3-button-text", "backToEditor": ".t--back-to-editor",
"inputWidget": ".t--widget-inputwidget", "inputWidget": ".t--widget-inputwidget",
"checkboxWidget": ".t--widget-checkboxwidget", "checkboxWidget": ".t--widget-checkboxwidget",
"radioWidget": ".t--widget-radiogroupwidget", "radioWidget": ".t--widget-radiogroupwidget",
@ -12,7 +12,14 @@
"dropdownWidget": ".t--widget-dropdownwidget", "dropdownWidget": ".t--widget-dropdownwidget",
"tabWidget": ".t--widget-tabswidget", "tabWidget": ".t--widget-tabswidget",
"chartWidget": ".t--widget-chartwidget", "chartWidget": ".t--widget-chartwidget",
"horizontalTab": ".t--widget-chartwidget g[class='raphael-group-104-axis-Line-group'] rect", "horizontalTab": ".t--widget-chartwidget g[class*='-scrollContainer'] rect",
"tableWidget": ".t--widget-tablewidget", "tableWidget": ".t--widget-tablewidget",
"tableLength": ".t--widget-tablewidget .e-gridcontent.e-lib.e-droppable" "chartCanvasVal": ".t--widget-chartwidget g[class*='-canvas'] rect ",
"mapWidget": ".t--widget-mapwidget",
"tableLength": ".t--widget-tablewidget .tbody",
"mapSearch": ".t--widget-mapwidget input",
"pickMyLocation": ".t--widget-mapwidget div[title='Pick My Location']",
"rectChart":".t--widget-chartwidget g rect",
"chartLab":".t--widget-chartwidget g:nth-child(5) text"
} }

View File

@ -11,6 +11,7 @@ const LayoutPage = require("../locators/Layout.json");
const formWidgetsPage = require("../locators/FormWidgets.json"); const formWidgetsPage = require("../locators/FormWidgets.json");
const ApiEditor = require("../locators/ApiEditor.json"); const ApiEditor = require("../locators/ApiEditor.json");
const apiwidget = require("../locators/apiWidgetslocator.json"); const apiwidget = require("../locators/apiWidgetslocator.json");
const dynamicInputLocators = require("../locators/DynamicInput.json");
let pageidcopy = " "; let pageidcopy = " ";
Cypress.Commands.add("CreateApp", appname => { Cypress.Commands.add("CreateApp", appname => {
@ -184,7 +185,8 @@ Cypress.Commands.add("enterDatasourceAndPath", (datasource, path) => {
cy.xpath(apiwidget.autoSuggest) cy.xpath(apiwidget.autoSuggest)
.first() .first()
.click({ force: true }); .click({ force: true });
cy.get(apiwidget.path) cy.get(apiwidget.editResourceUrl)
.first()
.click({ force: true }) .click({ force: true })
.type(path, { parseSpecialCharSequences: false }); .type(path, { parseSpecialCharSequences: false });
}); });
@ -210,16 +212,17 @@ Cypress.Commands.add(
Cypress.Commands.add("EditSourceDetail", (baseUrl, v1method) => { Cypress.Commands.add("EditSourceDetail", (baseUrl, v1method) => {
cy.get(apiwidget.editResourceUrl) cy.get(apiwidget.editResourceUrl)
.first() .first()
.clear()
.click({ force: true }) .click({ force: true })
.type(baseUrl); .clear()
.type(`{backspace}${baseUrl}`);
cy.xpath(apiwidget.autoSuggest) cy.xpath(apiwidget.autoSuggest)
.first() .first()
.click({ force: true }); .click({ force: true });
cy.get(ApiEditor.ApiRunBtn).scrollIntoView(); cy.get(ApiEditor.ApiRunBtn).scrollIntoView();
cy.get(apiwidget.path) cy.get(apiwidget.editResourceUrl)
.first()
.focus() .focus()
.type("{selectall}{backspace}api/users/2") .type(v1method)
.should("have.value", v1method); .should("have.value", v1method);
cy.SaveAPI(); cy.SaveAPI();
}); });
@ -947,6 +950,10 @@ Cypress.Commands.add("openPropertyPane", widgetType => {
.click(); .click();
}); });
Cypress.Commands.add("closePropertyPane", () => {
cy.get(commonlocators.editPropCrossButton).click();
});
Cypress.Commands.add("createApi", (url, parameters) => { Cypress.Commands.add("createApi", (url, parameters) => {
cy.get("@createNewApi").then(response => { cy.get("@createNewApi").then(response => {
cy.get(ApiEditor.ApiNameField).should("be.visible"); cy.get(ApiEditor.ApiNameField).should("be.visible");
@ -961,7 +968,7 @@ Cypress.Commands.add("createApi", (url, parameters) => {
cy.contains(url).click({ cy.contains(url).click({
force: true, force: true,
}); });
cy.get(".CodeMirror.CodeMirror-empty textarea") cy.get(apiwidget.editResourceUrl)
.first() .first()
.click({ force: true }) .click({ force: true })
.type(parameters, { force: true }); .type(parameters, { force: true });
@ -972,16 +979,13 @@ Cypress.Commands.add("createApi", (url, parameters) => {
Cypress.Commands.add("isSelectRow", index => { Cypress.Commands.add("isSelectRow", index => {
cy.get( cy.get(
'.e-gridcontent.e-lib.e-droppable td[index="' + '.tbody .td[data-rowindex="' + index + '"][data-colindex="' + 0 + '"]',
index +
'"][aria-colindex="' +
index +
'"]',
).click({ force: true }); ).click({ force: true });
}); });
Cypress.Commands.add("readTabledata", (rowNum, colNum) => { Cypress.Commands.add("readTabledata", (rowNum, colNum) => {
const selector = `.t--draggable-tablewidget .e-gridcontent.e-lib.e-droppable td[index=${rowNum}][aria-colindex=${colNum}]`; // const selector = `.t--draggable-tablewidget .e-gridcontent.e-lib.e-droppable td[index=${rowNum}][aria-colindex=${colNum}]`;
const selector = `.t--draggable-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div`;
const tabVal = cy.get(selector).invoke("text"); const tabVal = cy.get(selector).invoke("text");
return tabVal; return tabVal;
}); });
@ -1002,10 +1006,9 @@ Cypress.Commands.add("setDate", (date, dateFormate) => {
}); });
Cypress.Commands.add("pageNo", index => { Cypress.Commands.add("pageNo", index => {
cy.get(".e-pagercontainer a") cy.get(".page-item")
.eq(index) .first()
.click({ force: true }) .click({ force: true });
.should("be.visible");
}); });
Cypress.Commands.add("pageNoValidate", index => { Cypress.Commands.add("pageNoValidate", index => {
@ -1106,7 +1109,18 @@ Cypress.Commands.add("ExportVerify", (togglecss, name) => {
}); });
Cypress.Commands.add("readTabledataPublish", (rowNum, colNum) => { Cypress.Commands.add("readTabledataPublish", (rowNum, colNum) => {
const selector = `.t--widget-tablewidget .e-gridcontent.e-lib.e-droppable td[index=${rowNum}][aria-colindex=${colNum}]`; // const selector = `.t--widget-tablewidget .e-gridcontent.e-lib.e-droppable td[index=${rowNum}][aria-colindex=${colNum}]`;
const selector = `.t--widget-tablewidget .tbody .td[data-rowindex=${rowNum}][data-colindex=${colNum}] div`;
const tabVal = cy.get(selector).invoke("text"); const tabVal = cy.get(selector).invoke("text");
return tabVal; return tabVal;
}); });
Cypress.Commands.add("assertEvaluatedValuePopup", expectedType => {
cy.get(dynamicInputLocators.evaluatedValue)
.should("be.visible")
.children("p")
.should("contain.text", "Expected type:")
.should("contain.text", "Current Value:")
.siblings("pre")
.should("have.text", expectedType);
});

View File

@ -22,7 +22,6 @@ let appId;
import "./commands"; import "./commands";
before(function() { before(function() {
console.log("**** Got Cypress base URL as: ", process.env.CYPRESS_BASE_URL); console.log("**** Got Cypress base URL as: ", process.env.CYPRESS_BASE_URL);
cy.viewport("macbook-15");
cy.startServerAndRoutes(); cy.startServerAndRoutes();
cy.LogintoApp(loginData.username, loginData.password); cy.LogintoApp(loginData.username, loginData.password);
// cy.SearchApp(inputData.appname) // cy.SearchApp(inputData.appname)

View File

@ -18,6 +18,10 @@ server {
proxy_pass http://localhost:3000; proxy_pass http://localhost:3000;
} }
location /f {
proxy_pass https://cdn.optimizely.com/;
}
location /api { location /api {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Host $host;
@ -58,6 +62,10 @@ server {
location / { location / {
proxy_pass http://localhost:3000; proxy_pass http://localhost:3000;
} }
location /f {
proxy_pass https://cdn.optimizely.com/;
}
location /api { location /api {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;

View File

@ -16,6 +16,10 @@ server {
proxy_pass http://host.docker.internal:3000; proxy_pass http://host.docker.internal:3000;
} }
location /f {
proxy_pass https://cdn.optimizely.com/;
}
location /api { location /api {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Host $host;
@ -56,6 +60,10 @@ server {
location / { location / {
proxy_pass http://host.docker.internal:3000; proxy_pass http://host.docker.internal:3000;
} }
location /f {
proxy_pass https://cdn.optimizely.com/;
}
location /api { location /api {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;

View File

@ -60,6 +60,12 @@
force = true force = true
headers = { X-Forwarded-Host = "APP_HOST_PLACEHOLDER", X-Forwarded-Proto = "https" } headers = { X-Forwarded-Host = "APP_HOST_PLACEHOLDER", X-Forwarded-Proto = "https" }
[[redirects]]
from = "/f/*"
to = "https://cdn.optimizely.com/:splat"
status = 200
force = true
# This must be the last redirect in the chain because it's a catch-all # This must be the last redirect in the chain because it's a catch-all
[[redirects]] [[redirects]]
from = "/*" from = "/*"

View File

@ -32,6 +32,7 @@
"@types/react-instantsearch-dom": "^6.3.0", "@types/react-instantsearch-dom": "^6.3.0",
"@types/react-redux": "^7.0.1", "@types/react-redux": "^7.0.1",
"@types/react-router-dom": "^5.1.2", "@types/react-router-dom": "^5.1.2",
"@types/react-table": "^7.0.13",
"@types/styled-components": "^4.1.8", "@types/styled-components": "^4.1.8",
"@types/tinycolor2": "^1.4.2", "@types/tinycolor2": "^1.4.2",
"@uppy/core": "^1.8.2", "@uppy/core": "^1.8.2",
@ -90,6 +91,7 @@
"react-scripts": "^3.3.0", "react-scripts": "^3.3.0",
"react-select": "^3.0.8", "react-select": "^3.0.8",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-table": "^7.0.0",
"react-tabs": "^3.0.0", "react-tabs": "^3.0.0",
"react-toastify": "^5.5.0", "react-toastify": "^5.5.0",
"react-transition-group": "^4.3.0", "react-transition-group": "^4.3.0",

View File

@ -1,18 +1,19 @@
import { RestAction, PaginationField, ActionResponse } from "api/ActionAPI"; import { PaginationField, ActionResponse } from "api/ActionAPI";
import { import {
ReduxActionTypes, ReduxActionTypes,
ReduxAction, ReduxAction,
ReduxActionErrorTypes, ReduxActionErrorTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { Action, RestAction } from "entities/Action";
export const createActionRequest = (payload: Partial<RestAction>) => { export const createActionRequest = (payload: Partial<Action>) => {
return { return {
type: ReduxActionTypes.CREATE_ACTION_INIT, type: ReduxActionTypes.CREATE_ACTION_INIT,
payload, payload,
}; };
}; };
export const createActionSuccess = (payload: RestAction) => { export const createActionSuccess = (payload: Action) => {
return { return {
type: ReduxActionTypes.CREATE_ACTION_SUCCESS, type: ReduxActionTypes.CREATE_ACTION_SUCCESS,
payload, payload,

View File

@ -48,6 +48,16 @@ export const createNewApiAction = (
payload: { pageId }, payload: { pageId },
}); });
export const setDatasourceFieldText = (
apiId: string,
value: string,
): ReduxAction<{ apiId: string; value: string }> => {
return {
type: ReduxActionTypes.SET_DATASOURCE_FIELD_TEXT,
payload: { apiId, value },
};
};
export const setExtraFormData = ( export const setExtraFormData = (
apiId: string, apiId: string,
extraformData: {}, extraformData: {},

View File

@ -77,6 +77,12 @@ export const initDatasourcePane = (
}; };
}; };
export const storeAsDatasource = () => {
return {
type: ReduxActionTypes.STORE_AS_DATASOURCE_INIT,
};
};
export default { export default {
createDatasource, createDatasource,
fetchDatasources, fetchDatasources,

View File

@ -1,5 +1,5 @@
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
import { RestAction } from "api/ActionAPI"; import { RestAction } from "entities/Action";
export const createQueryRequest = (payload: Partial<RestAction>) => { export const createQueryRequest = (payload: Partial<RestAction>) => {
return { return {

View File

@ -1,12 +1,11 @@
import API, { HttpMethod } from "./Api"; import API from "./Api";
import { ApiResponse, GenericApiResponse, ResponseMeta } from "./ApiResponses"; import { ApiResponse, GenericApiResponse, ResponseMeta } from "./ApiResponses";
import { import {
APIRequest, APIRequest,
DEFAULT_EXECUTE_ACTION_TIMEOUT_MS, DEFAULT_EXECUTE_ACTION_TIMEOUT_MS,
} from "constants/ApiConstants"; } from "constants/ApiConstants";
import { AxiosPromise } from "axios"; import { AxiosPromise } from "axios";
import { Datasource } from "./DatasourcesApi"; import { RestAction } from "entities/Action";
import { PaginationType } from "pages/Editor/APIEditor/Pagination";
export interface CreateActionRequest<T> extends APIRequest { export interface CreateActionRequest<T> extends APIRequest {
datasourceId: string; datasourceId: string;
@ -19,21 +18,9 @@ export interface UpdateActionRequest<T> extends CreateActionRequest<T> {
actionId: string; actionId: string;
} }
export interface APIConfig {
datasourceId: string;
pageId: string;
name: string;
requestHeaders: Record<string, string>;
httpMethod: HttpMethod;
path: string;
body: JSON;
queryParams: Record<string, string>;
actionId: string;
}
export interface Property { export interface Property {
key: string; key: string;
value: string; value?: string;
} }
export interface BodyFormData { export interface BodyFormData {
@ -45,17 +32,6 @@ export interface BodyFormData {
type: string; type: string;
} }
export interface APIConfigRequest {
headers: Property[];
httpMethod: string;
path: string;
body: JSON | string | Record<string, any> | null;
queryParameters: Property[];
paginationType: PaginationType;
bodyFormData: BodyFormData[];
timeoutInMillisecond: number;
}
export interface QueryConfig { export interface QueryConfig {
queryString: string; queryString: string;
} }
@ -65,45 +41,6 @@ export interface ActionCreateUpdateResponse extends ApiResponse {
jsonPathKeys: Record<string, string>; jsonPathKeys: Record<string, string>;
} }
export interface RestAction {
id: string;
name: string;
datasource:
| Pick<Datasource, "id">
| Omit<Datasource, "id">
| Partial<Datasource>;
pluginType?: string;
pageId: string;
actionConfiguration: Partial<APIConfigRequest>;
jsonPathKeys: string[];
cacheResponse?: string;
pluginId: string;
}
export interface RapidApiAction {
id: string;
name: string;
datasource: Pick<Datasource, "id"> | Omit<Datasource, "id">;
pluginType: string;
pageId: string;
actionConfiguration: Partial<APIConfigRequest>;
jsonPathKeys: string[];
cacheResponse?: string;
templateId: string;
proverId: string;
provider: ProviderInfo;
pluginId: string;
documentation: { text: string };
}
export interface ProviderInfo {
name: string;
imageUrl: string;
url: string;
description: string;
credentialSteps: string;
}
export type PaginationField = "PREV" | "NEXT"; export type PaginationField = "PREV" | "NEXT";
export interface ExecuteActionRequest extends APIRequest { export interface ExecuteActionRequest extends APIRequest {

View File

@ -63,12 +63,15 @@ export interface CreatePageResponse extends ApiResponse {
} }
export interface FetchPageListResponse extends ApiResponse { export interface FetchPageListResponse extends ApiResponse {
data: Array<{ data: {
id: string; pages: Array<{
name: string; id: string;
isDefault: boolean; name: string;
layouts: Array<PageLayout>; isDefault: boolean;
}>; layouts: Array<PageLayout>;
}>;
organizationId: string;
};
} }
export interface DeletePageRequest { export interface DeletePageRequest {

View File

@ -0,0 +1,3 @@
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 16H20V12H0V16ZM2 13H4V15H2V13ZM0 0V4H20V0H0ZM4 3H2V1H4V3ZM0 10H20V6H0V10ZM2 7H4V9H2V7Z" fill="#F4F4F4"/>
</svg>

After

Width:  |  Height:  |  Size: 220 B

View File

@ -1,7 +1,9 @@
import React from "react"; import React from "react";
import Creatable from "react-select/creatable"; import Select, { InputActionMeta } from "react-select";
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form"; import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
import { theme } from "constants/DefaultTheme"; import { theme } from "constants/DefaultTheme";
import { SelectComponents } from "react-select/src/components";
type DropdownProps = { type DropdownProps = {
options: Array<{ options: Array<{
@ -12,9 +14,12 @@ type DropdownProps = {
isLoading?: boolean; isLoading?: boolean;
input: WrappedFieldInputProps; input: WrappedFieldInputProps;
meta: WrappedFieldMetaProps; meta: WrappedFieldMetaProps;
components: SelectComponents<any>;
onCreateOption: (inputValue: string) => void; onCreateOption: (inputValue: string) => void;
formatCreateLabel?: (value: string) => React.ReactNode; formatCreateLabel?: (value: string) => React.ReactNode;
noOptionsMessage?: (obj: { inputValue: string }) => string; noOptionsMessage?: (obj: { inputValue: string }) => string;
inputValue?: string;
onInputChange: (value: string, actionMeta: InputActionMeta) => void;
}; };
const selectStyles = { const selectStyles = {
@ -22,7 +27,7 @@ const selectStyles = {
...provided, ...provided,
color: "#a3b3bf", color: "#a3b3bf",
}), }),
singleValue: (provided: any) => ({ multiValue: (provided: any) => ({
...provided, ...provided,
backgroundColor: "rgba(104,113,239,0.1)", backgroundColor: "rgba(104,113,239,0.1)",
border: "1px solid rgba(104, 113, 239, 0.5)", border: "1px solid rgba(104, 113, 239, 0.5)",
@ -34,9 +39,15 @@ const selectStyles = {
display: "inline-block", display: "inline-block",
transform: "none", transform: "none",
}), }),
multiValueRemove: () => {
return {
display: "none",
};
},
container: (styles: any) => ({ container: (styles: any) => ({
...styles, ...styles,
flex: 1, flex: 1,
zIndex: "5",
}), }),
control: (styles: any, state: any) => ({ control: (styles: any, state: any) => ({
...styles, ...styles,
@ -69,23 +80,34 @@ class CreatableDropdown extends React.Component<DropdownProps> {
placeholder, placeholder,
options, options,
isLoading, isLoading,
onCreateOption,
input, input,
formatCreateLabel,
noOptionsMessage, noOptionsMessage,
components,
inputValue,
onInputChange,
} = this.props; } = this.props;
const optionalProps: Partial<DropdownProps> = {}; const optionalProps: Partial<DropdownProps> = {};
if (formatCreateLabel) optionalProps.formatCreateLabel = formatCreateLabel;
if (noOptionsMessage) optionalProps.noOptionsMessage = noOptionsMessage; if (noOptionsMessage) optionalProps.noOptionsMessage = noOptionsMessage;
if (components) optionalProps.components = components;
if (inputValue) optionalProps.inputValue = inputValue;
if (onInputChange) optionalProps.onInputChange = onInputChange;
return ( return (
<Creatable <Select
isMulti
placeholder={placeholder} placeholder={placeholder}
options={options} options={options}
styles={selectStyles} styles={selectStyles}
isLoading={isLoading} isLoading={isLoading}
onCreateOption={onCreateOption}
{...input} {...input}
onChange={value => input.onChange(value)} onChange={value => {
const formattedValue = value;
if (formattedValue && formattedValue.length > 1) {
formattedValue.shift();
}
input.onChange(formattedValue);
}}
onBlur={() => input.value} onBlur={() => input.value}
isClearable isClearable
{...optionalProps} {...optionalProps}

View File

@ -0,0 +1,483 @@
import React from "react";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import Table from "./Table";
import { RenderMode, RenderModes } from "constants/WidgetConstants";
import _ from "lodash";
import { getMenuOptions, renderActions, renderCell } from "./TableUtilities";
interface ReactTableComponentState {
trigger: number;
columnIndex: number;
pageSize: number;
action: string;
columnName: string;
}
export interface ReactTableColumnProps {
Header: string;
accessor: string;
width: number;
minWidth: number;
draggable: boolean;
isHidden?: boolean;
Cell: (props: any) => JSX.Element;
}
export interface ColumnMenuOptionProps {
content: string | JSX.Element;
closeOnClick?: boolean;
isSelected?: boolean;
columnAccessor?: string;
id?: string;
category?: boolean;
options?: ColumnMenuSubOptionProps[];
onClick?: (isSelected: boolean) => void;
}
export interface ColumnMenuSubOptionProps {
content: string;
isSelected: boolean;
closeOnClick: boolean;
onClick: () => void;
}
interface ReactTableComponentProps {
widgetId: string;
isDisabled?: boolean;
isVisible?: boolean;
renderMode: RenderMode;
width: number;
height: number;
pageSize: number;
tableData: object[];
columnOrder?: string[];
disableDrag: (disable: boolean) => void;
onRowClick: (rowData: object, rowIndex: number) => void;
onCommandClick: (dynamicTrigger: string) => void;
updatePageNo: Function;
updateHiddenColumns: Function;
resetSelectedRowIndex: Function;
nextPageClick: Function;
prevPageClick: Function;
pageNo: number;
serverSidePaginationEnabled: boolean;
columnActions?: ColumnAction[];
selectedRowIndex: number;
hiddenColumns?: string[];
columnNameMap?: { [key: string]: string };
columnTypeMap?: {
[key: string]: {
type: string;
format: string;
};
};
columnSizeMap?: { [key: string]: number };
updateColumnType: Function;
updateColumnName: Function;
handleResizeColumn: Function;
handleReorderColumn: Function;
}
export class ReactTableComponent extends React.Component<
ReactTableComponentProps,
ReactTableComponentState
> {
private dragged = -1;
constructor(props: ReactTableComponentProps) {
super(props);
this.state = {
trigger: 0,
columnIndex: -1,
action: "",
columnName: "",
pageSize: props.pageSize,
};
}
componentDidMount() {
this.mountEvents();
}
componentDidUpdate(prevProps: ReactTableComponentProps) {
if (this.props.pageSize !== prevProps.pageSize) {
this.setState({ pageSize: this.props.pageSize });
}
this.mountEvents();
}
mountEvents() {
const headers = Array.prototype.slice.call(
document.querySelectorAll(
`#table${this.props.widgetId} .draggable-header`,
),
);
const columns = this.getTableColumns();
headers.forEach((header, i) => {
header.setAttribute("draggable", true);
header.ondragstart = (e: React.DragEvent<HTMLDivElement>) => {
header.style =
"background: #efefef; border-radius: 4px; z-index: 100; width: 100%; text-overflow: none; overflow: none;";
e.stopPropagation();
this.dragged = i;
};
header.ondrag = (e: React.DragEvent<HTMLDivElement>) => {
e.stopPropagation();
};
header.ondragend = (e: React.DragEvent<HTMLDivElement>) => {
header.style = "";
e.stopPropagation();
setTimeout(() => (this.dragged = -1), 1000);
};
// the dropped header
header.ondragover = (e: React.DragEvent<HTMLDivElement>) => {
if (i !== this.dragged && this.dragged !== -1) {
if (this.dragged > i) {
header.parentElement.className = "th header-reorder highlight-left";
} else if (this.dragged < i) {
header.parentElement.className =
"th header-reorder highlight-right";
}
}
e.preventDefault();
};
header.ondragenter = (e: React.DragEvent<HTMLDivElement>) => {
if (i !== this.dragged && this.dragged !== -1) {
if (this.dragged > i) {
header.parentElement.className = "th header-reorder highlight-left";
} else if (this.dragged < i) {
header.parentElement.className =
"th header-reorder highlight-right";
}
}
e.preventDefault();
};
header.ondragleave = (e: React.DragEvent<HTMLDivElement>) => {
header.parentElement.className = "th header-reorder";
e.preventDefault();
};
header.ondrop = (e: React.DragEvent<HTMLDivElement>) => {
header.style = "";
header.parentElement.className = "th header-reorder";
if (i !== this.dragged && this.dragged !== -1) {
e.preventDefault();
let columnOrder = this.props.columnOrder;
if (columnOrder === undefined) {
columnOrder = this.props.tableData.length
? Object.keys(this.props.tableData[0])
: [];
}
const draggedColumn = columns[this.dragged].accessor;
columnOrder.splice(this.dragged, 1);
columnOrder.splice(i, 0, draggedColumn);
this.props.handleReorderColumn(columnOrder);
this.setState({ trigger: Math.random() });
} else {
this.dragged = -1;
}
};
});
}
getTableColumns = () => {
const tableData: object[] = this.props.tableData;
let columns: ReactTableColumnProps[] = [];
const hiddenColumns: ReactTableColumnProps[] = [];
if (tableData.length) {
const row = tableData[0];
for (const i in row) {
const columnName: string =
this.props.columnNameMap && this.props.columnNameMap[i]
? this.props.columnNameMap[i]
: i;
const columnType: { type: string; format?: string } =
this.props.columnTypeMap && this.props.columnTypeMap[i]
? this.props.columnTypeMap[i]
: { type: "text" };
const columnSize: number =
this.props.columnSizeMap && this.props.columnSizeMap[i]
? this.props.columnSizeMap[i]
: 150;
const isHidden =
!!this.props.hiddenColumns && this.props.hiddenColumns.includes(i);
const columnData = {
Header: columnName,
accessor: i,
width: columnSize,
minWidth: 60,
draggable: true,
isHidden: false,
Cell: (props: any) => {
return renderCell(
props.cell.value,
columnType.type,
isHidden,
columnType.format,
);
},
};
if (isHidden) {
columnData.isHidden = true;
hiddenColumns.push(columnData);
} else {
columns.push(columnData);
}
}
columns = this.reorderColumns(columns);
if (this.props.columnActions?.length) {
columns.push({
Header: "Actions",
accessor: "actions",
width: 150,
minWidth: 60,
draggable: true,
Cell: () => {
return renderActions({
columnActions: this.props.columnActions,
onCommandClick: this.props.onCommandClick,
});
},
});
}
if (
hiddenColumns.length &&
this.props.renderMode === RenderModes.CANVAS
) {
columns = columns.concat(hiddenColumns);
}
}
return columns;
};
reorderColumns = (columns: ReactTableColumnProps[]) => {
const columnOrder = this.props.columnOrder || [];
const reorderedColumns = [];
const reorderedFlagMap: { [key: string]: boolean } = {};
for (let index = 0; index < columns.length; index++) {
const accessor = columnOrder[index];
if (accessor) {
const column = columns.filter((col: ReactTableColumnProps) => {
return col.accessor === accessor;
});
if (column.length && !reorderedFlagMap[column[0].accessor]) {
reorderedColumns.push(column[0]);
reorderedFlagMap[column[0].accessor] = true;
} else if (!reorderedFlagMap[columns[index].accessor]) {
reorderedColumns.push(columns[index]);
reorderedFlagMap[columns[index].accessor] = true;
}
} else if (!reorderedFlagMap[columns[index].accessor]) {
reorderedColumns.push(columns[index]);
reorderedFlagMap[columns[index].accessor] = true;
}
}
if (reorderedColumns.length < columns.length) {
for (let index = 0; index < columns.length; index++) {
if (!reorderedFlagMap[columns[index].accessor]) {
reorderedColumns.push(columns[index]);
reorderedFlagMap[columns[index].accessor] = true;
}
}
}
return reorderedColumns;
};
showMenu = (columnIndex: number) => {
this.setState({ columnIndex: columnIndex, action: "" });
};
getColumnMenu = () => {
const { columnIndex } = this.state;
let columnType = "";
let columnId = "";
let format = "";
if (columnIndex !== -1) {
const columns = this.getTableColumns();
const column = columns[columnIndex];
columnId = column.accessor;
columnType =
this.props.columnTypeMap && this.props.columnTypeMap[columnId]
? this.props.columnTypeMap[columnId].type
: "";
format =
this.props.columnTypeMap && this.props.columnTypeMap[columnId]
? this.props.columnTypeMap[columnId].format
: "";
}
const isColumnHidden = !!(
this.props.hiddenColumns && this.props.hiddenColumns.includes(columnId)
);
const columnMenuOptions: ColumnMenuOptionProps[] = getMenuOptions({
columnAccessor: columnId,
isColumnHidden,
columnType,
format,
hideColumn: this.hideColumn,
updateColumnType: this.updateColumnType,
handleUpdateCurrencySymbol: this.handleUpdateCurrencySymbol,
handleDateFormatUpdate: this.handleDateFormatUpdate,
updateAction: (action: string) => this.setState({ action: action }),
});
return columnMenuOptions;
};
hideColumn = (isColumnHidden: boolean) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
let hiddenColumns = this.props.hiddenColumns || [];
if (!isColumnHidden) {
hiddenColumns.push(column.accessor);
const columnOrder = this.props.columnOrder || [];
if (columnOrder.includes(column.accessor)) {
columnOrder.splice(columnOrder.indexOf(column.accessor), 1);
this.props.handleReorderColumn(columnOrder);
}
} else {
hiddenColumns = hiddenColumns.filter(item => {
return item !== column.accessor;
});
}
this.props.updateHiddenColumns(hiddenColumns);
this.setState({ columnIndex: -1 });
};
updateColumnType = (columnType: string) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
const columnTypeMap = this.props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: columnType,
format: "",
};
this.props.updateColumnType(columnTypeMap);
this.setState({ action: "", columnIndex: -1 });
};
onColumnNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ columnName: event.target.value });
};
onKeyPress = (key: string) => {
if (key === "Enter") {
this.handleColumnNameUpdate();
}
};
handleColumnNameUpdate = () => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const columnName = this.state.columnName;
const column = columns[columnIndex];
const columnNameMap = this.props.columnNameMap || {};
columnNameMap[column.accessor] = columnName;
this.props.updateColumnName(columnNameMap);
this.setState({
columnIndex: -1,
columnName: "",
action: "",
});
};
handleUpdateCurrencySymbol = (currencySymbol: string) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
const columnTypeMap = this.props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: "currency",
format: currencySymbol,
};
this.props.updateColumnType(columnTypeMap);
this.setState({
columnIndex: -1,
action: "",
});
};
handleDateFormatUpdate = (dateFormat: string) => {
const columns = this.getTableColumns();
const columnIndex = this.state.columnIndex;
const column = columns[columnIndex];
const columnTypeMap = this.props.columnTypeMap || {};
columnTypeMap[column.accessor] = {
type: "date",
format: dateFormat,
};
this.props.updateColumnType(columnTypeMap);
this.setState({
columnIndex: -1,
action: "",
});
};
handleResizeColumn = (columnIndex: number, columnWidth: string) => {
const columns = this.getTableColumns();
const column = columns[columnIndex];
const columnSizeMap = this.props.columnSizeMap || {};
const width = Number(columnWidth.split("px")[0]);
columnSizeMap[column.accessor] = width;
this.props.handleResizeColumn(columnSizeMap);
};
selectTableRow = (
row: { original: object; index: number },
isSelected: boolean,
) => {
if (!isSelected) {
this.props.onRowClick(row.original, row.index);
} else {
this.props.resetSelectedRowIndex();
}
};
render() {
const columns = this.getTableColumns();
return (
<Table
width={this.props.width}
height={this.props.height}
pageSize={this.state.pageSize || 1}
widgetId={this.props.widgetId}
columns={columns}
data={this.props.tableData}
showMenu={this.showMenu}
displayColumnActions={this.props.renderMode === RenderModes.CANVAS}
columnNameMap={this.props.columnNameMap}
columnMenuOptions={this.getColumnMenu()}
columnIndex={this.state.columnIndex}
columnAction={this.state.action}
onColumnNameChange={this.onColumnNameChange}
handleColumnNameUpdate={this.handleColumnNameUpdate}
handleResizeColumn={_.debounce(this.handleResizeColumn, 300)}
selectTableRow={this.selectTableRow}
pageNo={this.props.pageNo - 1}
updatePageNo={this.props.updatePageNo}
nextPageClick={() => {
this.props.nextPageClick();
}}
prevPageClick={() => {
this.props.prevPageClick();
}}
onKeyPress={this.onKeyPress}
serverSidePaginationEnabled={this.props.serverSidePaginationEnabled}
selectedRowIndex={this.props.selectedRowIndex}
disableDrag={() => {
this.props.disableDrag(true);
}}
enableDrag={() => {
this.props.disableDrag(false);
}}
/>
);
}
}
export default ReactTableComponent;

View File

@ -0,0 +1,274 @@
import React from "react";
import {
useTable,
usePagination,
useFlexLayout,
useResizeColumns,
useRowSelect,
} from "react-table";
import { Icon, InputGroup } from "@blueprintjs/core";
import {
TableWrapper,
PaginationWrapper,
PaginationItemWrapper,
} from "./TableStyledWrappers";
import {
ReactTableColumnProps,
ColumnMenuOptionProps,
} from "./ReactTableComponent";
import { TableColumnMenuPopup } from "./TableColumnMenu";
interface TableProps {
width: number;
height: number;
pageSize: number;
widgetId: string;
columns: ReactTableColumnProps[];
data: object[];
showMenu: (columnIndex: number) => void;
displayColumnActions: boolean;
columnNameMap?: { [key: string]: string };
columnMenuOptions: ColumnMenuOptionProps[];
columnIndex: number;
columnAction: string;
onColumnNameChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
handleColumnNameUpdate: () => void;
handleResizeColumn: Function;
selectTableRow: (
row: { original: object; index: number },
isSelected: boolean,
) => void;
pageNo: number;
updatePageNo: Function;
nextPageClick: () => void;
prevPageClick: () => void;
onKeyPress: (key: string) => void;
serverSidePaginationEnabled: boolean;
selectedRowIndex: number;
disableDrag: () => void;
enableDrag: () => void;
}
export const Table = (props: TableProps) => {
const { data, columns } = props;
const defaultColumn = React.useMemo(
() => ({
minWidth: 30,
width: 150,
maxWidth: 400,
}),
[],
);
const pageCount = Math.ceil(data.length / props.pageSize);
const currentPageIndex = props.pageNo < pageCount ? props.pageNo : 0;
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
pageOptions,
} = useTable(
{
columns,
data,
defaultColumn,
initialState: {
pageIndex: currentPageIndex,
pageSize: props.pageSize,
},
manualPagination: true,
pageCount,
},
useFlexLayout,
useResizeColumns,
usePagination,
useRowSelect,
);
let startIndex = currentPageIndex * props.pageSize;
let endIndex = startIndex + props.pageSize;
if (props.serverSidePaginationEnabled) {
startIndex = 0;
endIndex = data.length;
}
const subPage = page.slice(startIndex, endIndex);
const selectedRowIndex = props.selectedRowIndex;
return (
<TableWrapper
width={props.width}
height={props.height}
id={`table${props.widgetId}`}
>
<div className="tableWrap">
<div {...getTableProps()} className="table">
<div onMouseOver={props.disableDrag} onMouseLeave={props.enableDrag}>
{headerGroups.map((headerGroup: any, index: number) => (
<div
key={index}
{...headerGroup.getHeaderGroupProps()}
className="tr"
>
{headerGroup.headers.map((column: any, columnIndex: number) => {
if (column.isResizing) {
props.handleResizeColumn(
columnIndex,
column.getHeaderProps().style.width,
);
}
return (
<div
key={columnIndex}
{...column.getHeaderProps()}
className="th header-reorder"
>
{props.columnIndex === columnIndex &&
props.columnAction === "rename_column" && (
<InputGroup
placeholder="Enter Column Name"
onChange={props.onColumnNameChange}
onKeyPress={event => props.onKeyPress(event.key)}
type="text"
defaultValue={
props.columnNameMap &&
props.columnNameMap[column.id]
? props.columnNameMap[column.id]
: column.id
}
className="input-group"
onBlur={() => props.handleColumnNameUpdate()}
/>
)}
{(props.columnIndex !== columnIndex ||
(props.columnIndex === columnIndex &&
"rename_column" !== props.columnAction)) && (
<div
className={
!column.isHidden
? "draggable-header"
: "hidden-header"
}
>
{column.render("Header")}
</div>
)}
{props.displayColumnActions && (
<div className="column-menu">
<TableColumnMenuPopup
showMenu={props.showMenu}
columnMenuOptions={props.columnMenuOptions}
columnIndex={columnIndex}
/>
</div>
)}
<div
{...column.getResizerProps()}
className={`resizer ${
column.isResizing ? "isResizing" : ""
}`}
/>
</div>
);
})}
</div>
))}
</div>
<div {...getTableBodyProps()} className="tbody">
{subPage.map((row, index) => {
prepareRow(row);
return (
<div
key={index}
{...row.getRowProps()}
className={
"tr" +
`${row.index === selectedRowIndex ? " selected-row" : ""}`
}
onClick={() => {
row.toggleRowSelected();
props.selectTableRow(row, row.index === selectedRowIndex);
}}
>
{row.cells.map((cell, cellIndex) => {
return (
<div
key={cellIndex}
{...cell.getCellProps()}
className="td"
data-rowindex={index}
data-colindex={cellIndex}
>
{cell.render("Cell")}
</div>
);
})}
</div>
);
})}
</div>
</div>
</div>
{props.serverSidePaginationEnabled && (
<PaginationWrapper>
<PaginationItemWrapper
disabled={false}
onClick={() => {
props.prevPageClick();
}}
>
<Icon icon="chevron-left" iconSize={16} color="#A1ACB3" />
</PaginationItemWrapper>
<PaginationItemWrapper selected className="page-item">
{props.pageNo + 1}
</PaginationItemWrapper>
<PaginationItemWrapper
disabled={false}
onClick={() => {
props.nextPageClick();
}}
>
<Icon icon="chevron-right" iconSize={16} color="#A1ACB3" />
</PaginationItemWrapper>
</PaginationWrapper>
)}
{!props.serverSidePaginationEnabled && (
<PaginationWrapper>
<PaginationItemWrapper
disabled={currentPageIndex === 0}
onClick={() => {
const pageNo = currentPageIndex > 0 ? currentPageIndex - 1 : 0;
props.updatePageNo(pageNo + 1);
}}
>
<Icon icon="chevron-left" iconSize={16} color="#A1ACB3" />
</PaginationItemWrapper>
{pageOptions.map((pageNumber: number, index: number) => {
return (
<PaginationItemWrapper
key={index}
selected={pageNumber === currentPageIndex}
onClick={() => {
props.updatePageNo(pageNumber + 1);
}}
className="page-item"
>
{index + 1}
</PaginationItemWrapper>
);
})}
<PaginationItemWrapper
disabled={currentPageIndex === pageCount - 1}
onClick={() => {
const pageNo =
currentPageIndex < pageCount - 1 ? currentPageIndex + 1 : 0;
props.updatePageNo(pageNo + 1);
}}
>
<Icon icon="chevron-right" iconSize={16} color="#A1ACB3" />
</PaginationItemWrapper>
</PaginationWrapper>
)}
</TableWrapper>
);
};
export default Table;

View File

@ -0,0 +1,96 @@
import React from "react";
import {
Popover,
Classes,
PopoverInteractionKind,
Icon,
Position,
} from "@blueprintjs/core";
import {
DropDownWrapper,
OptionWrapper,
IconOptionWrapper,
} from "./TableStyledWrappers";
import {
ColumnMenuSubOptionProps,
ColumnMenuOptionProps,
} from "./ReactTableComponent";
interface TableColumnMenuPopup {
showMenu: (index: number) => void;
columnIndex: number;
columnMenuOptions: ColumnMenuOptionProps[];
}
export const TableColumnMenuPopup = (props: TableColumnMenuPopup) => {
return (
<Popover
minimal
usePortal
enforceFocus={false}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM}
>
<Icon
icon="more"
iconSize={12}
color="#A1ACB3"
onClick={e => {
props.showMenu(props.columnIndex);
}}
/>
<DropDownWrapper>
{props.columnMenuOptions.map(
(option: ColumnMenuOptionProps, index: number) => (
<OptionWrapper
key={index}
onClick={() => {
if (option.onClick) {
option.onClick(!!option.isSelected);
}
}}
className={
option.closeOnClick
? Classes.POPOVER_DISMISS
: option.category
? "non-selectable"
: ""
}
selected={!!option.isSelected}
>
{!option.options && <div>{option.content}</div>}
{option.options && (
<Popover
minimal
usePortal
enforceFocus={false}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_RIGHT}
className="column-type"
>
<IconOptionWrapper>{option.content}</IconOptionWrapper>
<DropDownWrapper>
{option.options.map(
(item: ColumnMenuSubOptionProps, itemIndex: number) => (
<OptionWrapper
key={itemIndex}
onClick={item.onClick}
className={
item.closeOnClick ? Classes.POPOVER_DISMISS : ""
}
selected={!!item.isSelected}
>
{item.content}
</OptionWrapper>
),
)}
</DropDownWrapper>
</Popover>
)}
</OptionWrapper>
),
)}
</DropDownWrapper>
</Popover>
);
};

View File

@ -0,0 +1,254 @@
import styled from "styled-components";
import { Colors } from "constants/Colors";
export const TableWrapper = styled.div<{ width: number; height: number }>`
width: ${props => props.width - 5}px;
height: ${props => props.height - 5}px;
background: white;
border: 1px solid ${Colors.GEYSER_LIGHT};
box-sizing: border-box;
display: flex;
justify-content: space-between;
flex-direction: column;
.tableWrap {
display: block;
overflow-x: auto;
overflow-y: hidden;
border-bottom: 1px solid ${Colors.GEYSER_LIGHT};
}
.table {
border-spacing: 0;
color: ${Colors.BLUE_BAYOUX};
position: relative;
.thead {
overflow-y: auto;
overflow-x: hidden;
}
.tbody {
overflow: scroll;
height: ${props => props.height - 5 - 102}px;
}
.tr {
:last-child {
.td {
border-bottom: 0;
}
}
:nth-child(even) {
background: ${Colors.ATHENS_GRAY_DARKER};
}
&.selected-row {
background: ${Colors.ATHENS_GRAY};
}
&:hover {
background: ${Colors.ATHENS_GRAY};
}
}
.th,
.td {
margin: 0;
padding: 9px 10px;
border-bottom: 1px solid ${Colors.GEYSER_LIGHT};
border-right: 1px solid ${Colors.GEYSER_LIGHT};
position: relative;
:last-child {
border-right: 0;
}
.resizer {
display: inline-block;
width: 10px;
height: 100%;
position: absolute;
right: 0;
top: 0;
transform: translateX(50%);
z-index: 1;
${"" /* prevents from scrolling while dragging on touch devices */}
touch-action:none;
&.isResizing {
cursor: isResizing;
}
}
}
.th {
padding: 0 10px 0 0;
height: 52px;
line-height: 52px;
background: ${Colors.ATHENS_GRAY_DARKER};
}
.td {
height: 52px;
line-height: 52px;
padding: 0 10px;
}
}
.draggable-header,
.hidden-header {
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
color: ${Colors.OXFORD_BLUE};
font-weight: 500;
padding-left: 10px;
}
.draggable-header {
cursor: pointer;
&.reorder-line {
width: 1px;
height: 100%;
}
}
.hidden-header {
opacity: 0.6;
}
.column-menu {
cursor: pointer;
height: 52px;
line-height: 52px;
}
.th {
display: flex;
justify-content: space-between;
&.highlight-left {
border-left: 2px solid ${Colors.GREEN};
}
&.highlight-right {
border-right: 2px solid ${Colors.GREEN};
}
}
.input-group {
height: 52px;
line-height: 52px;
padding: 0 5px;
}
`;
export const DropDownWrapper = styled.div`
display: flex;
flex-direction: column;
background: white;
z-index: 1;
padding: 10px;
border-radius: 4px;
border: 1px solid ${Colors.ATHENS_GRAY};
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
`;
export const OptionWrapper = styled.div<{ selected: boolean }>`
display: flex;
width: 100%;
justify-content: space-between;
height: 32px;
box-sizing: border-box;
padding: 8px;
color: ${props => (props.selected ? Colors.WHITE : Colors.OXFORD_BLUE)};
font-size: 14px;
min-width: 200px;
cursor: pointer;
border-radius: 4px;
margin: 3px 0;
background: ${props => (props.selected ? Colors.GREEN : Colors.WHITE)};
&:hover {
background: ${props => (props.selected ? Colors.GREEN : Colors.POLAR)};
}
.column-type {
width: 100%;
}
&.non-selectable {
color: ${Colors.GRAY};
pointer-events: none;
}
`;
export const IconOptionWrapper = styled.div`
display: flex;
justify-content: space-between;
width: 100%;
cursor: pointer;
`;
export const PaginationWrapper = styled.div`
box-sizing: border-box;
padding: 10px;
display: flex;
width: 100%;
justify-content: flex-end;
align-items: center;
`;
export const PaginationItemWrapper = styled.div<{
disabled?: boolean;
selected?: boolean;
}>`
background: ${props => (props.disabled ? Colors.ATHENS_GRAY : Colors.WHITE)};
border: 1px solid
${props => (props.selected ? Colors.GREEN : Colors.GEYSER_LIGHT)};
box-sizing: border-box;
border-radius: 4px;
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
margin: 0 0 0 8px;
pointer-events: ${props => props.disabled && "none"};
cursor: pointer;
&:hover {
border-color: ${Colors.GREEN};
}
`;
export const MenuColumnWrapper = styled.div<{ selected: boolean }>`
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
background: ${props => props.selected && Colors.GREEN};
position: relative;
.title {
color: ${props => (props.selected ? Colors.WHITE : Colors.OXFORD_BLUE)};
margin-left: 10px;
}
.sub-menu {
position: absolute;
right: 0;
}
`;
export const ActionWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
margin: 10px 5px 0 0;
cursor: pointer;
padding: 5px;
height: 32px;
color: ${Colors.WHITE};
background: ${Colors.GREEN};
border-radius: 4px;
letter-spacing: -0.03em;
font-weight: bold;
`;
export const CellWrapper = styled.div<{ isHidden: boolean }>`
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
opacity: ${props => (props.isHidden ? "0.6" : "1")};
.image-cell {
width: 40px;
height: 32px;
margin: 0 5px 0 0;
border-radius: 4px;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
}
video {
border-radius: 4px;
}
`;

View File

@ -0,0 +1,384 @@
import React from "react";
import { Icon } from "@blueprintjs/core";
import moment from "moment-timezone";
import {
MenuColumnWrapper,
CellWrapper,
ActionWrapper,
} from "./TableStyledWrappers";
import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl";
import { ColumnMenuOptionProps } from "./ReactTableComponent";
interface MenuOptionProps {
columnAccessor?: string;
isColumnHidden: boolean;
columnType: string;
format?: string;
hideColumn: (isColumnHidden: boolean) => void;
updateAction: (action: string) => void;
updateColumnType: (columnType: string) => void;
handleUpdateCurrencySymbol: (currencySymbol: string) => void;
handleDateFormatUpdate: (dateFormat: string) => void;
}
export const getMenuOptions = (props: MenuOptionProps) => {
const basicOptions: ColumnMenuOptionProps[] = [
{
content: "Rename a Column",
closeOnClick: true,
id: "rename_column",
onClick: () => {
props.updateAction("rename_column");
},
},
{
content: props.isColumnHidden ? "Show Column" : "Hide Column",
closeOnClick: true,
id: "hide_column",
onClick: () => {
props.hideColumn(props.isColumnHidden);
},
},
];
if (props.columnAccessor && props.columnAccessor === "actions") {
return basicOptions;
}
const columnMenuOptions: ColumnMenuOptionProps[] = [
...basicOptions,
{
content: "Select a Data Type",
id: "change_column_type",
category: true,
},
{
content: (
<MenuColumnWrapper selected={props.columnType === "image"}>
<Icon
icon="media"
iconSize={12}
color={props.columnType === "image" ? "#ffffff" : "#2E3D49"}
/>
<div className="title">Image</div>
</MenuColumnWrapper>
),
closeOnClick: true,
isSelected: props.columnType === "image",
onClick: (isSelected: boolean) => {
if (isSelected) {
props.updateColumnType("");
} else {
props.updateColumnType("image");
}
},
},
{
content: (
<MenuColumnWrapper selected={props.columnType === "video"}>
<Icon
icon="video"
iconSize={12}
color={props.columnType === "video" ? "#ffffff" : "#2E3D49"}
/>
<div className="title">Video</div>
</MenuColumnWrapper>
),
isSelected: props.columnType === "video",
closeOnClick: true,
onClick: (isSelected: boolean) => {
if (isSelected) {
props.updateColumnType("");
} else {
props.updateColumnType("video");
}
},
},
{
content: (
<MenuColumnWrapper selected={props.columnType === "text"}>
<Icon
icon="label"
iconSize={12}
color={props.columnType === "text" ? "#ffffff" : "#2E3D49"}
/>
<div className="title">Text</div>
</MenuColumnWrapper>
),
closeOnClick: true,
isSelected: props.columnType === "text",
onClick: (isSelected: boolean) => {
if (isSelected) {
props.updateColumnType("");
} else {
props.updateColumnType("text");
}
},
},
{
content: (
<MenuColumnWrapper selected={props.columnType === "currency"}>
<Icon
icon="dollar"
iconSize={12}
color={props.columnType === "currency" ? "#ffffff" : "#2E3D49"}
/>
<div className="title">Currency</div>
<Icon
className="sub-menu"
icon="chevron-right"
iconSize={16}
color={props.columnType === "currency" ? "#ffffff" : "#2E3D49"}
/>
</MenuColumnWrapper>
),
closeOnClick: false,
isSelected: props.columnType === "currency",
options: [
{
content: "USD - $",
isSelected: props.format === "$",
closeOnClick: true,
onClick: () => {
props.handleUpdateCurrencySymbol("$");
},
},
{
content: "INR - ₹",
isSelected: props.format === "₹",
closeOnClick: true,
onClick: () => {
props.handleUpdateCurrencySymbol("₹");
},
},
{
content: "GBP - £",
isSelected: props.format === "£",
closeOnClick: true,
onClick: () => {
props.handleUpdateCurrencySymbol("£");
},
},
{
content: "AUD - A$",
isSelected: props.format === "A$",
closeOnClick: true,
onClick: () => {
props.handleUpdateCurrencySymbol("A$");
},
},
{
content: "EUR - €",
isSelected: props.format === "€",
closeOnClick: true,
onClick: () => {
props.handleUpdateCurrencySymbol("€");
},
},
{
content: "SGD - S$",
isSelected: props.format === "S$",
closeOnClick: true,
onClick: () => {
props.handleUpdateCurrencySymbol("S$");
},
},
{
content: "CAD - C$",
isSelected: props.format === "C$",
closeOnClick: true,
onClick: () => {
props.handleUpdateCurrencySymbol("C$");
},
},
],
},
{
content: (
<MenuColumnWrapper selected={props.columnType === "date"}>
<Icon
icon="calendar"
iconSize={12}
color={props.columnType === "date" ? "#ffffff" : "#2E3D49"}
/>
<div className="title">Date</div>
<Icon
className="sub-menu"
icon="chevron-right"
iconSize={16}
color={props.columnType === "date" ? "#ffffff" : "#2E3D49"}
/>
</MenuColumnWrapper>
),
closeOnClick: false,
isSelected: props.columnType === "date",
options: [
{
content: "MM-DD-YY",
isSelected: props.format === "MM-DD-YY",
closeOnClick: true,
onClick: () => {
props.handleDateFormatUpdate("MM-DD-YY");
},
},
{
content: "DD-MM-YY",
isSelected: props.format === "DD-MM-YY",
closeOnClick: true,
onClick: () => {
props.handleDateFormatUpdate("DD-MM-YY");
},
},
{
content: "DD/MM/YY",
isSelected: props.format === "DD/MM/YY",
closeOnClick: true,
onClick: () => {
props.handleDateFormatUpdate("DD/MM/YY");
},
},
{
content: "MM/DD/YY",
isSelected: props.format === "MM/DD/YY",
closeOnClick: true,
onClick: () => {
props.handleDateFormatUpdate("MM/DD/YY");
},
},
],
},
{
content: (
<MenuColumnWrapper selected={props.columnType === "time"}>
<Icon
icon="time"
iconSize={12}
color={props.columnType === "time" ? "#ffffff" : "#2E3D49"}
/>
<div className="title">Time</div>
</MenuColumnWrapper>
),
closeOnClick: true,
isSelected: props.columnType === "time",
onClick: (isSelected: boolean) => {
if (isSelected) {
props.updateColumnType("");
} else {
props.updateColumnType("time");
}
},
},
];
return columnMenuOptions;
};
export const renderCell = (
value: any,
columnType: string,
isHidden: boolean,
format?: string,
) => {
if (!value) {
return <div></div>;
}
switch (columnType) {
case "image":
return (
<CellWrapper isHidden={isHidden}>
{value
.toString()
.split(",")
.map((item: string, index: number) => {
if (item.match(/\.(jpeg|jpg|gif|png)$/)) {
return (
<div
key={index}
className="image-cell"
style={{ backgroundImage: `url("${item}")` }}
/>
);
} else {
return <div>Invalid Image</div>;
}
})}
</CellWrapper>
);
case "video":
return (
<CellWrapper isHidden={isHidden}>
<video width="56" height="32" autoPlay={false}>
<source src={`${value}`} type="video/mp4" />
</video>
</CellWrapper>
);
case "currency":
if (!isNaN(value)) {
return (
<CellWrapper isHidden={isHidden}>{`${format}${value}`}</CellWrapper>
);
} else {
return <CellWrapper isHidden={isHidden}>Invalid Value</CellWrapper>;
}
case "date":
let isValidDate = true;
if (isNaN(value)) {
const dateTime = Date.parse(value);
if (isNaN(dateTime)) {
isValidDate = false;
}
}
if (isValidDate) {
return (
<CellWrapper isHidden={isHidden}>
{moment(value).format(format)}
</CellWrapper>
);
} else {
return <CellWrapper isHidden={isHidden}>Invalid Date</CellWrapper>;
}
case "time":
let isValidTime = true;
if (isNaN(value)) {
const time = Date.parse(value);
if (isNaN(time)) {
isValidTime = false;
}
}
if (isValidTime) {
return (
<CellWrapper isHidden={isHidden}>
{moment(value).format("HH:mm")}
</CellWrapper>
);
} else {
return <CellWrapper isHidden={isHidden}>Invalid Time</CellWrapper>;
}
case "text":
return <CellWrapper isHidden={isHidden}>{value}</CellWrapper>;
default:
return <CellWrapper isHidden={isHidden}>{value}</CellWrapper>;
}
};
interface RenderActionProps {
columnActions?: ColumnAction[];
onCommandClick: (dynamicTrigger: string) => void;
}
export const renderActions = (props: RenderActionProps) => {
if (!props.columnActions) return <CellWrapper isHidden={false}></CellWrapper>;
return (
<CellWrapper isHidden={false}>
{props.columnActions.map((action: ColumnAction, index: number) => {
return (
<ActionWrapper
key={index}
onClick={() => {
props.onCommandClick(action.dynamicTrigger);
}}
>
{action.label}
</ActionWrapper>
);
})}
</CellWrapper>
);
};

View File

@ -1,6 +1,5 @@
import React, { useContext } from "react"; import React, { useContext } from "react";
import DocumentationSearch from "components/designSystems/appsmith/help/DocumentationSearch"; import DocumentationSearch from "components/designSystems/appsmith/help/DocumentationSearch";
import Button from "components/editorComponents/Button";
import { useSelector } from "store"; import { useSelector } from "store";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
@ -13,8 +12,7 @@ import {
setHelpModalVisibility, setHelpModalVisibility,
} from "actions/helpActions"; } from "actions/helpActions";
import styled from "styled-components"; import styled from "styled-components";
import { IntentColors, theme } from "constants/DefaultTheme"; import { theme } from "constants/DefaultTheme";
import { Position } from "@blueprintjs/core";
import ModalComponent from "components/designSystems/blueprint/ModalComponent"; import ModalComponent from "components/designSystems/blueprint/ModalComponent";
import { LayersContext } from "constants/Layers"; import { LayersContext } from "constants/Layers";
import { HelpIcons } from "icons/HelpIcons"; import { HelpIcons } from "icons/HelpIcons";
@ -87,7 +85,7 @@ export function HelpModal() {
dispatch(setHelpModalVisibility(!helpModalOpen)); dispatch(setHelpModalVisibility(!helpModalOpen));
}} }}
> >
<HelpIcon></HelpIcon> <HelpIcon />
</HelpButton> </HelpButton>
</> </>
); );

View File

@ -9,11 +9,11 @@ import { AppState } from "reducers";
import { ActionResponse } from "api/ActionAPI"; import { ActionResponse } from "api/ActionAPI";
import { formatBytes } from "utils/helpers"; import { formatBytes } from "utils/helpers";
import { APIEditorRouteParams } from "constants/routes"; import { APIEditorRouteParams } from "constants/routes";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen"; import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
import CodeEditor from "components/editorComponents/CodeEditor"; import CodeEditor from "components/editorComponents/CodeEditor";
import { getActionResponses } from "selectors/entitiesSelector"; import { getActionResponses } from "selectors/entitiesSelector";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import _ from "lodash";
import FormActionButton from "./form/FormActionButton"; import FormActionButton from "./form/FormActionButton";
const ResponseWrapper = styled.div` const ResponseWrapper = styled.div`
@ -53,7 +53,7 @@ const TableWrapper = styled.div`
interface ReduxStateProps { interface ReduxStateProps {
responses: Record<string, ActionResponse | undefined>; responses: Record<string, ActionResponse | undefined>;
apiPane: ApiPaneReduxState; isRunning: Record<string, boolean>;
} }
const ResponseHeadersView = (props: { data: Record<string, string[]> }) => { const ResponseHeadersView = (props: { data: Record<string, string[]> }) => {
@ -109,14 +109,13 @@ const ApiResponseView = (props: Props) => {
params: { apiId }, params: { apiId },
}, },
responses, responses,
apiPane,
} = props; } = props;
let response: ActionResponse = EMPTY_RESPONSE; let response: ActionResponse = EMPTY_RESPONSE;
let isRunning = false; let isRunning = false;
let hasFailed = false; let hasFailed = false;
if (apiId && apiId in responses) { if (apiId && apiId in responses) {
response = responses[apiId] || EMPTY_RESPONSE; response = responses[apiId] || EMPTY_RESPONSE;
isRunning = apiPane.isRunning[apiId]; isRunning = props.isRunning[apiId];
hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false; hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false;
} }
@ -135,7 +134,7 @@ const ApiResponseView = (props: Props) => {
onClick={() => { onClick={() => {
setSelectedIndex(3); setSelectedIndex(3);
}} }}
></FormActionButton> />
)} )}
</FailedMessageContainer> </FailedMessageContainer>
<CodeEditor <CodeEditor
@ -165,9 +164,9 @@ const ApiResponseView = (props: Props) => {
panelComponent: ( panelComponent: (
<CodeEditor <CodeEditor
input={{ input={{
value: response.requestBody value: _.isObject(response.requestBody)
? JSON.stringify(response.requestBody, null, 2) ? JSON.stringify(response.requestBody, null, 2)
: "", : response.requestBody || "",
}} }}
height={700} height={700}
/> />
@ -217,7 +216,7 @@ const ApiResponseView = (props: Props) => {
const mapStateToProps = (state: AppState): ReduxStateProps => { const mapStateToProps = (state: AppState): ReduxStateProps => {
return { return {
responses: getActionResponses(state), responses: getActionResponses(state),
apiPane: state.ui.apiPane, isRunning: state.ui.apiPane.isRunning,
}; };
}; };

View File

@ -13,11 +13,11 @@ import "codemirror/addon/mode/multiplex";
import "codemirror/addon/tern/tern.css"; import "codemirror/addon/tern/tern.css";
import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors"; import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants"; import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants";
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
import HelperTooltip from "components/editorComponents/HelperTooltip"; import HelperTooltip from "components/editorComponents/HelperTooltip";
import EvaluatedValuePopup from "components/editorComponents/EvaluatedValuePopup";
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form"; import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
import _ from "lodash"; import _ from "lodash";
import { parseDynamicString } from "utils/DynamicBindingUtils"; import { getDynamicStringSegments } from "utils/DynamicBindingUtils";
import { DataTree } from "entities/DataTree/dataTreeFactory"; import { DataTree } from "entities/DataTree/dataTreeFactory";
import { Theme } from "constants/DefaultTheme"; import { Theme } from "constants/DefaultTheme";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
@ -63,7 +63,7 @@ CodeMirror.defineMode("js-js", function(config) {
const getBorderStyle = ( const getBorderStyle = (
props: { theme: Theme } & { props: { theme: Theme } & {
editorTheme?: THEME; editorTheme?: EditorTheme;
hasError: boolean; hasError: boolean;
singleLine: boolean; singleLine: boolean;
isFocused: boolean; isFocused: boolean;
@ -78,7 +78,7 @@ const getBorderStyle = (
return "transparent"; return "transparent";
}; };
const HintStyles = createGlobalStyle` const HintStyles = createGlobalStyle<{ editorTheme: EditorTheme }>`
.CodeMirror-hints { .CodeMirror-hints {
position: absolute; position: absolute;
z-index: 20; z-index: 20;
@ -91,8 +91,11 @@ const HintStyles = createGlobalStyle`
max-height: 20em; max-height: 20em;
width: 200px; width: 200px;
overflow-y: auto; overflow-y: auto;
background: #FFFFFF; background: ${props =>
border: 1px solid #EBEFF2; props.editorTheme === "DARK" ? "#090A0F" : "#ffffff"};
border: 1px solid;
border-color: ${props =>
props.editorTheme === "DARK" ? "#535B62" : "#EBEFF2"}
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14); box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
border-radius: 4px; border-radius: 4px;
} }
@ -102,7 +105,7 @@ const HintStyles = createGlobalStyle`
padding: 3px; padding: 3px;
margin: 0; margin: 0;
white-space: pre; white-space: pre;
color: #2E3D49; color: ${props => (props.editorTheme === "DARK" ? "#F4F4F4" : "#1E242B")};
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
@ -110,7 +113,10 @@ const HintStyles = createGlobalStyle`
} }
li.CodeMirror-hint-active { li.CodeMirror-hint-active {
background: #E9FAF3; background: ${props =>
props.editorTheme === "DARK"
? "rgba(244,244,244,0.1)"
: "rgba(128,136,141,0.1)"};
border-radius: 4px; border-radius: 4px;
} }
.CodeMirror-Tern-completion { .CodeMirror-Tern-completion {
@ -121,10 +127,30 @@ const HintStyles = createGlobalStyle`
bottom: 7px !important; bottom: 7px !important;
line-height: 15px !important; line-height: 15px !important;
} }
.CodeMirror-Tern-tooltip {
z-index: 20 !important;
}
.CodeMirror-Tern-hint-doc {
background-color: ${props =>
props.editorTheme === "DARK" ? "#23292e" : "#fff"} !important;
color: ${props =>
props.editorTheme === "DARK" ? "#F4F4F4" : "#1E242B"} !important;
max-height: 150px;
width: 250px;
padding: 12px !important;
border: 1px solid #DEDEDE !important;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.12) !important;
}
`; `;
const Wrapper = styled.div<{ const Wrapper = styled.div`
editorTheme?: THEME; position: relative;
flex: 1;
height: 100%;
`;
const EditorWrapper = styled.div<{
editorTheme?: EditorTheme;
hasError: boolean; hasError: boolean;
singleLine: boolean; singleLine: boolean;
isFocused: boolean; isFocused: boolean;
@ -245,12 +271,12 @@ const IconContainer = styled.div`
} }
`; `;
const THEMES = { const THEMES: Record<string, EditorTheme> = {
LIGHT: "LIGHT", LIGHT: "LIGHT",
DARK: "DARK", DARK: "DARK",
}; };
type THEME = "LIGHT" | "DARK"; export type EditorTheme = "LIGHT" | "DARK";
const AUTOCOMPLETE_CLOSE_KEY_CODES = ["Enter", "Tab", "Escape"]; const AUTOCOMPLETE_CLOSE_KEY_CODES = ["Enter", "Tab", "Escape"];
@ -264,7 +290,7 @@ export type DynamicAutocompleteInputProps = {
rightIcon?: Function; rightIcon?: Function;
description?: string; description?: string;
height?: number; height?: number;
theme?: THEME; theme?: EditorTheme;
meta?: Partial<WrappedFieldMetaProps>; meta?: Partial<WrappedFieldMetaProps>;
showLineNumbers?: boolean; showLineNumbers?: boolean;
allowTabIndent?: boolean; allowTabIndent?: boolean;
@ -276,6 +302,8 @@ export type DynamicAutocompleteInputProps = {
link?: string; link?: string;
baseMode?: string | object; baseMode?: string | object;
setMaxHeight?: boolean; setMaxHeight?: boolean;
dataTreePath?: string;
expected?: string;
}; };
type Props = ReduxStateProps & type Props = ReduxStateProps &
@ -316,7 +344,7 @@ class DynamicAutocompleteInput extends Component<Props, State> {
mode: this.props.mode || { name: "javascript", globalVars: true }, mode: this.props.mode || { name: "javascript", globalVars: true },
viewportMargin: 10, viewportMargin: 10,
tabSize: 2, tabSize: 2,
indentWithTabs: true, indentWithTabs: !!this.props.allowTabIndent,
lineWrapping: !this.props.singleLine, lineWrapping: !this.props.singleLine,
extraKeys, extraKeys,
autoCloseBrackets: true, autoCloseBrackets: true,
@ -324,6 +352,7 @@ class DynamicAutocompleteInput extends Component<Props, State> {
}); });
this.editor.on("change", _.debounce(this.handleChange, 300)); this.editor.on("change", _.debounce(this.handleChange, 300));
this.editor.on("change", this.handleAutocompleteVisibility);
this.editor.on("keyup", this.handleAutocompleteHide); this.editor.on("keyup", this.handleAutocompleteHide);
this.editor.on("focus", this.handleEditorFocus); this.editor.on("focus", this.handleEditorFocus);
this.editor.on("blur", this.handleEditorBlur); this.editor.on("blur", this.handleEditorBlur);
@ -409,7 +438,6 @@ class DynamicAutocompleteInput extends Component<Props, State> {
globalScope: this.props.dynamicData, globalScope: this.props.dynamicData,
}); });
} }
this.editor.on("cursorActivity", this.handleAutocompleteVisibility);
} }
handleEditorFocus = () => { handleEditorFocus = () => {
@ -447,20 +475,30 @@ class DynamicAutocompleteInput extends Component<Props, State> {
let cursorBetweenBinding = false; let cursorBetweenBinding = false;
const cursor = this.editor.getCursor(); const cursor = this.editor.getCursor();
const value = this.editor.getValue(); const value = this.editor.getValue();
let cursorIndex = cursor.ch;
if (cursor.line > 0) {
for (let lineIndex = 0; lineIndex < cursor.line; lineIndex++) {
const line = this.editor.getLine(lineIndex);
// Add line length + 1 for new line character
cursorIndex = cursorIndex + line.length + 1;
}
}
const stringSegments = getDynamicStringSegments(value);
// count of chars processed
let cumulativeCharCount = 0; let cumulativeCharCount = 0;
parseDynamicString(value).forEach(segment => { stringSegments.forEach((segment: string) => {
const start = cumulativeCharCount; const start = cumulativeCharCount;
const dynamicStart = segment.indexOf("{{"); const dynamicStart = segment.indexOf("{{");
const dynamicDoesStart = dynamicStart > -1; const dynamicDoesStart = dynamicStart > -1;
const dynamicEnd = segment.indexOf("}}"); const dynamicEnd = segment.indexOf("}}");
const dynamicDoesEnd = dynamicEnd > -1; const dynamicDoesEnd = dynamicEnd > -1;
const dynamicStartIndex = dynamicStart + start + 1; const dynamicStartIndex = dynamicStart + start + 2;
const dynamicEndIndex = dynamicEnd + start + 1; const dynamicEndIndex = dynamicEnd + start;
if ( if (
dynamicDoesStart && dynamicDoesStart &&
cursor.ch > dynamicStartIndex && cursorIndex >= dynamicStartIndex &&
((dynamicDoesEnd && cursor.ch < dynamicEndIndex) || ((dynamicDoesEnd && cursorIndex <= dynamicEndIndex) ||
(!dynamicDoesEnd && cursor.ch > dynamicStartIndex)) (!dynamicDoesEnd && cursorIndex >= dynamicStartIndex))
) { ) {
cursorBetweenBinding = true; cursorBetweenBinding = true;
} }
@ -523,63 +561,75 @@ class DynamicAutocompleteInput extends Component<Props, State> {
disabled, disabled,
className, className,
setMaxHeight, setMaxHeight,
dataTreePath,
dynamicData,
expected,
} = this.props; } = this.props;
const hasError = !!(meta && meta.error); const hasError = !!(meta && meta.error);
let showError = false; let evaluatedValue: any = "undefined";
if (this.editor) { if (dataTreePath) {
showError = evaluatedValue = _.get(dynamicData, dataTreePath);
hasError && this.state.isFocused && !this.state.autoCompleteVisible;
} }
const showEvaluatedValue =
this.state.isFocused && "dataTreePath" in this.props;
return ( return (
<ErrorTooltip message={meta ? meta.error : ""} isOpen={showError}> <Wrapper>
<Wrapper <EvaluatedValuePopup
editorTheme={theme} theme={theme || THEMES.LIGHT}
isOpen={showEvaluatedValue}
evaluatedValue={evaluatedValue}
expected={expected}
hasError={hasError} hasError={hasError}
singleLine={singleLine}
isFocused={this.state.isFocused}
disabled={disabled}
className={className}
setMaxHeight={setMaxHeight}
> >
<HintStyles /> <EditorWrapper
<IconContainer> editorTheme={theme}
{this.props.leftIcon && <this.props.leftIcon />} hasError={hasError}
</IconContainer> singleLine={singleLine}
isFocused={this.state.isFocused}
disabled={disabled}
className={className}
setMaxHeight={setMaxHeight}
>
<HintStyles editorTheme={theme || THEMES.LIGHT} />
<IconContainer>
{this.props.leftIcon && <this.props.leftIcon />}
</IconContainer>
{this.props.leftImage && ( {this.props.leftImage && (
<img <img
src={this.props.leftImage} src={this.props.leftImage}
alt="img" alt="img"
className="leftImageStyles" className="leftImageStyles"
/> />
)} )}
<textarea <textarea
ref={this.textArea} ref={this.textArea}
{..._.omit(this.props.input, ["onChange", "value"])} {..._.omit(this.props.input, ["onChange", "value"])}
defaultValue={input.value} defaultValue={input.value}
placeholder={this.props.placeholder} placeholder={this.props.placeholder}
/>
{this.props.link && (
<React.Fragment>
<a
href={this.props.link}
target="_blank"
className="linkStyles"
rel="noopener noreferrer"
>
API documentation
</a>
</React.Fragment>
)}
{this.props.rightIcon && (
<HelperTooltip
description={this.props.description}
rightIcon={this.props.rightIcon}
/> />
)} {this.props.link && (
</Wrapper> <React.Fragment>
</ErrorTooltip> <a
href={this.props.link}
target="_blank"
className="linkStyles"
rel="noopener noreferrer"
>
API documentation
</a>
</React.Fragment>
)}
{this.props.rightIcon && (
<HelperTooltip
description={this.props.description}
rightIcon={this.props.rightIcon}
/>
)}
</EditorWrapper>
</EvaluatedValuePopup>
</Wrapper>
); );
} }
} }

View File

@ -0,0 +1,200 @@
import React, { useRef } from "react";
import styled from "styled-components";
import _ from "lodash";
import Popper from "pages/Editor/Popper";
import ReactJson from "react-json-view";
import { EditorTheme } from "components/editorComponents/DynamicAutocompleteInput";
import { theme } from "constants/DefaultTheme";
import { Placement } from "popper.js";
const Wrapper = styled.div`
position: relative;
flex: 1;
height: 100%;
`;
type ThemeConfig = {
backgroundColor: string;
textColor: string;
editorColor: string;
editorBackground: string;
};
type PopupTheme = Record<EditorTheme, ThemeConfig>;
const THEMES: PopupTheme = {
LIGHT: {
backgroundColor: "#fff",
textColor: "#1E242B",
editorBackground: "#F4F4F4",
editorColor: "#1E242B",
},
DARK: {
backgroundColor: "#23292e",
textColor: "#F4F4F4",
editorBackground: "#090a0f",
editorColor: "#F4F4F4",
},
};
const ContentWrapper = styled.div<{ colorTheme: EditorTheme }>`
width: ${props => props.theme.evaluatedValuePopup.width}px;
max-height: ${props => props.theme.evaluatedValuePopup.height}px;
overflow-y: auto;
background-color: ${props => THEMES[props.colorTheme].backgroundColor};
color: ${props => THEMES[props.colorTheme].textColor};
padding: 15px;
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 4px;
.react-json-view {
.icon-container {
display: none !important;
}
}
`;
const CurrentValueWrapper = styled.div`
max-height: 150px;
overflow-y: auto;
`;
const CodeWrapper = styled.pre<{ colorTheme: EditorTheme }>`
padding: 10px;
background-color: ${props => THEMES[props.colorTheme].editorBackground};
color: ${props => THEMES[props.colorTheme].editorColor};
overflow: scroll;
`;
const TypeText = styled.pre<{ colorTheme: EditorTheme }>`
padding: 5px;
background-color: ${props => THEMES[props.colorTheme].editorBackground};
color: ${props => THEMES[props.colorTheme].editorColor};
overflow: scroll;
`;
const ErrorText = styled.p`
margin: 10px 0;
padding: 5px;
border-radius: 2px;
background-color: rgba(235, 87, 87, 0.2);
color: ${props => props.theme.colors.error};
`;
interface Props {
theme: EditorTheme;
isOpen: boolean;
hasError: boolean;
expected?: string;
evaluatedValue?: any;
children: JSX.Element;
}
interface PopoverContentProps {
hasError: boolean;
expected?: string;
evaluatedValue: any;
theme: EditorTheme;
}
const CurrentValueViewer = (props: {
theme: EditorTheme;
evaluatedValue: any;
}) => {
let content = (
<CodeWrapper colorTheme={props.theme}>{"undefined"}</CodeWrapper>
);
if (props.evaluatedValue !== undefined) {
if (
_.isObject(props.evaluatedValue) ||
Array.isArray(props.evaluatedValue)
) {
const reactJsonProps = {
theme: props.theme === "DARK" ? "monokai" : "rjv-default",
name: null,
enableClipboard: false,
displayObjectSize: false,
displayDataTypes: false,
style: {
fontSize: "14px",
},
collapsed: 2,
collapseStringsAfterLength: 20,
};
content = <ReactJson src={props.evaluatedValue} {...reactJsonProps} />;
} else {
content = (
<CodeWrapper colorTheme={props.theme}>
{props.evaluatedValue.toString()}
</CodeWrapper>
);
}
}
return (
<React.Fragment>
<p>Current Value:</p>
<CurrentValueWrapper>{content}</CurrentValueWrapper>
</React.Fragment>
);
};
const PopoverContent = (props: PopoverContentProps) => {
return (
<ContentWrapper
colorTheme={props.theme}
className="t--CodeEditor-evaluatedValue"
>
{props.hasError && (
<ErrorText>{`This value does not evaluate to type "${props.expected}"`}</ErrorText>
)}
{props.expected && (
<React.Fragment>
<p>Expected type:</p>
<TypeText colorTheme={props.theme}>{props.expected}</TypeText>
</React.Fragment>
)}
<CurrentValueViewer
theme={props.theme}
evaluatedValue={props.evaluatedValue}
/>
</ContentWrapper>
);
};
const EvaluatedValuePopup = (props: Props) => {
const wrapperRef = useRef<HTMLDivElement>(null);
let placement: Placement = "left-start";
if (wrapperRef.current) {
const boundingRect = wrapperRef.current.getBoundingClientRect();
if (boundingRect.left < theme.evaluatedValuePopup.width) {
placement = "right-start";
}
}
return (
<Wrapper ref={wrapperRef}>
{props.isOpen && (
<Popper
targetNode={wrapperRef.current || undefined}
isOpen
zIndex={15}
placement={placement}
modifiers={{
offset: {
enabled: true,
offset: "0, 15",
},
}}
>
<PopoverContent
expected={props.expected}
evaluatedValue={props.evaluatedValue}
hasError={props.hasError}
theme={props.theme}
/>
</Popper>
)}
{props.children}
</Wrapper>
);
};
export default EvaluatedValuePopup;

View File

@ -196,7 +196,7 @@ const views = {
} }
}} }}
isValid={props.isValid} isValid={props.isValid}
validationMessage={props.validationMessage} errorMessage={props.validationMessage}
/> />
</ControlWrapper> </ControlWrapper>
); );

View File

@ -1,65 +1,266 @@
import React from "react"; import React, { useState, useEffect } from "react";
import CreatableDropdown from "components/designSystems/appsmith/CreatableDropdown"; import CreatableDropdown from "components/designSystems/appsmith/CreatableDropdown";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Field } from "redux-form"; import { Field, formValueSelector, change } from "redux-form";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { ReactComponent as StorageIcon } from "assets/icons/menu/storage.svg";
import { DatasourceDataState } from "reducers/entityReducers/datasourceReducer"; import { DatasourceDataState } from "reducers/entityReducers/datasourceReducer";
import { Plugin } from "api/PluginApi"; import { Plugin } from "api/PluginApi";
import { getDatasourcePlugins } from "selectors/entitiesSelector"; import { getDatasourcePlugins } from "selectors/entitiesSelector";
import _ from "lodash"; import _ from "lodash";
import { createDatasource } from "actions/datasourceActions"; import { createDatasource, storeAsDatasource } from "actions/datasourceActions";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { Datasource, CreateDatasourceConfig } from "api/DatasourcesApi";
import styled, { createGlobalStyle } from "styled-components";
import { MenuItem, Menu, Popover, Position } from "@blueprintjs/core";
import { IconWrapper } from "constants/IconConstants";
import { theme } from "constants/DefaultTheme";
import { ControlIcons } from "icons/ControlIcons";
import { API_EDITOR_FORM_NAME } from "constants/forms";
import { InputActionMeta } from "react-select";
import { setDatasourceFieldText } from "actions/apiPaneActions";
interface ReduxStateProps { interface ReduxStateProps {
datasources: DatasourceDataState; datasources: DatasourceDataState;
validDatasourcePlugins: Plugin[]; validDatasourcePlugins: Plugin[];
apiId: string;
value: Datasource;
} }
interface ReduxActionProps { interface ReduxActionProps {
createDatasource: (value: string) => void; createDatasource: (value: string) => void;
storeAsDatasource: () => void;
changeDatasource: (value: Datasource | CreateDatasourceConfig) => void;
changePath: (value: string) => void;
setDatasourceFieldText: (apiId: string, value: string) => void;
} }
interface ComponentProps { interface ComponentProps {
name: string; name: string;
pluginId: string; pluginId: string;
appName: string; appName: string;
datasourceFieldText: string;
} }
const StyledMenuItem = styled(MenuItem)`
&&&&.bp3-menu-item {
align-items: center;
width: 202px;
justify-content: center;
}
`;
const StyledMenu = styled(Menu)`
&&&&.bp3-menu {
padding: 8px;
background: #ffffff;
border: 1px solid #ebeff2;
box-sizing: border-box;
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
border-radius: 4px;
}
`;
const TooltipStyles = createGlobalStyle`
.helper-tooltip{
.bp3-popover {
margin-right: 10px;
margin-top: 5px;
}
}
`;
const DatasourcesField = ( const DatasourcesField = (
props: ReduxActionProps & ReduxStateProps & ComponentProps, props: ReduxActionProps & ReduxStateProps & ComponentProps,
) => { ) => {
const options = props.datasources.list const [inputValue, setValue] = useState(props.datasourceFieldText);
.filter(r => r.pluginId === props.pluginId)
.filter(r => useEffect(() => {
props.validDatasourcePlugins.some(plugin => plugin.id === r.pluginId), setValue(props.datasourceFieldText);
) }, [props.datasourceFieldText]);
.filter(r => r.datasourceConfiguration)
.filter(r => r.datasourceConfiguration.url) const options = React.useMemo(() => {
.map(r => ({ return props.datasources.list
label: r.datasourceConfiguration?.url.endsWith("/") .filter(r => r.pluginId === props.pluginId)
? r.datasourceConfiguration?.url.slice(0, -1) .filter(r => {
: r.datasourceConfiguration.url, return props.validDatasourcePlugins.some(
value: r.id, plugin => plugin.id === r.pluginId,
})); );
})
.filter(r => r.datasourceConfiguration)
.filter(r => r.datasourceConfiguration.url)
.map(r => ({
label: r.datasourceConfiguration?.url,
value: r.id,
}));
}, [props.datasources.list, props.validDatasourcePlugins, props.pluginId]);
const { storeAsDatasource } = props;
let isEmbeddedDatasource = true;
if (props.value && props.value.id) {
isEmbeddedDatasource = false;
} else if (props.value && props.value.datasourceConfiguration) {
isEmbeddedDatasource = true;
}
const DropdownIndicator = (props: any) => {
if (props.hasValue) return null;
const MenuContainer = (
<StyledMenu>
<StyledMenuItem
icon={
<IconWrapper
width={theme.fontSizes[4]}
height={theme.fontSizes[4]}
color={"#535B62"}
>
<StorageIcon />
</IconWrapper>
}
text="Store as datasource"
onClick={storeAsDatasource}
/>
</StyledMenu>
);
return (
<>
<TooltipStyles />
<Popover
content={MenuContainer}
position={Position.BOTTOM_LEFT}
usePortal
portalClassName="helper-tooltip"
>
<div
style={{
padding: "8px 13px 3px 13px",
}}
onMouseDown={e => {
e.stopPropagation();
}}
>
<ControlIcons.MORE_HORIZONTAL_CONTROL
width={theme.fontSizes[4]}
height={theme.fontSizes[4]}
color="#C4C4C4"
/>
</div>
</Popover>
</>
);
};
return ( return (
<Field <Field
name={props.name} name={props.name}
component={CreatableDropdown} component={CreatableDropdown}
isLoading={props.datasources.loading} isLoading={props.datasources.loading}
options={options} options={options}
components={{
ClearIndicator: () => null,
IndicatorSeparator: () => null,
DropdownIndicator,
}}
placeholder="https://<base-url>.com" placeholder="https://<base-url>.com"
onCreateOption={props.createDatasource} onInputChange={(value: string, actionMeta: InputActionMeta) => {
format={(value: string) => _.find(options, { value }) || ""} const { action } = actionMeta;
parse={(option: { value: string }) => (option ? option.value : null)} if (action === "input-blur") {
formatCreateLabel={(value: string) => `Create data source "${value}"`} props.setDatasourceFieldText(props.apiId, inputValue);
noOptionsMessage={() => "No data sources created"}
return value;
} else if (action === "set-value") {
setValue("");
return "";
} else if (action === "menu-close") {
return value;
}
setValue(value);
if (isEmbeddedDatasource) {
let datasourcePayload: Datasource | CreateDatasourceConfig;
let pathPayload: string;
try {
const url = new URL(value);
const path = url.pathname === "/" ? "" : url.pathname;
const params = url.search;
const baseUrl = url.origin;
datasourcePayload = {
name: baseUrl,
datasourceConfiguration: {
url: baseUrl,
},
pluginId: props.pluginId,
appName: props.appName,
};
pathPayload = path + params;
} catch (e) {
datasourcePayload = {
name: value,
datasourceConfiguration: {
url: value,
},
pluginId: props.pluginId,
appName: props.appName,
};
pathPayload = "";
}
const updateValues = _.debounce(() => {
props.changeDatasource(datasourcePayload);
props.changePath(pathPayload);
}, 50);
updateValues();
} else {
const updatePath = _.debounce(() => {
props.changePath(value);
}, 50);
updatePath();
}
}}
format={(value: Datasource) => {
if (!value || !value.datasourceConfiguration) return "";
if (!value.id) {
return "";
}
const option = _.find(options, { value: value.id });
return option ? [option] : "";
}}
parse={(option: { value: string }[]) => {
if (!option) return null;
if (option.length) {
const datasources = props.datasources.list;
return datasources.find(
datasource => datasource.id === option[0].value,
);
}
}}
inputValue={inputValue}
noOptionsMessage={() => null}
/> />
); );
}; };
const mapStateToProps = (state: AppState): ReduxStateProps => ({ const mapStateToProps = (state: AppState): ReduxStateProps => {
datasources: state.entities.datasources, const selector = formValueSelector(API_EDITOR_FORM_NAME);
validDatasourcePlugins: getDatasourcePlugins(state), const apiId = selector(state, "id");
}); const datasource = selector(state, "datasource");
return {
datasources: state.entities.datasources,
validDatasourcePlugins: getDatasourcePlugins(state),
apiId,
value: datasource,
};
};
const mapDispatchToProps = ( const mapDispatchToProps = (
dispatch: any, dispatch: any,
@ -88,6 +289,16 @@ const mapDispatchToProps = (
}), }),
); );
}, },
storeAsDatasource: () => dispatch(storeAsDatasource()),
changeDatasource: value => {
dispatch(change(API_EDITOR_FORM_NAME, "datasource", value));
},
changePath: (value: string) => {
dispatch(change(API_EDITOR_FORM_NAME, "actionConfiguration.path", value));
},
setDatasourceFieldText: (apiId, value) => {
dispatch(setDatasourceFieldText(apiId, value));
},
}); });
export default connect(mapStateToProps, mapDispatchToProps)(DatasourcesField); export default connect(mapStateToProps, mapDispatchToProps)(DatasourcesField);

View File

@ -1,19 +0,0 @@
import React from "react";
import { Field } from "redux-form";
import DynamicAutocompleteInput, {
DynamicAutocompleteInputProps,
} from "components/editorComponents/DynamicAutocompleteInput";
type Props = { name: string } & DynamicAutocompleteInputProps;
const JSONEditorField = (props: Props) => {
return (
<Field
name={props.name}
component={DynamicAutocompleteInput}
singleLine={false}
/>
);
};
export default JSONEditorField;

View File

@ -6,6 +6,7 @@ import { FormIcons } from "icons/FormIcons";
import DynamicTextField from "./DynamicTextField"; import DynamicTextField from "./DynamicTextField";
import FormRow from "components/editorComponents/FormRow"; import FormRow from "components/editorComponents/FormRow";
import FormLabel from "components/editorComponents/FormLabel"; import FormLabel from "components/editorComponents/FormLabel";
import FIELD_VALUES from "constants/FieldExpectedValue";
const FormRowWithLabel = styled(FormRow)` const FormRowWithLabel = styled(FormRow)`
flex-wrap: wrap; flex-wrap: wrap;
@ -26,7 +27,6 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
} }
} }
}, [props.fields, props.pushFields]); }, [props.fields, props.pushFields]);
return ( return (
<React.Fragment> <React.Fragment>
{typeof props.fields.getAll() === "object" && ( {typeof props.fields.getAll() === "object" && (
@ -42,6 +42,8 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
placeholder="Key" placeholder="Key"
singleLine singleLine
setMaxHeight setMaxHeight
dataTreePath={`${props.dataTreePath}[${index}].key`}
expected={FIELD_VALUES.API_ACTION.params}
/> />
{!props.actionConfig && ( {!props.actionConfig && (
<DynamicTextField <DynamicTextField
@ -50,6 +52,8 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
placeholder="Value" placeholder="Value"
singleLine singleLine
setMaxHeight setMaxHeight
dataTreePath={`${props.dataTreePath}[${index}].value`}
expected={FIELD_VALUES.API_ACTION.params}
/> />
)} )}
@ -58,7 +62,9 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
<DynamicTextField <DynamicTextField
className={`t--${field}.value.${index}`} className={`t--${field}.value.${index}`}
name={`${field}.value`} name={`${field}.value`}
dataTreePath={`${props.dataTreePath}[${index}].value`}
setMaxHeight setMaxHeight
expected={FIELD_VALUES.API_ACTION.params}
placeholder={ placeholder={
props.placeholder props.placeholder
? props.placeholder ? props.placeholder
@ -123,6 +129,7 @@ type Props = {
type?: string; type?: string;
placeholder?: string; placeholder?: string;
pushFields?: boolean; pushFields?: boolean;
dataTreePath?: string;
}; };
const KeyValueFieldArray = (props: Props) => { const KeyValueFieldArray = (props: Props) => {

View File

@ -33,7 +33,7 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
props.fields.push({ [keyName[1]]: "", [valueName[1]]: "" }); props.fields.push({ [keyName[1]]: "", [valueName[1]]: "" });
} }
} }
}, [props.fields]); }, [props.fields, keyName, valueName]);
useEffect(() => { useEffect(() => {
if (typeof props.fields.getAll() === "string") { if (typeof props.fields.getAll() === "string") {

View File

@ -15,7 +15,7 @@ class ActionSelectorControl extends BaseControl<ControlProps> {
<ActionCreator <ActionCreator
value={propertyValue} value={propertyValue}
isValid={this.props.isValid} isValid={this.props.isValid}
validationMessage={this.props.validationMessage} validationMessage={this.props.errorMessage}
onValueChange={this.handleValueUpdate} onValueChange={this.handleValueUpdate}
/> />
); );

View File

@ -33,7 +33,10 @@ export interface ControlData {
controlType: ControlType; controlType: ControlType;
propertyValue?: any; propertyValue?: any;
isValid: boolean; isValid: boolean;
errorMessage?: string;
expected: string;
validationMessage?: string; validationMessage?: string;
dataTreePath: string;
} }
export interface ControlFunctions { export interface ControlFunctions {

View File

@ -70,18 +70,11 @@ type RenderComponentProps = {
validationMessage: string; validationMessage: string;
deleteOption: Function; deleteOption: Function;
updateOption: Function; updateOption: Function;
dataTreePath: string;
}; };
function DataControlComponent(props: RenderComponentProps) { function DataControlComponent(props: RenderComponentProps) {
const { const { deleteOption, updateOption, item, index, length, isValid } = props;
deleteOption,
updateOption,
item,
index,
length,
isValid,
validationMessage,
} = props;
return ( return (
<StyledOptionControlWrapper orientation={"VERTICAL"}> <StyledOptionControlWrapper orientation={"VERTICAL"}>
<StyledOptionControlWrapper orientation={"HORIZONTAL"}> <StyledOptionControlWrapper orientation={"HORIZONTAL"}>
@ -117,8 +110,9 @@ function DataControlComponent(props: RenderComponentProps) {
updateOption(index, "data", value); updateOption(index, "data", value);
}, },
}} }}
dataTreePath={`${props.dataTreePath}`}
meta={{ meta={{
error: isValid ? "" : validationMessage, error: isValid ? "" : "There is an error",
touched: true, touched: true,
}} }}
theme={"DARK"} theme={"DARK"}
@ -190,6 +184,7 @@ class ChartDataControl extends BaseControl<ControlProps> {
updateOption={this.updateOption} updateOption={this.updateOption}
isValid={validations[index].isValid} isValid={validations[index].isValid}
validationMessage={validations[index].validationMessage} validationMessage={validations[index].validationMessage}
dataTreePath={`${this.props.dataTreePath}`}
/> />
); );
})} })}

View File

@ -4,13 +4,21 @@ import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocom
import { EventOrValueHandler } from "redux-form"; import { EventOrValueHandler } from "redux-form";
class CodeEditorControl extends BaseControl<ControlProps> { class CodeEditorControl extends BaseControl<ControlProps> {
render() { render() {
const { validationMessage, propertyValue, isValid } = this.props; const {
errorMessage,
expected,
propertyValue,
isValid,
dataTreePath,
} = this.props;
return ( return (
<DynamicAutocompleteInput <DynamicAutocompleteInput
theme={"DARK"} theme={"DARK"}
input={{ value: propertyValue, onChange: this.onChange }} input={{ value: propertyValue, onChange: this.onChange }}
dataTreePath={dataTreePath}
expected={expected}
meta={{ meta={{
error: isValid ? "" : validationMessage, error: isValid ? "" : errorMessage,
touched: true, touched: true,
}} }}
singleLine={false} singleLine={false}

View File

@ -9,10 +9,20 @@ export function InputText(props: {
value: string; value: string;
onChange: (event: React.ChangeEvent<HTMLTextAreaElement> | string) => void; onChange: (event: React.ChangeEvent<HTMLTextAreaElement> | string) => void;
isValid: boolean; isValid: boolean;
validationMessage?: string; errorMessage?: string;
expected?: string;
placeholder?: string; placeholder?: string;
dataTreePath?: string;
}) { }) {
const { validationMessage, value, isValid, onChange, placeholder } = props; const {
errorMessage,
expected,
value,
isValid,
onChange,
placeholder,
dataTreePath,
} = props;
return ( return (
<StyledDynamicInput> <StyledDynamicInput>
<DynamicAutocompleteInput <DynamicAutocompleteInput
@ -20,8 +30,10 @@ export function InputText(props: {
value: value, value: value,
onChange: onChange, onChange: onChange,
}} }}
expected={expected}
dataTreePath={dataTreePath}
meta={{ meta={{
error: isValid ? "" : validationMessage, error: isValid ? "" : errorMessage,
touched: true, touched: true,
}} }}
theme={"DARK"} theme={"DARK"}
@ -35,11 +47,13 @@ export function InputText(props: {
class InputTextControl extends BaseControl<InputControlProps> { class InputTextControl extends BaseControl<InputControlProps> {
render() { render() {
const { const {
validationMessage, errorMessage,
expected,
propertyValue, propertyValue,
isValid, isValid,
label, label,
placeholderText, placeholderText,
dataTreePath,
} = this.props; } = this.props;
return ( return (
<InputText <InputText
@ -47,8 +61,10 @@ class InputTextControl extends BaseControl<InputControlProps> {
value={propertyValue} value={propertyValue}
onChange={this.onTextChange} onChange={this.onTextChange}
isValid={isValid} isValid={isValid}
validationMessage={validationMessage} errorMessage={errorMessage}
expected={expected}
placeholder={placeholderText} placeholder={placeholderText}
dataTreePath={dataTreePath}
/> />
); );
} }

View File

@ -2,7 +2,8 @@ export type ContentType =
| "application/json" | "application/json"
| "application/x-www-form-urlencoded"; | "application/x-www-form-urlencoded";
export const REQUEST_TIMEOUT_MS = 10000; export const REQUEST_TIMEOUT_MS = 20000;
export const DEFAULT_ACTION_TIMEOUT = 10000;
export const DEFAULT_EXECUTE_ACTION_TIMEOUT_MS = 15000; export const DEFAULT_EXECUTE_ACTION_TIMEOUT_MS = 15000;
export const DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS = 30000; export const DEFAULT_TEST_DATA_SOURCE_TIMEOUT_MS = 30000;

View File

@ -1,4 +1,5 @@
import { RestAction } from "api/ActionAPI"; import { RestAction } from "entities/Action";
import { DEFAULT_ACTION_TIMEOUT } from "constants/ApiConstants";
export const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"]; export const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
@ -11,6 +12,7 @@ export const REST_PLUGIN_PACKAGE_NAME = "restapi-plugin";
export const DEFAULT_API_ACTION: Partial<RestAction> = { export const DEFAULT_API_ACTION: Partial<RestAction> = {
actionConfiguration: { actionConfiguration: {
timeoutInMillisecond: DEFAULT_ACTION_TIMEOUT,
httpMethod: HTTP_METHODS[0], httpMethod: HTTP_METHODS[0],
headers: [ headers: [
{ key: "", value: "" }, { key: "", value: "" },

View File

@ -45,6 +45,8 @@ export const Colors: Record<string, string> = {
BLUE_CHARCOAL: "#23292E", BLUE_CHARCOAL: "#23292E",
TROUT: "#4C565E", TROUT: "#4C565E",
JAFFA_DARK: "#EF7541", JAFFA_DARK: "#EF7541",
GRAY: "#828282",
ATHENS_GRAY_DARKER: "#F8F9FA",
}; };
export type Color = typeof Colors[keyof typeof Colors]; export type Color = typeof Colors[keyof typeof Colors];

View File

@ -243,6 +243,10 @@ export type Theme = {
lineHeights: Array<number>; lineHeights: Array<number>;
fonts: Array<FontFamily>; fonts: Array<FontFamily>;
borders: ThemeBorder[]; borders: ThemeBorder[];
evaluatedValuePopup: {
width: number;
height: number;
};
propertyPane: PropertyPaneTheme; propertyPane: PropertyPaneTheme;
headerHeight: string; headerHeight: string;
sidebarWidth: string; sidebarWidth: string;
@ -312,6 +316,10 @@ export const theme: Theme = {
height: 600, height: 600,
dividerColor: Colors.MAKO, dividerColor: Colors.MAKO,
}, },
evaluatedValuePopup: {
width: 300,
height: 400,
},
drawerWidth: "80%", drawerWidth: "80%",
colors: { colors: {
primary: Colors.GREEN, primary: Colors.GREEN,

View File

@ -0,0 +1,146 @@
import { WidgetType } from "constants/WidgetConstants";
const FIELD_VALUES: Record<
WidgetType | "API_ACTION",
Record<string, string>
> = {
API_ACTION: {
body: "JSON",
params: "string",
headers: "string",
path: "string",
},
CANVAS_WIDGET: {},
ICON_WIDGET: {},
CONTAINER_WIDGET: {
backgroundColor: "string",
isVisible: "boolean",
},
DATE_PICKER_WIDGET: {
defaultDate: "Date",
isRequired: "boolean",
isVisible: "boolean",
isDisabled: "boolean",
onDateSelected: "undefined",
},
TABLE_WIDGET: {
tableData: "Array<Object>",
serverSidePaginationEnabled: "boolean",
isVisible: "boolean",
exportPDF: "boolean",
exportExcel: "boolean",
exportCsv: "boolean",
onRowSelected: "undefined",
onPageChange: "undefined",
},
IMAGE_WIDGET: {
image: "string",
defaultImage: "string",
isVisible: "boolean",
},
RADIO_GROUP_WIDGET: {
options: "Array<{ label: string, value: string }>",
defaultOptionValue: "string",
isRequired: "boolean",
isVisible: "boolean",
onSelectionChange: "undefined",
},
TABS_WIDGET: {
tabs: "Array<{ label: string, id: string }>",
selectedTab: "string",
isVisible: "boolean",
},
CHART_WIDGET: {
chartName: "string",
chartType: "LINE_CHART | BAR_CHART | PIE_CHART | COLUMN_CHART | AREA_CHART",
singleChartData: "Array<{ x: string, y: number }>",
xAxisName: "string",
yAxisName: "string",
isVisible: "boolean",
},
MODAL_WIDGET: {
canOutsideClickClose: "boolean",
size: "MODAL_LARGE | MODAL_SMALL",
},
INPUT_WIDGET: {
inputType: "string",
placeholderText: "string",
defaultText: "string",
regex: "string",
errorMessage: "string",
isRequired: "boolean",
isVisible: "boolean",
isDisabled: "boolean",
onTextChanged: "undefined",
},
DROP_DOWN_WIDGET: {
label: "string",
selectionType: "SINGLE_SELECT | MULTI_SELECT",
options: "Array<{ label: string, value: string }>",
defaultOptionValue: "string",
isRequired: "boolean",
isVisible: "boolean",
onOptionChange: "boolean",
},
FORM_BUTTON_WIDGET: {
text: "string",
buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON",
disabledWhenInvalid: "boolean",
resetFormOnClick: "boolean",
isVisible: "boolean",
onClick: "boolean",
},
MAP_WIDGET: {
defaultMarkers: "Array<{ lat: number, long: number }>",
enableSearch: "boolean",
enablePickLocation: "boolean",
enableCreateMarker: "boolean",
isVisible: "boolean",
onMarkerClick: "undefined",
onCreateMarker: "undefined",
},
BUTTON_WIDGET: {
text: "string",
buttonStyle: "PRIMARY_BUTTON | SECONDARY_BUTTON | DANGER_BUTTON",
isVisible: "boolean",
onClick: "boolean",
},
RICH_TEXT_EDITOR_WIDGET: {
defaultText: "string",
isVisible: "boolean",
isDisabled: "boolean",
onTextChange: "undefined",
},
FILE_PICKER_WIDGET: {
label: "string",
maxNumFiles: "number",
maxFileSize: "number",
allowedFileTypes: "Array<string>",
isRequired: "boolean",
isVisible: "boolean",
uploadedFileUrls: "string",
onFilesSelected: "undefined",
},
CHECKBOX_WIDGET: {
label: "string",
defaultCheckedState: "boolean",
isRequired: "boolean",
isDisabled: "boolean",
isVisible: "boolean",
onCheckChange: "undefined",
},
FORM_WIDGET: {
backgroundColor: "string",
isVisible: "boolean",
},
TEXT_WIDGET: {
text: "string",
textAlign: "LEFT | CENTER | RIGHT",
textStyle: "HEADING | LABEL | BODY",
shouldScroll: "boolean",
isVisible: "boolean",
},
};
export default FIELD_VALUES;

View File

@ -77,6 +77,10 @@ export const ReduxActionTypes: { [key: string]: string } = {
FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS", FETCH_PUBLISHED_PAGE_SUCCESS: "FETCH_PUBLISHED_PAGE_SUCCESS",
DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT", DELETE_DATASOURCE_INIT: "DELETE_DATASOURCE_INIT",
DELETE_DATASOURCE_SUCCESS: "DELETE_DATASOURCE_SUCCESS", DELETE_DATASOURCE_SUCCESS: "DELETE_DATASOURCE_SUCCESS",
STORE_AS_DATASOURCE_INIT: "STORE_AS_DATASOURCE_INIT",
STORE_AS_DATASOURCE_UPDATE: "STORE_AS_DATASOURCE_UPDATE",
STORE_AS_DATASOURCE_COMPLETE: "STORE_AS_DATASOURCE_COMPLETE",
SET_DATASOURCE_FIELD_TEXT: "SET_DATASOURCE_FIELD_TEXT",
PUBLISH_APPLICATION_INIT: "PUBLISH_APPLICATION_INIT", PUBLISH_APPLICATION_INIT: "PUBLISH_APPLICATION_INIT",
PUBLISH_APPLICATION_SUCCESS: "PUBLISH_APPLICATION_SUCCESS", PUBLISH_APPLICATION_SUCCESS: "PUBLISH_APPLICATION_SUCCESS",
CREATE_PAGE_INIT: "CREATE_PAGE_INIT", CREATE_PAGE_INIT: "CREATE_PAGE_INIT",
@ -127,6 +131,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
FETCH_ORGS_SUCCESS: "FETCH_ORGS_SUCCES", FETCH_ORGS_SUCCESS: "FETCH_ORGS_SUCCES",
FETCH_ORGS_INIT: "FETCH_ORGS_INIT", FETCH_ORGS_INIT: "FETCH_ORGS_INIT",
SAVE_ORG_INIT: "SAVE_ORG_INIT", SAVE_ORG_INIT: "SAVE_ORG_INIT",
SET_CURRENT_ORG_ID: "SET_CURRENT_ORG_ID",
STORE_DATASOURCE_REFS: "STORE_DATASOURCE_REFS", STORE_DATASOURCE_REFS: "STORE_DATASOURCE_REFS",
UPDATE_DATASOURCE_REFS: "UPDATE_DATASOURCE_REFS", UPDATE_DATASOURCE_REFS: "UPDATE_DATASOURCE_REFS",
FETCH_USER_INIT: "FETCH_USER_INIT", FETCH_USER_INIT: "FETCH_USER_INIT",

View File

@ -11,6 +11,7 @@ export const VALIDATION_TYPES = {
ARRAY: "ARRAY", ARRAY: "ARRAY",
TABLE_DATA: "TABLE_DATA", TABLE_DATA: "TABLE_DATA",
OPTIONS_DATA: "OPTIONS_DATA", OPTIONS_DATA: "OPTIONS_DATA",
SINGLE_CHART_DATA: "SINGLE_CHART_DATA",
DATE: "DATE", DATE: "DATE",
TABS_DATA: "TABS_DATA", TABS_DATA: "TABS_DATA",
CHART_DATA: "CHART_DATA", CHART_DATA: "CHART_DATA",

View File

@ -0,0 +1,91 @@
import { Datasource } from "api/DatasourcesApi";
export enum PluginType {
API = "API",
DB = "DB",
}
export enum PaginationType {
NONE = "NONE",
PAGE_NO = "PAGE_NO",
URL = "URL",
}
export interface ActionConfig {
timeoutInMillisecond: number;
paginationType?: PaginationType;
}
export interface ActionProvider {
name: string;
imageUrl: string;
url: string;
description: string;
credentialSteps: string;
}
export interface Property {
key: string;
value: string;
}
export interface BodyFormData {
editable: boolean;
mandatory: boolean;
description: string;
key: string;
value?: string;
type: string;
}
export interface ApiActionConfig extends ActionConfig {
headers: Property[];
httpMethod: string;
path?: string;
body?: JSON | string | Record<string, any> | null;
queryParameters?: Property[];
bodyFormData?: BodyFormData[];
}
export interface QueryActionConfig extends ActionConfig {
queryString: string;
}
export interface Action {
id: string;
name: string;
datasource: Partial<Datasource>;
organizationId: string;
pageId: string;
collectionId?: string;
actionConfiguration: Partial<ActionConfig>;
pluginId: string;
pluginType: PluginType;
executeOnLoad: boolean;
dynamicBindingPathList: Property[];
isValid: boolean;
invalids: string[];
jsonPathKeys: string[];
cacheResponse: string;
templateId?: string;
providerId?: string;
provider?: ActionProvider;
documentation?: { text: string };
}
export interface RestAction extends Action {
actionConfiguration: Partial<ApiActionConfig>;
}
export interface RapidApiAction extends Action {
actionConfiguration: Partial<ApiActionConfig>;
templateId: string;
proverId: string;
provider: ActionProvider;
pluginId: string;
documentation: { text: string };
}
export interface QueryAction extends Action {
actionConfiguration: Partial<QueryActionConfig>;
}

View File

@ -8,6 +8,8 @@ import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsRe
import { MetaState } from "reducers/entityReducers/metaReducer"; import { MetaState } from "reducers/entityReducers/metaReducer";
import { PageListPayload } from "constants/ReduxActionConstants"; import { PageListPayload } from "constants/ReduxActionConstants";
import WidgetFactory from "utils/WidgetFactory"; import WidgetFactory from "utils/WidgetFactory";
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
import { Property } from "entities/Action";
export type ActionDescription<T> = { export type ActionDescription<T> = {
type: string; type: string;
@ -32,6 +34,7 @@ export type RunActionPayload = {
export interface DataTreeAction extends Omit<ActionData, "data"> { export interface DataTreeAction extends Omit<ActionData, "data"> {
data: ActionResponse["body"]; data: ActionResponse["body"];
run: ActionDispatcher<RunActionPayload, [string, string]>; run: ActionDispatcher<RunActionPayload, [string, string]>;
dynamicBindingPathList: Property[];
ENTITY_TYPE: ENTITY_TYPE.ACTION; ENTITY_TYPE: ENTITY_TYPE.ACTION;
} }
@ -63,6 +66,7 @@ export type DataTree = {
type DataTreeSeed = { type DataTreeSeed = {
actions: ActionDataState; actions: ActionDataState;
actionDrafts: ActionDraftsState;
widgets: CanvasWidgetsReduxState; widgets: CanvasWidgetsReduxState;
widgetsMeta: MetaState; widgetsMeta: MetaState;
pageList: PageListPayload; pageList: PageListPayload;
@ -72,6 +76,7 @@ type DataTreeSeed = {
export class DataTreeFactory { export class DataTreeFactory {
static create({ static create({
actions, actions,
actionDrafts,
widgets, widgets,
widgetsMeta, widgetsMeta,
pageList, pageList,
@ -84,8 +89,23 @@ export class DataTreeFactory {
"closeModal", "closeModal",
]; ];
actions.forEach(a => { actions.forEach(a => {
dataTree[a.config.name] = { const config =
a.config.id in actionDrafts ? actionDrafts[a.config.id] : a.config;
let dynamicBindingPathList: Property[] = [];
// update paths
if (
config.dynamicBindingPathList &&
config.dynamicBindingPathList.length
) {
dynamicBindingPathList = config.dynamicBindingPathList.map(d => ({
...d,
key: `config.${d.key}`,
}));
}
dataTree[config.name] = {
...a, ...a,
config: config,
dynamicBindingPathList,
data: a.data ? a.data.body : {}, data: a.data ? a.data.body : {},
run: function(onSuccess: string, onError: string) { run: function(onSuccess: string, onError: string) {
return { return {
@ -99,7 +119,7 @@ export class DataTreeFactory {
}, },
ENTITY_TYPE: ENTITY_TYPE.ACTION, ENTITY_TYPE: ENTITY_TYPE.ACTION,
}; };
dataTree.actionPaths && dataTree.actionPaths.push(`${a.config.name}.run`); dataTree.actionPaths && dataTree.actionPaths.push(`${config.name}.run`);
}); });
Object.keys(widgets).forEach(w => { Object.keys(widgets).forEach(w => {
const widget = widgets[w]; const widget = widgets[w];

View File

@ -312,39 +312,34 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
chartType: "LINE_CHART", chartType: "LINE_CHART",
chartName: "Sales on working days", chartName: "Sales on working days",
allowHorizontalScroll: false, allowHorizontalScroll: false,
chartData: [ singleChartData: [
{ {
seriesName: "", x: "Mon",
data: [ y: 10000,
{ },
x: "Mon", {
y: 10000, x: "Tue",
}, y: 12000,
{ },
x: "Tue", {
y: 12000, x: "Wed",
}, y: 32000,
{ },
x: "Wed", {
y: 32000, x: "Thu",
}, y: 28000,
{ },
x: "Thu", {
y: 28000, x: "Fri",
}, y: 14000,
{ },
x: "Fri", {
y: 14000, x: "Sat",
}, y: 19000,
{ },
x: "Sat", {
y: 19000, x: "Sun",
}, y: 36000,
{
x: "Sun",
y: 36000,
},
],
}, },
], ],
xAxisName: "Last Week", xAxisName: "Last Week",

View File

@ -20,6 +20,7 @@ export const AppViewerHeader = (props: AppViewerHeaderProps) => {
<HeaderWrapper> <HeaderWrapper>
{props.url && ( {props.url && (
<Button <Button
className="t--back-to-editor"
href={props.url} href={props.url}
intent="primary" intent="primary"
icon="chevron-left" icon="chevron-left"

View File

@ -9,29 +9,27 @@ import {
import { import {
HTTP_METHOD_OPTIONS, HTTP_METHOD_OPTIONS,
HTTP_METHODS, HTTP_METHODS,
CONTENT_TYPE,
} from "constants/ApiEditorConstants"; } from "constants/ApiEditorConstants";
import styled from "styled-components"; import styled from "styled-components";
import PostBodyData from "./PostBodyData";
import FormLabel from "components/editorComponents/FormLabel"; import FormLabel from "components/editorComponents/FormLabel";
import FormRow from "components/editorComponents/FormRow"; import FormRow from "components/editorComponents/FormRow";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import { RestAction, PaginationField } from "api/ActionAPI"; import { PaginationField } from "api/ActionAPI";
import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxActionTypes } from "constants/ReduxActionConstants";
import TextField from "components/editorComponents/form/fields/TextField"; import TextField from "components/editorComponents/form/fields/TextField";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import DropdownField from "components/editorComponents/form/fields/DropdownField"; import DropdownField from "components/editorComponents/form/fields/DropdownField";
import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField"; import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField";
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
import ApiResponseView from "components/editorComponents/ApiResponseView";
import { API_EDITOR_FORM_NAME } from "constants/forms"; import { API_EDITOR_FORM_NAME } from "constants/forms";
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen"; import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
import { FormIcons } from "icons/FormIcons";
import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView"; import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView";
import Pagination, { PaginationType } from "./Pagination"; import Pagination from "./Pagination";
import { PaginationType, RestAction } from "entities/Action";
import { Icon } from "@blueprintjs/core"; import { Icon } from "@blueprintjs/core";
import { HelpMap, HelpBaseURL } from "constants/HelpConstants"; import { HelpMap, HelpBaseURL } from "constants/HelpConstants";
import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp"; import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp";
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
import PostBodyData from "./PostBodyData";
import ApiResponseView from "components/editorComponents/ApiResponseView";
const Form = styled.form` const Form = styled.form`
display: flex; display: flex;
@ -59,23 +57,6 @@ const MainConfiguration = styled.div`
padding-left: 17px; padding-left: 17px;
`; `;
const SecondaryWrapper = styled.div`
display: flex;
height: 100%;
border-top: 1px solid #d0d7dd;
margin-top: 15px;
`;
const RequestParamsWrapper = styled.div`
flex: 4;
border-right: 1px solid #d0d7dd;
height: 100%;
overflow-y: auto;
padding-top: 6px;
padding-left: 17px;
padding-right: 10px;
`;
const ActionButtons = styled.div` const ActionButtons = styled.div`
flex: 1; flex: 1;
`; `;
@ -88,13 +69,15 @@ const ActionButton = styled(BaseButton)`
} }
`; `;
const HeadersSection = styled.div`
margin-bottom: 32px;
`;
const DatasourceWrapper = styled.div` const DatasourceWrapper = styled.div`
width: 100%; width: 100%;
max-width: 320px; `;
const SecondaryWrapper = styled.div`
display: flex;
height: 100%;
border-top: 1px solid #d0d7dd;
margin-top: 15px;
`; `;
const TabbedViewContainer = styled.div` const TabbedViewContainer = styled.div`
@ -113,6 +96,19 @@ const StyledOpenDocsIcon = styled(Icon)`
height: 18px; height: 18px;
} }
`; `;
const RequestParamsWrapper = styled.div`
flex: 4;
border-right: 1px solid #d0d7dd;
height: 100%;
overflow-y: auto;
padding-top: 6px;
padding-left: 17px;
padding-right: 10px;
`;
const HeadersSection = styled.div`
margin-bottom: 32px;
`;
interface APIFormProps { interface APIFormProps {
pluginId: string; pluginId: string;
@ -126,18 +122,16 @@ interface APIFormProps {
isDeleting: boolean; isDeleting: boolean;
paginationType: PaginationType; paginationType: PaginationType;
appName: string; appName: string;
actionConfiguration?: any;
httpMethodFromForm: string; httpMethodFromForm: string;
actionConfigurationBody: object | string; actionConfigurationBody: object | string;
actionConfigurationHeaders?: any; actionConfigurationHeaders?: any;
contentType: { actionName: string;
key: string; apiId: string;
value: string;
};
location: { location: {
pathname: string; pathname: string;
}; };
dispatch: any; dispatch: any;
datasourceFieldText: string;
} }
type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>; type Props = APIFormProps & InjectedFormProps<RestAction, APIFormProps>;
@ -153,15 +147,14 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
isDeleting, isDeleting,
isRunning, isRunning,
isSaving, isSaving,
actionConfiguration,
actionConfigurationHeaders, actionConfigurationHeaders,
actionConfigurationBody, actionConfigurationBody,
httpMethodFromForm, httpMethodFromForm,
actionName,
location, location,
dispatch, dispatch,
apiId,
} = props; } = props;
const allowPostBody =
httpMethodFromForm && httpMethodFromForm !== HTTP_METHODS[0];
useEffect(() => { useEffect(() => {
dispatch({ dispatch({
type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE, type: ReduxActionTypes.SET_LAST_USED_EDITOR_PAGE,
@ -170,6 +163,8 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
}, },
}); });
}); });
const allowPostBody =
httpMethodFromForm && httpMethodFromForm !== HTTP_METHODS[0];
return ( return (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@ -219,20 +214,13 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
/> />
<DatasourceWrapper className="t--dataSourceField"> <DatasourceWrapper className="t--dataSourceField">
<DatasourcesField <DatasourcesField
name="datasource.id" key={apiId}
name="datasource"
pluginId={pluginId} pluginId={pluginId}
datasourceFieldText={props.datasourceFieldText}
appName={props.appName} appName={props.appName}
/> />
</DatasourceWrapper> </DatasourceWrapper>
<DynamicTextField
className="t--path"
placeholder="v1/method"
name="actionConfiguration.path"
leftIcon={FormIcons.SLASH_ICON}
normalize={value => value.trim()}
singleLine
setMaxHeight
/>
</FormRow> </FormRow>
</MainConfiguration> </MainConfiguration>
<SecondaryWrapper> <SecondaryWrapper>
@ -252,23 +240,23 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
{" Learn How "} {" Learn How "}
<StyledOpenDocsIcon icon="document-open"></StyledOpenDocsIcon> <StyledOpenDocsIcon icon="document-open" />
</a> </a>
</CollapsibleHelp> </CollapsibleHelp>
<HeadersSection> <HeadersSection>
<KeyValueFieldArray <KeyValueFieldArray
name="actionConfiguration.headers" name="actionConfiguration.headers"
label="Headers" label="Headers"
actionConfig={ actionConfig={actionConfigurationHeaders}
actionConfiguration && actionConfigurationHeaders
}
placeholder="Value" placeholder="Value"
dataTreePath={`${actionName}.config.actionConfiguration.headers`}
pushFields pushFields
/> />
</HeadersSection> </HeadersSection>
<KeyValueFieldArray <KeyValueFieldArray
name="actionConfiguration.queryParameters" name="actionConfiguration.queryParameters"
label="Params" label="Params"
dataTreePath={`${actionName}.config.actionConfiguration.queryParameters`}
pushFields pushFields
/> />
{allowPostBody && ( {allowPostBody && (
@ -276,6 +264,7 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
actionConfigurationHeaders={actionConfigurationHeaders} actionConfigurationHeaders={actionConfigurationHeaders}
actionConfiguration={actionConfigurationBody} actionConfiguration={actionConfigurationBody}
change={props.change} change={props.change}
dataTreePath={`${actionName}.config.actionConfiguration.body`}
/> />
)} )}
</RequestParamsWrapper> </RequestParamsWrapper>
@ -305,24 +294,19 @@ const selector = formValueSelector(API_EDITOR_FORM_NAME);
export default connect(state => { export default connect(state => {
const httpMethodFromForm = selector(state, "actionConfiguration.httpMethod"); const httpMethodFromForm = selector(state, "actionConfiguration.httpMethod");
const actionConfiguration = selector(state, "actionConfiguration");
const actionConfigurationBody = selector(state, "actionConfiguration.body"); const actionConfigurationBody = selector(state, "actionConfiguration.body");
const actionName = selector(state, "name");
const actionConfigurationHeaders = selector( const actionConfigurationHeaders = selector(
state, state,
"actionConfiguration.headers", "actionConfiguration.headers",
); );
let contentType; const apiId = selector(state, "id");
if (actionConfigurationHeaders) {
contentType = actionConfigurationHeaders.find(
(header: any) => header.key.toLowerCase() === CONTENT_TYPE,
);
}
return { return {
actionName,
apiId,
httpMethodFromForm, httpMethodFromForm,
actionConfiguration,
actionConfigurationBody, actionConfigurationBody,
contentType,
actionConfigurationHeaders, actionConfigurationHeaders,
}; };
})( })(

View File

@ -6,6 +6,8 @@ import DropdownField from "components/editorComponents/form/fields/DropdownField
import FormRow from "components/editorComponents/FormRow"; import FormRow from "components/editorComponents/FormRow";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import CalloutComponent from "components/designSystems/blueprint/CalloutComponent"; import CalloutComponent from "components/designSystems/blueprint/CalloutComponent";
import { PaginationType } from "entities/Action";
interface PaginationProps { interface PaginationProps {
onTestClick: Function; onTestClick: Function;
paginationType: PaginationType; paginationType: PaginationType;
@ -15,12 +17,6 @@ const PaginationFieldWrapper = styled.div`
margin-bottom: 5px; margin-bottom: 5px;
`; `;
export enum PaginationType {
"NONE" = "NONE",
"PAGE_NO" = "PAGE_NO",
"URL" = "URL",
}
const ExampleApi = styled.p` const ExampleApi = styled.p`
color: #ef7b63; color: #ef7b63;
font-family: monospace; font-family: monospace;

View File

@ -14,6 +14,7 @@ import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValue
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxActionTypes } from "constants/ReduxActionConstants";
import FIELD_VALUES from "constants/FieldExpectedValue";
const DropDownContainer = styled.div` const DropDownContainer = styled.div`
width: 300px; width: 300px;
@ -45,6 +46,7 @@ interface PostDataProps {
onDisplayFormatChange: Function; onDisplayFormatChange: Function;
apiId: string; apiId: string;
setDisplayFormat: Function; setDisplayFormat: Function;
dataTreePath: string;
} }
type Props = PostDataProps; type Props = PostDataProps;
@ -56,6 +58,7 @@ const PostBodyData = (props: Props) => {
displayFormat, displayFormat,
setDisplayFormat, setDisplayFormat,
apiId, apiId,
dataTreePath,
} = props; } = props;
return ( return (
<PostbodyContainer> <PostbodyContainer>
@ -110,14 +113,13 @@ const PostBodyData = (props: Props) => {
<React.Fragment> <React.Fragment>
<JSONEditorFieldWrapper className={"t--apiFormPostBody"}> <JSONEditorFieldWrapper className={"t--apiFormPostBody"}>
<DynamicTextField <DynamicTextField
expected={FIELD_VALUES.API_ACTION.body}
name="actionConfiguration.body[0]" name="actionConfiguration.body[0]"
height={300} height={300}
showLineNumbers showLineNumbers
allowTabIndent allowTabIndent
singleLine={false} singleLine={false}
placeholder={ dataTreePath={`${dataTreePath}[0]`}
'Please enter this POST request\'s JSON body.\n\n\nDid you know?\n\tIn Appsmith, we can use a widget\'s or API\'s property dynamically, using {{ }} templates.\n\n\tFor example: If we have an input widget named Input1 in which the user would provide their name \n\tand this post body structure should be { "name": "<text from Input1>" } \n\tWe can access it in this post body using { "name": "{{Input1.text}}" }'
}
/> />
</JSONEditorFieldWrapper> </JSONEditorFieldWrapper>
</React.Fragment> </React.Fragment>
@ -125,7 +127,11 @@ const PostBodyData = (props: Props) => {
{displayFormat?.value === POST_BODY_FORMAT_OPTIONS[1].value && ( {displayFormat?.value === POST_BODY_FORMAT_OPTIONS[1].value && (
<React.Fragment> <React.Fragment>
<KeyValueFieldArray name="actionConfiguration.body[1]" label="" /> <KeyValueFieldArray
name="actionConfiguration.body[1]"
dataTreePath={`${dataTreePath}[1]`}
label=""
/>
</React.Fragment> </React.Fragment>
)} )}
@ -137,6 +143,7 @@ const PostBodyData = (props: Props) => {
height={300} height={300}
allowTabIndent allowTabIndent
singleLine={false} singleLine={false}
dataTreePath={`${dataTreePath}[2]`}
/> />
</JSONEditorFieldWrapper> </JSONEditorFieldWrapper>
</React.Fragment> </React.Fragment>

View File

@ -11,12 +11,7 @@ import styled from "styled-components";
import FormLabel from "components/editorComponents/FormLabel"; import FormLabel from "components/editorComponents/FormLabel";
import FormRow from "components/editorComponents/FormRow"; import FormRow from "components/editorComponents/FormRow";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import { import { PaginationField, BodyFormData, Property } from "api/ActionAPI";
RestAction,
PaginationField,
BodyFormData,
Property,
} from "api/ActionAPI";
import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxActionTypes } from "constants/ReduxActionConstants";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray"; import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
@ -26,7 +21,8 @@ import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScre
import CredentialsTooltip from "components/editorComponents/form/CredentialsTooltip"; import CredentialsTooltip from "components/editorComponents/form/CredentialsTooltip";
import { FormIcons } from "icons/FormIcons"; import { FormIcons } from "icons/FormIcons";
import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView"; import { BaseTabbedView } from "components/designSystems/appsmith/TabbedView";
import Pagination, { PaginationType } from "./Pagination"; import Pagination from "./Pagination";
import { PaginationType, RestAction } from "entities/Action";
const Form = styled.form` const Form = styled.form`
display: flex; display: flex;

View File

@ -9,7 +9,7 @@ import {
deleteAction, deleteAction,
updateAction, updateAction,
} from "actions/actionActions"; } from "actions/actionActions";
import { PaginationField, RapidApiAction, RestAction } from "api/ActionAPI"; import { PaginationField } from "api/ActionAPI";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { RouteComponentProps } from "react-router"; import { RouteComponentProps } from "react-router";
import { API_EDITOR_FORM_NAME } from "constants/forms"; import { API_EDITOR_FORM_NAME } from "constants/forms";
@ -17,7 +17,6 @@ import {
ActionData, ActionData,
ActionDataState, ActionDataState,
} from "reducers/entityReducers/actionsReducer"; } from "reducers/entityReducers/actionsReducer";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants"; import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants";
import _ from "lodash"; import _ from "lodash";
import { getCurrentApplication } from "selectors/applicationSelectors"; import { getCurrentApplication } from "selectors/applicationSelectors";
@ -26,6 +25,7 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import { getActionById, getCurrentPageName } from "selectors/editorSelectors"; import { getActionById, getCurrentPageName } from "selectors/editorSelectors";
import { Plugin } from "api/PluginApi"; import { Plugin } from "api/PluginApi";
import styled from "styled-components"; import styled from "styled-components";
import { RapidApiAction, RestAction, PaginationType } from "entities/Action";
import FeatureFlag from "utils/featureFlags"; import FeatureFlag from "utils/featureFlags";
import { FeatureFlagsEnum } from "configs/types"; import { FeatureFlagsEnum } from "configs/types";
@ -39,15 +39,19 @@ const EmptyStateContainer = styled.div`
interface ReduxStateProps { interface ReduxStateProps {
actions: ActionDataState; actions: ActionDataState;
apiPane: ApiPaneReduxState; isRunning: Record<string, boolean>;
formData: RestAction; isSaving: Record<string, boolean>;
isDeleting: Record<string, boolean>;
allowSave: boolean;
apiName: string;
currentApplication: UserApplication; currentApplication: UserApplication;
currentPageName: string | undefined; currentPageName: string | undefined;
pages: any; pages: any;
plugins: Plugin[]; plugins: Plugin[];
pluginId: any; pluginId: any;
apiAction: RestAction | ActionData | RapidApiAction | undefined; apiAction: RestAction | ActionData | RapidApiAction | undefined;
data: RestAction | ActionData | RapidApiAction | undefined; paginationType: PaginationType;
datasourceFieldText: string;
} }
interface ReduxActionProps { interface ReduxActionProps {
submitForm: (name: string) => void; submitForm: (name: string) => void;
@ -67,35 +71,42 @@ type Props = ReduxActionProps &
class ApiEditor extends React.Component<Props> { class ApiEditor extends React.Component<Props> {
handleSubmit = (values: RestAction) => { handleSubmit = (values: RestAction) => {
const { formData } = this.props; this.props.updateAction(values);
this.props.updateAction(formData);
}; };
handleSaveClick = () => { handleSaveClick = () => {
const pageName = getPageName(this.props.pages, this.props.formData.pageId); const pageName = getPageName(
this.props.pages,
this.props.match.params.pageId,
);
AnalyticsUtil.logEvent("SAVE_API_CLICK", { AnalyticsUtil.logEvent("SAVE_API_CLICK", {
apiName: this.props.formData.name, apiName: this.props.apiName,
apiID: this.props.match.params.apiId, apiID: this.props.match.params.apiId,
pageName: pageName, pageName: pageName,
}); });
this.props.submitForm(API_EDITOR_FORM_NAME); this.props.submitForm(API_EDITOR_FORM_NAME);
}; };
handleDeleteClick = () => { handleDeleteClick = () => {
const pageName = getPageName(this.props.pages, this.props.formData.pageId); const pageName = getPageName(
this.props.pages,
this.props.match.params.pageId,
);
AnalyticsUtil.logEvent("DELETE_API_CLICK", { AnalyticsUtil.logEvent("DELETE_API_CLICK", {
apiName: this.props.formData.name, apiName: this.props.apiName,
apiID: this.props.match.params.apiId, apiID: this.props.match.params.apiId,
pageName: pageName, pageName: pageName,
}); });
this.props.deleteAction( this.props.deleteAction(this.props.match.params.apiId, this.props.apiName);
this.props.match.params.apiId,
this.props.formData.name,
);
}; };
handleRunClick = (paginationField?: PaginationField) => { handleRunClick = (paginationField?: PaginationField) => {
const pageName = getPageName(this.props.pages, this.props.formData.pageId); const pageName = getPageName(
this.props.pages,
this.props.match.params.pageId,
);
AnalyticsUtil.logEvent("RUN_API_CLICK", { AnalyticsUtil.logEvent("RUN_API_CLICK", {
apiName: this.props.formData.name, apiName: this.props.apiName,
apiID: this.props.match.params.apiId, apiID: this.props.match.params.apiId,
pageName: pageName, pageName: pageName,
}); });
@ -130,13 +141,16 @@ class ApiEditor extends React.Component<Props> {
render() { render() {
const { const {
apiPane,
match: { match: {
params: { apiId }, params: { apiId },
}, },
plugins, plugins,
pluginId, pluginId,
data, isSaving,
isRunning,
isDeleting,
allowSave,
paginationType,
} = this.props; } = this.props;
let formUiComponent: string | undefined; let formUiComponent: string | undefined;
@ -148,8 +162,6 @@ class ApiEditor extends React.Component<Props> {
} }
} }
const { isSaving, isRunning, isDeleting, drafts } = apiPane;
const paginationType = _.get(data, "actionConfiguration.paginationType");
const apiHomeScreen = ( const apiHomeScreen = (
<ApiHomeScreen <ApiHomeScreen
applicationId={this.props.match.params.applicationId} applicationId={this.props.match.params.applicationId}
@ -177,7 +189,7 @@ class ApiEditor extends React.Component<Props> {
{formUiComponent === "ApiEditorForm" && ( {formUiComponent === "ApiEditorForm" && (
<ApiEditorForm <ApiEditorForm
pluginId={pluginId} pluginId={pluginId}
allowSave={apiId in drafts} allowSave={allowSave}
paginationType={paginationType} paginationType={paginationType}
isSaving={isSaving[apiId]} isSaving={isSaving[apiId]}
isRunning={isRunning[apiId]} isRunning={isRunning[apiId]}
@ -186,6 +198,7 @@ class ApiEditor extends React.Component<Props> {
onSaveClick={this.handleSaveClick} onSaveClick={this.handleSaveClick}
onDeleteClick={this.handleDeleteClick} onDeleteClick={this.handleDeleteClick}
onRunClick={this.handleRunClick} onRunClick={this.handleRunClick}
datasourceFieldText={this.props.datasourceFieldText}
appName={ appName={
this.props.currentApplication this.props.currentApplication
? this.props.currentApplication.name ? this.props.currentApplication.name
@ -197,7 +210,7 @@ class ApiEditor extends React.Component<Props> {
{formUiComponent === "RapidApiEditorForm" && ( {formUiComponent === "RapidApiEditorForm" && (
<RapidApiEditorForm <RapidApiEditorForm
allowSave={apiId in drafts} allowSave={allowSave}
paginationType={paginationType} paginationType={paginationType}
isSaving={isSaving[apiId]} isSaving={isSaving[apiId]}
isRunning={isRunning[apiId]} isRunning={isRunning[apiId]}
@ -227,25 +240,34 @@ const mapStateToProps = (state: AppState, props: any): ReduxStateProps => {
const formData = getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction; const formData = getFormValues(API_EDITOR_FORM_NAME)(state) as RestAction;
const apiAction = getActionById(state, props); const apiAction = getActionById(state, props);
const { drafts } = state.ui.apiPane; const { drafts, isSaving, isDeleting, isRunning } = state.ui.apiPane;
let data: RestAction | ActionData | RapidApiAction | undefined; let data: RestAction | ActionData | RapidApiAction | undefined;
let allowSave;
if (apiAction && apiAction.id in drafts) { if (apiAction && apiAction.id in drafts) {
data = drafts[apiAction.id]; data = drafts[apiAction.id];
allowSave = true;
} else { } else {
data = apiAction; data = apiAction;
allowSave = false;
} }
const datasourceFieldText =
state.ui.apiPane.datasourceFieldText[formData?.id ?? ""] || "";
return { return {
datasourceFieldText,
actions: state.entities.actions, actions: state.entities.actions,
apiPane: state.ui.apiPane,
currentApplication: getCurrentApplication(state), currentApplication: getCurrentApplication(state),
currentPageName: getCurrentPageName(state), currentPageName: getCurrentPageName(state),
pages: state.entities.pageList.pages, pages: state.entities.pageList.pages,
formData, apiName: formData?.name || "",
data,
plugins: state.entities.plugins.list, plugins: state.entities.plugins.list,
pluginId: _.get(data, "pluginId"), pluginId: _.get(data, "pluginId"),
paginationType: _.get(data, "actionConfiguration.paginationType"),
apiAction, apiAction,
isSaving,
isRunning,
isDeleting,
allowSave,
}; };
}; };

View File

@ -19,11 +19,11 @@ import {
createNewApiAction, createNewApiAction,
initApiPane, initApiPane,
} from "actions/apiPaneActions"; } from "actions/apiPaneActions";
import { RestAction } from "api/ActionAPI";
import EditorSidebar from "pages/Editor/EditorSidebar"; import EditorSidebar from "pages/Editor/EditorSidebar";
import { getNextEntityName } from "utils/AppsmithUtils"; import { getNextEntityName } from "utils/AppsmithUtils";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { Page } from "constants/ReduxActionConstants"; import { Page } from "constants/ReduxActionConstants";
import { RestAction } from "entities/Action";
import { FeatureFlagsEnum } from "configs/types"; import { FeatureFlagsEnum } from "configs/types";
import FeatureFlag from "utils/featureFlags"; import FeatureFlag from "utils/featureFlags";

View File

@ -41,6 +41,7 @@ interface DatasourceDBEditorProps {
isTesting: boolean; isTesting: boolean;
loadingFormConfigs: boolean; loadingFormConfigs: boolean;
formConfig: []; formConfig: [];
isNewDatasource: boolean;
} }
interface DatasourceDBEditorState { interface DatasourceDBEditorState {
@ -327,7 +328,7 @@ class DatasourceDBEditor extends React.Component<
<Field <Field
name="name" name="name"
component={FormTitle} component={FormTitle}
focusOnMount={this.isNewDatasource()} focusOnMount={this.props.isNewDatasource}
/> />
</FormTitleContainer> </FormTitleContainer>
{!_.isNil(sections) {!_.isNil(sections)

View File

@ -26,6 +26,7 @@ interface ReduxStateProps {
formConfig: []; formConfig: [];
loadingFormConfigs: boolean; loadingFormConfigs: boolean;
isDeleting: boolean; isDeleting: boolean;
newDatasource: string;
} }
type Props = ReduxStateProps & type Props = ReduxStateProps &
@ -58,6 +59,7 @@ class DataSourceEditor extends React.Component<Props> {
formConfig, formConfig,
isDeleting, isDeleting,
deleteDatasource, deleteDatasource,
newDatasource,
} = this.props; } = this.props;
return ( return (
@ -69,6 +71,7 @@ class DataSourceEditor extends React.Component<Props> {
isSaving={isSaving} isSaving={isSaving}
isTesting={isTesting} isTesting={isTesting}
isDeleting={isDeleting} isDeleting={isDeleting}
isNewDatasource={newDatasource === datasourceId}
onSubmit={this.handleSubmit} onSubmit={this.handleSubmit}
onSave={this.handleSave} onSave={this.handleSave}
onTest={this.props.testDatasource} onTest={this.props.testDatasource}
@ -112,6 +115,7 @@ const mapStateToProps = (state: AppState): ReduxStateProps => {
isTesting: datasources.isTesting, isTesting: datasources.isTesting,
formConfig: formConfigs[datasourcePane.selectedPlugin] || [], formConfig: formConfigs[datasourcePane.selectedPlugin] || [],
loadingFormConfigs, loadingFormConfigs,
newDatasource: datasourcePane.newDatasource,
}; };
}; };

View File

@ -1,47 +1,20 @@
import React, { useRef, useEffect } from "react"; import React, { useRef, useEffect } from "react";
import styled from "styled-components"; import styled from "styled-components";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import PopperJS from "popper.js"; import PopperJS, { Placement, PopperOptions } from "popper.js";
import PaneWrapper from "pages/common/PaneWrapper";
import { getColorWithOpacity } from "constants/DefaultTheme";
type PopperProps = { type PopperProps = {
zIndex: number;
isOpen: boolean; isOpen: boolean;
targetNode?: Element; targetNode?: Element;
children: JSX.Element; children: JSX.Element;
placement: Placement;
modifiers?: Partial<PopperOptions["modifiers"]>;
}; };
const PopperWrapper = styled(PaneWrapper)` const PopperWrapper = styled.div<{ zIndex: number }>`
z-index: ${props => props.zIndex};
position: absolute; position: absolute;
z-index: 3;
max-height: ${props => props.theme.propertyPane.height}px;
width: ${props => props.theme.propertyPane.width}px;
margin: ${props => props.theme.spaces[2]}px;
box-shadow: 0px 0px 10px ${props => props.theme.colors.paneCard};
border: ${props => props.theme.spaces[5]}px solid
${props => props.theme.colors.paneBG};
border-right: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 0 ${props => props.theme.spaces[5]}px 0 0;
text-transform: none;
scrollbar-color: ${props => props.theme.colors.paneCard}
${props => props.theme.colors.paneBG};
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px
${props => getColorWithOpacity(props.theme.colors.paneBG, 0.3)};
}
&::-webkit-scrollbar-thumb {
background-color: ${props => props.theme.colors.paneCard};
outline: 1px solid ${props => props.theme.paneText};
border-radius: ${props => props.theme.radii[1]}px;
}
`; `;
/* eslint-disable react/display-name */ /* eslint-disable react/display-name */
@ -64,7 +37,7 @@ export default (props: PopperProps) => {
props.targetNode, props.targetNode,
(contentRef.current as unknown) as Element, (contentRef.current as unknown) as Element,
{ {
placement: "right-start", placement: props.placement,
modifiers: { modifiers: {
flip: { flip: {
behavior: ["right", "left", "bottom", "top"], behavior: ["right", "left", "bottom", "top"],
@ -79,6 +52,7 @@ export default (props: PopperProps) => {
enabled: true, enabled: true,
boundariesElement: "viewport", boundariesElement: "viewport",
}, },
...props.modifiers,
}, },
}, },
); );
@ -87,9 +61,11 @@ export default (props: PopperProps) => {
_popper.destroy(); _popper.destroy();
}; };
} }
}, [props.targetNode, props.isOpen]); }, [props.targetNode, props.isOpen, props.modifiers, props.placement]);
return createPortal( return createPortal(
<PopperWrapper ref={contentRef}>{props.children}</PopperWrapper>, <PopperWrapper ref={contentRef} zIndex={props.zIndex}>
{props.children}
</PopperWrapper>,
document.body, document.body,
); );
}; };

View File

@ -10,6 +10,7 @@ import PropertyControlFactory from "utils/PropertyControlFactory";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { PropertyControlPropsType } from "components/propertyControls"; import { PropertyControlPropsType } from "components/propertyControls";
import { Tooltip, Position } from "@blueprintjs/core"; import { Tooltip, Position } from "@blueprintjs/core";
import FIELD_EXPECTED_VALUE from "constants/FieldExpectedValue";
type Props = { type Props = {
widgetProperties: WidgetProps; widgetProperties: WidgetProps;
@ -63,7 +64,7 @@ function UnderlinedLabel({
} }
: {} : {}
} }
></span> />
</div> </div>
</Tooltip> </Tooltip>
); );
@ -98,8 +99,16 @@ const PropertyControl = (props: Props) => {
const { propertyName, label } = propertyConfig; const { propertyName, label } = propertyConfig;
if (widgetProperties) { if (widgetProperties) {
const propertyValue = widgetProperties[propertyName]; const propertyValue = widgetProperties[propertyName];
const validation = getPropertyValidation(propertyName); const dataTreePath = `${widgetProperties.widgetName}.evaluatedValues.${propertyName}`;
const config = { ...propertyConfig, ...validation, propertyValue }; const { isValid, validationMessage } = getPropertyValidation(propertyName);
const config = {
...propertyConfig,
isValid,
propertyValue,
validationMessage,
dataTreePath,
expected: FIELD_EXPECTED_VALUE[widgetProperties.type][propertyName],
};
const isDynamic: boolean = _.get( const isDynamic: boolean = _.get(
widgetProperties, widgetProperties,
["dynamicProperties", propertyName], ["dynamicProperties", propertyName],
@ -122,10 +131,7 @@ const PropertyControl = (props: Props) => {
} }
> >
<ControlPropertyLabelContainer> <ControlPropertyLabelContainer>
<UnderlinedLabel <UnderlinedLabel tooltip={propertyConfig.helpText} label={label} />
tooltip={propertyConfig.helpText}
label={label}
></UnderlinedLabel>
{isConvertible && ( {isConvertible && (
<JSToggleButton <JSToggleButton

View File

@ -22,12 +22,13 @@ import { generateClassName } from "utils/generators";
import { RenderModes } from "constants/WidgetConstants"; import { RenderModes } from "constants/WidgetConstants";
import { ReduxActionTypes } from "constants/ReduxActionConstants"; import { ReduxActionTypes } from "constants/ReduxActionConstants";
import { CloseButton } from "components/designSystems/blueprint/CloseButton"; import { CloseButton } from "components/designSystems/blueprint/CloseButton";
import { theme } from "constants/DefaultTheme"; import { getColorWithOpacity, theme } from "constants/DefaultTheme";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import PropertyPaneTitle from "pages/Editor/PropertyPaneTitle"; import PropertyPaneTitle from "pages/Editor/PropertyPaneTitle";
import PropertyControl from "pages/Editor/PropertyPane/PropertyControl"; import PropertyControl from "pages/Editor/PropertyPane/PropertyControl";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import * as log from "loglevel"; import * as log from "loglevel";
import PaneWrapper from "pages/common/PaneWrapper";
import { BindingText } from "pages/Editor/APIEditor/Form"; import { BindingText } from "pages/Editor/APIEditor/Form";
const PropertySectionLabel = styled.div` const PropertySectionLabel = styled.div`
@ -40,9 +41,38 @@ const PropertySectionLabel = styled.div`
align-items: center; align-items: center;
`; `;
const PropertyPaneWrapper = styled.div` const PropertyPaneWrapper = styled(PaneWrapper)`
position: relative; position: relative;
width: 100%; width: 100%;
max-height: ${props => props.theme.propertyPane.height}px;
width: ${props => props.theme.propertyPane.width}px;
margin: ${props => props.theme.spaces[2]}px;
box-shadow: 0px 0px 10px ${props => props.theme.colors.paneCard};
border: ${props => props.theme.spaces[5]}px solid
${props => props.theme.colors.paneBG};
border-right: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 0 ${props => props.theme.spaces[5]}px 0 0;
text-transform: none;
scrollbar-color: ${props => props.theme.colors.paneCard}
${props => props.theme.colors.paneBG};
scrollbar-width: thin;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px
${props => getColorWithOpacity(props.theme.colors.paneBG, 0.3)};
}
&::-webkit-scrollbar-thumb {
background-color: ${props => props.theme.colors.paneCard};
outline: 1px solid ${props => props.theme.paneText};
border-radius: ${props => props.theme.radii[1]}px;
}
`; `;
const StyledToolTip = styled(Tooltip)` const StyledToolTip = styled(Tooltip)`
@ -66,7 +96,12 @@ class PropertyPane extends Component<
generateClassName(this.props.widgetId), generateClassName(this.props.widgetId),
)[0]; )[0];
return ( return (
<Popper isOpen={true} targetNode={el}> <Popper
isOpen={true}
targetNode={el}
zIndex={3}
placement="right-start"
>
{content} {content}
</Popper> </Popper>
); );

View File

@ -25,12 +25,12 @@ import TextField from "components/editorComponents/form/fields/TextField";
import DropdownField from "components/editorComponents/form/fields/DropdownField"; import DropdownField from "components/editorComponents/form/fields/DropdownField";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import { Datasource } from "api/DatasourcesApi"; import { Datasource } from "api/DatasourcesApi";
import { RestAction } from "api/ActionAPI";
import { QUERY_EDITOR_FORM_NAME } from "constants/forms"; import { QUERY_EDITOR_FORM_NAME } from "constants/forms";
import { PLUGIN_PACKAGE_POSTGRES } from "constants/QueryEditorConstants"; import { PLUGIN_PACKAGE_POSTGRES } from "constants/QueryEditorConstants";
import "@syncfusion/ej2-react-grids/styles/material.css"; import "@syncfusion/ej2-react-grids/styles/material.css";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import JSONViewer from "./JSONViewer"; import JSONViewer from "./JSONViewer";
import { RestAction } from "entities/Action";
const QueryFormContainer = styled.div` const QueryFormContainer = styled.div`
font-size: 20px; font-size: 20px;

View File

@ -14,7 +14,6 @@ import {
} from "selectors/entitiesSelector"; } from "selectors/entitiesSelector";
import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import { Datasource } from "api/DatasourcesApi"; import { Datasource } from "api/DatasourcesApi";
import { RestAction } from "api/ActionAPI";
import history from "utils/history"; import history from "utils/history";
import { createActionRequest } from "actions/actionActions"; import { createActionRequest } from "actions/actionActions";
import { import {
@ -29,6 +28,7 @@ import {
DATA_SOURCES_EDITOR_URL, DATA_SOURCES_EDITOR_URL,
} from "constants/routes"; } from "constants/routes";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import { QueryAction } from "entities/Action";
const QueryHomePage = styled.div` const QueryHomePage = styled.div`
font-size: 20px; font-size: 20px;
@ -116,7 +116,7 @@ type QueryHomeScreenProps = {
dataSources: Datasource[]; dataSources: Datasource[];
applicationId: string; applicationId: string;
pageId: string; pageId: string;
createAction: (data: Partial<RestAction>) => void; createAction: (data: Partial<QueryAction>) => void;
actions: ActionDataState; actions: ActionDataState;
pluginIds: Array<string> | undefined; pluginIds: Array<string> | undefined;
isCreating: boolean; isCreating: boolean;
@ -240,7 +240,7 @@ class QueryHomeScreen extends React.Component<QueryHomeScreenProps> {
src={this.getImageSrc(dataSource)} src={this.getImageSrc(dataSource)}
className="dataSourceImage" className="dataSourceImage"
alt="Datasource" alt="Datasource"
></img> />
<p <p
className="textBtn t--datasource-name" className="textBtn t--datasource-name"
@ -268,7 +268,7 @@ const mapStateToProps = (state: AppState) => ({
}); });
const mapDispatchToProps = (dispatch: any) => ({ const mapDispatchToProps = (dispatch: any) => ({
createAction: (data: Partial<RestAction>) => { createAction: (data: Partial<QueryAction>) => {
dispatch(createActionRequest(data)); dispatch(createActionRequest(data));
}, },
}); });

View File

@ -18,7 +18,6 @@ import { AppState } from "reducers";
import { getDataSources } from "selectors/editorSelectors"; import { getDataSources } from "selectors/editorSelectors";
import { QUERY_EDITOR_FORM_NAME } from "constants/forms"; import { QUERY_EDITOR_FORM_NAME } from "constants/forms";
import { Datasource } from "api/DatasourcesApi"; import { Datasource } from "api/DatasourcesApi";
import { RestAction } from "api/ActionAPI";
import { QueryPaneReduxState } from "reducers/uiReducers/queryPaneReducer"; import { QueryPaneReduxState } from "reducers/uiReducers/queryPaneReducer";
import { import {
getPluginIdsOfPackageNames, getPluginIdsOfPackageNames,
@ -27,6 +26,7 @@ import {
import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants"; import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants";
import { getCurrentApplication } from "selectors/applicationSelectors"; import { getCurrentApplication } from "selectors/applicationSelectors";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer"; import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import { RestAction } from "entities/Action";
const EmptyStateContainer = styled.div` const EmptyStateContainer = styled.div`
display: flex; display: flex;

View File

@ -15,12 +15,12 @@ import {
copyActionRequest, copyActionRequest,
} from "actions/actionActions"; } from "actions/actionActions";
import { deleteQuery } from "actions/queryPaneActions"; import { deleteQuery } from "actions/queryPaneActions";
import { RestAction } from "api/ActionAPI";
import { changeQuery, initQueryPane } from "actions/queryPaneActions"; import { changeQuery, initQueryPane } from "actions/queryPaneActions";
import { getQueryActions } from "selectors/entitiesSelector"; import { getQueryActions } from "selectors/entitiesSelector";
import { getNextEntityName } from "utils/AppsmithUtils"; import { getNextEntityName } from "utils/AppsmithUtils";
import { getDataSources } from "selectors/editorSelectors"; import { getDataSources } from "selectors/editorSelectors";
import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes"; import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes";
import { RestAction } from "entities/Action";
const ActionItem = styled.div` const ActionItem = styled.div`
flex: 1; flex: 1;

View File

@ -46,7 +46,7 @@ export type CustomizedDropdownProps = {
openOnHover?: boolean; openOnHover?: boolean;
}; };
const getIcon = (icon?: string, intent?: Intent) => { export const getIcon = (icon?: string, intent?: Intent) => {
if (icon) { if (icon) {
if (MenuIcons[icon]) { if (MenuIcons[icon]) {
return MenuIcons[icon]({ return MenuIcons[icon]({
@ -68,7 +68,7 @@ const getIcon = (icon?: string, intent?: Intent) => {
} }
}; };
const getContentSection = (section: CustomizedDropdownOptionSection) => { export const getContentSection = (section: CustomizedDropdownOptionSection) => {
return ( return (
<React.Fragment> <React.Fragment>
{section.options && {section.options &&

View File

@ -0,0 +1,25 @@
import { createReducer } from "utils/AppsmithUtils";
import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants";
import { Action, RestAction } from "entities/Action";
import _ from "lodash";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
export type ActionDraftsState = Record<string, Action>;
const initialState: ActionDraftsState = {};
const actionDraftsReducer = createReducer(initialState, {
[ReduxActionTypes.UPDATE_API_DRAFT]: (
state: ApiPaneReduxState,
action: ReduxAction<{ id: string; draft: Partial<RestAction> }>,
) => ({
...state,
[action.payload.id]: action.payload.draft,
}),
[ReduxActionTypes.DELETE_API_DRAFT]: (
state: ApiPaneReduxState,
action: ReduxAction<{ id: string }>,
) => _.omit(state, action.payload.id),
});
export default actionDraftsReducer;

View File

@ -4,9 +4,10 @@ import {
ReduxAction, ReduxAction,
ReduxActionErrorTypes, ReduxActionErrorTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { ActionResponse, RapidApiAction, RestAction } from "api/ActionAPI"; import { ActionResponse } from "api/ActionAPI";
import { ExecuteErrorPayload } from "constants/ActionConstants"; import { ExecuteErrorPayload } from "constants/ActionConstants";
import _ from "lodash"; import _ from "lodash";
import { RapidApiAction, RestAction } from "entities/Action";
export interface ActionData { export interface ActionData {
isLoading: boolean; isLoading: boolean;
config: RestAction | RapidApiAction; config: RestAction | RapidApiAction;

View File

@ -9,12 +9,14 @@ import pageListReducer from "./pageListReducer";
import jsExecutionsReducer from "./jsExecutionsReducer"; import jsExecutionsReducer from "./jsExecutionsReducer";
import pluginsReducer from "reducers/entityReducers/pluginsReducer"; import pluginsReducer from "reducers/entityReducers/pluginsReducer";
import metaReducer from "./metaReducer"; import metaReducer from "./metaReducer";
import actionDraftsReducer from "reducers/entityReducers/actionDraftsReducer";
const entityReducer = combineReducers({ const entityReducer = combineReducers({
canvasWidgets: canvasWidgetsReducer, canvasWidgets: canvasWidgetsReducer,
queryData: queryDataReducer, queryData: queryDataReducer,
widgetConfig: widgetConfigReducer, widgetConfig: widgetConfigReducer,
actions: actionsReducer, actions: actionsReducer,
actionDrafts: actionDraftsReducer,
propertyConfig: propertyPaneConfigReducer, propertyConfig: propertyPaneConfigReducer,
datasources: datasourceReducer, datasources: datasourceReducer,
pageList: pageListReducer, pageList: pageListReducer,

View File

@ -27,6 +27,7 @@ import { ImportedCollectionsReduxState } from "reducers/uiReducers/importedColle
import { ProvidersReduxState } from "reducers/uiReducers/providerReducer"; import { ProvidersReduxState } from "reducers/uiReducers/providerReducer";
import { MetaState } from "./entityReducers/metaReducer"; import { MetaState } from "./entityReducers/metaReducer";
import { ImportReduxState } from "reducers/uiReducers/importReducer"; import { ImportReduxState } from "reducers/uiReducers/importReducer";
import { ActionDraftsState } from "reducers/entityReducers/actionDraftsReducer";
import { HelpReduxState } from "./uiReducers/helpReducer"; import { HelpReduxState } from "./uiReducers/helpReducer";
const appReducer = combineReducers({ const appReducer = combineReducers({
@ -61,6 +62,7 @@ export interface AppState {
canvasWidgets: CanvasWidgetsReduxState; canvasWidgets: CanvasWidgetsReduxState;
queryData: QueryDataState; queryData: QueryDataState;
actions: ActionDataState; actions: ActionDataState;
actionDrafts: ActionDraftsState;
propertyConfig: PropertyPaneConfigState; propertyConfig: PropertyPaneConfigState;
widgetConfig: WidgetConfigReducerState; widgetConfig: WidgetConfigReducerState;
datasources: DatasourceDataState; datasources: DatasourceDataState;

View File

@ -4,8 +4,8 @@ import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxAction, ReduxAction,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { RestAction } from "api/ActionAPI";
import _ from "lodash"; import _ from "lodash";
import { RestAction } from "entities/Action";
const initialState: ApiPaneReduxState = { const initialState: ApiPaneReduxState = {
lastUsed: "", lastUsed: "",
@ -18,6 +18,7 @@ const initialState: ApiPaneReduxState = {
lastUsedEditorPage: "", lastUsedEditorPage: "",
lastSelectedPage: "", lastSelectedPage: "",
extraformData: {}, extraformData: {},
datasourceFieldText: {},
}; };
export interface ApiPaneReduxState { export interface ApiPaneReduxState {
@ -29,6 +30,7 @@ export interface ApiPaneReduxState {
isDeleting: Record<string, boolean>; isDeleting: Record<string, boolean>;
currentCategory: string; currentCategory: string;
lastUsedEditorPage: string; lastUsedEditorPage: string;
datasourceFieldText: Record<string, string>;
lastSelectedPage: string; lastSelectedPage: string;
extraformData: Record<string, any>; extraformData: Record<string, any>;
} }
@ -201,6 +203,19 @@ const apiPaneReducer = createReducer(initialState, {
}, },
}; };
}, },
[ReduxActionTypes.SET_DATASOURCE_FIELD_TEXT]: (
state: ApiPaneReduxState,
action: ReduxAction<{ apiId: string; value: string }>,
) => {
const { apiId } = action.payload;
return {
...state,
datasourceFieldText: {
...state.datasourceFieldText,
[apiId]: action.payload.value,
},
};
},
}); });
export default apiPaneReducer; export default apiPaneReducer;

View File

@ -8,12 +8,21 @@ const initialState: DatasourcePaneReduxState = {
selectedPlugin: "", selectedPlugin: "",
datasourceRefs: {}, datasourceRefs: {},
drafts: {}, drafts: {},
actionRouteInfo: {},
newDatasource: "",
}; };
export interface DatasourcePaneReduxState { export interface DatasourcePaneReduxState {
selectedPlugin: string; selectedPlugin: string;
datasourceRefs: {}; datasourceRefs: {};
drafts: Record<string, Datasource>; drafts: Record<string, Datasource>;
actionRouteInfo: Partial<{
apiId: string;
datasourceId: string;
pageId: string;
applicationId: string;
}>;
newDatasource: string;
} }
const datasourcePaneReducer = createReducer(initialState, { const datasourcePaneReducer = createReducer(initialState, {
@ -64,6 +73,43 @@ const datasourcePaneReducer = createReducer(initialState, {
...state, ...state,
drafts: _.omit(state.drafts, action.payload.id), drafts: _.omit(state.drafts, action.payload.id),
}), }),
[ReduxActionTypes.STORE_AS_DATASOURCE_UPDATE]: (
state: DatasourcePaneReduxState,
action: ReduxAction<{
apiId: string;
datasourceId: string;
pageId: string;
applicationId: string;
}>,
) => {
return {
...state,
actionRouteInfo: action.payload,
};
},
[ReduxActionTypes.STORE_AS_DATASOURCE_COMPLETE]: (
state: DatasourcePaneReduxState,
) => ({
...state,
actionRouteInfo: {},
}),
[ReduxActionTypes.CREATE_DATASOURCE_SUCCESS]: (
state: DatasourcePaneReduxState,
action: ReduxAction<{ id: string }>,
) => {
return {
...state,
newDatasource: action.payload.id,
};
},
[ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS]: (
state: DatasourcePaneReduxState,
) => {
return {
...state,
newDatasource: "",
};
},
}); });
export default datasourcePaneReducer; export default datasourcePaneReducer;

View File

@ -13,6 +13,7 @@ const initialState: OrgReduxState = {
isFetchAllUsers: false, isFetchAllUsers: false,
isDeletingOrgUser: false, isDeletingOrgUser: false,
}, },
currentOrgId: "",
orgUsers: [], orgUsers: [],
orgRoles: [], orgRoles: [],
}; };
@ -39,6 +40,7 @@ const orgReducer = createReducer(initialState, {
isFetchAllUsers: true, isFetchAllUsers: true,
}, },
}), }),
[ReduxActionTypes.FETCH_ORG_ROLES_SUCCESS]: ( [ReduxActionTypes.FETCH_ORG_ROLES_SUCCESS]: (
state: OrgReduxState, state: OrgReduxState,
action: ReduxAction<OrgRole[]>, action: ReduxAction<OrgRole[]>,
@ -113,6 +115,13 @@ const orgReducer = createReducer(initialState, {
[ReduxActionTypes.DELETE_ORG_USER_ERROR]: (state: OrgReduxState) => { [ReduxActionTypes.DELETE_ORG_USER_ERROR]: (state: OrgReduxState) => {
return { ...state, isDeletingOrgUser: false }; return { ...state, isDeletingOrgUser: false };
}, },
[ReduxActionTypes.SET_CURRENT_ORG_ID]: (
state: OrgReduxState,
action: ReduxAction<{ orgId: string }>,
) => ({
...state,
currentOrgId: action.payload.orgId,
}),
[ReduxActionTypes.FETCH_ORGS_SUCCESS]: ( [ReduxActionTypes.FETCH_ORGS_SUCCESS]: (
state: OrgReduxState, state: OrgReduxState,
@ -134,6 +143,7 @@ export interface OrgReduxState {
}; };
orgUsers: OrgUser[]; orgUsers: OrgUser[];
orgRoles: any; orgRoles: any;
currentOrgId: string;
} }
export default orgReducer; export default orgReducer;

View File

@ -4,8 +4,8 @@ import {
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxAction, ReduxAction,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { RestAction } from "api/ActionAPI";
import _ from "lodash"; import _ from "lodash";
import { RestAction } from "entities/Action";
const initialState: QueryPaneReduxState = { const initialState: QueryPaneReduxState = {
isFetching: false, isFetching: false,

View File

@ -24,7 +24,6 @@ import ActionAPI, {
ExecuteActionRequest, ExecuteActionRequest,
PaginationField, PaginationField,
Property, Property,
RestAction,
} from "api/ActionAPI"; } from "api/ActionAPI";
import { AppState } from "reducers"; import { AppState } from "reducers";
import _ from "lodash"; import _ from "lodash";
@ -74,6 +73,7 @@ import { ToastType } from "react-toastify";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import * as log from "loglevel"; import * as log from "loglevel";
import { QUERY_CONSTANT } from "constants/QueryEditorConstants"; import { QUERY_CONSTANT } from "constants/QueryEditorConstants";
import { RestAction } from "entities/Action";
export const getAction = ( export const getAction = (
state: AppState, state: AppState,

View File

@ -33,8 +33,8 @@ import {
import { initialize, autofill, change } from "redux-form"; import { initialize, autofill, change } from "redux-form";
import { getAction } from "./ActionSagas"; import { getAction } from "./ActionSagas";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { Property, RestAction } from "api/ActionAPI"; import { Property } from "api/ActionAPI";
import { changeApi } from "actions/apiPaneActions"; import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
import { import {
API_PATH_START_WITH_SLASH_ERROR, API_PATH_START_WITH_SLASH_ERROR,
FIELD_REQUIRED_ERROR, FIELD_REQUIRED_ERROR,
@ -46,6 +46,8 @@ import { getPluginIdOfPackageName } from "sagas/selectors";
import { getActions } from "selectors/entitiesSelector"; import { getActions } from "selectors/entitiesSelector";
import { ActionData } from "reducers/entityReducers/actionsReducer"; import { ActionData } from "reducers/entityReducers/actionsReducer";
import { createActionRequest } from "actions/actionActions"; import { createActionRequest } from "actions/actionActions";
import { RestAction } from "entities/Action";
import { isDynamicValue } from "utils/DynamicBindingUtils";
const getApiDraft = (state: AppState, id: string) => { const getApiDraft = (state: AppState, id: string) => {
const drafts = state.ui.apiPane.drafts; const drafts = state.ui.apiPane.drafts;
@ -147,6 +149,28 @@ function* syncApiParamsSaga(
`${currentPath}${paramsString}`, `${currentPath}${paramsString}`,
), ),
); );
if (
actionPayload.type === ReduxFormActionTypes.VALUE_CHANGE ||
actionPayload.type === ReduxFormActionTypes.ARRAY_REMOVE
) {
if (values.datasource && values.datasource.id) {
yield put(
setDatasourceFieldText(values.id, `${currentPath}${paramsString}`),
);
} else if (
values.datasource &&
values.datasource.datasourceConfiguration
) {
yield put(
setDatasourceFieldText(
values.id,
values.datasource.datasourceConfiguration.url +
`${currentPath}${paramsString}`,
),
);
}
}
} }
} }
@ -191,13 +215,6 @@ function* changeApiSaga(actionPayload: ReduxAction<{ id: string }>) {
data = draft; data = draft;
} }
if (data.actionConfiguration.path) {
if (data.actionConfiguration.path.charAt(0) === "/")
data.actionConfiguration.path = data.actionConfiguration.path.substring(
1,
);
}
if ( if (
data.actionConfiguration.httpMethod !== "GET" && data.actionConfiguration.httpMethod !== "GET" &&
!data.providerId && !data.providerId &&
@ -286,29 +303,23 @@ function* validateInputSaga(
payload, payload,
meta: { field }, meta: { field },
} = actionPayload; } = actionPayload;
if (field === "dynamicBindingPathList") return;
const actions: RestAction[] = yield select(getActionConfigs); const actions: RestAction[] = yield select(getActionConfigs);
const sameNames = actions.filter(
(action: RestAction) => action.name === payload && action.id,
);
if (field === "name") { if (field === "name") {
const sameNames = actions.filter(
(action: RestAction) => action.name === payload && action.id,
);
if (!_.trim(payload)) { if (!_.trim(payload)) {
_.set(errors, field, FIELD_REQUIRED_ERROR); _.set(errors, field, FIELD_REQUIRED_ERROR);
} else if (payload.indexOf(" ") !== -1) { } else if (payload.indexOf(" ") !== -1) {
_.set(errors, field, VALID_FUNCTION_NAME_ERROR); _.set(errors, field, VALID_FUNCTION_NAME_ERROR);
} else if (sameNames.length > 0) { } else if (sameNames.length > 0) {
// TODO Check this
_.set(errors, field, UNIQUE_NAME_ERROR); _.set(errors, field, UNIQUE_NAME_ERROR);
} else { } else {
_.unset(errors, field); _.unset(errors, field);
} }
} }
if (field === "actionConfiguration.path") {
if (payload && payload.startsWith("/")) {
_.set(errors, field, API_PATH_START_WITH_SLASH_ERROR);
} else {
_.unset(errors, field);
}
}
yield put({ yield put({
type: ReduxFormActionTypes.UPDATE_FIELD_ERROR, type: ReduxFormActionTypes.UPDATE_FIELD_ERROR,
meta: { meta: {
@ -391,12 +402,37 @@ function* updateFormFields(
} }
} }
function* updateDynamicBindingsSaga(
actionPayload: ReduxActionWithMeta<string, { field: string }>,
) {
const field = actionPayload.meta.field;
if (field === "dynamicBindingPathList") return;
const value = actionPayload.payload;
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
if (!values.id) return;
const isDynamic = isDynamicValue(value);
let dynamicBindings: Property[] = values.dynamicBindingPathList || [];
const fieldExists = _.some(dynamicBindings, { key: field });
if (!isDynamic && fieldExists) {
dynamicBindings = dynamicBindings.filter(d => d.key !== field);
}
if (isDynamic && !fieldExists) {
dynamicBindings.push({ key: field });
}
yield put(
change(API_EDITOR_FORM_NAME, "dynamicBindingPathList", dynamicBindings),
);
}
function* formValueChangeSaga( function* formValueChangeSaga(
actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>, actionPayload: ReduxActionWithMeta<string, { field: string; form: string }>,
) { ) {
const { form } = actionPayload.meta; const { form } = actionPayload.meta;
if (form !== API_EDITOR_FORM_NAME) return; if (form !== API_EDITOR_FORM_NAME) return;
yield all([ yield all([
call(updateDynamicBindingsSaga, actionPayload),
call(validateInputSaga, actionPayload), call(validateInputSaga, actionPayload),
call(updateDraftsSaga), call(updateDraftsSaga),
call(syncApiParamsSaga, actionPayload), call(syncApiParamsSaga, actionPayload),

View File

@ -1,11 +1,9 @@
import { takeLatest, put, all, select } from "redux-saga/effects"; import { takeLatest, put, all, select, take } from "redux-saga/effects";
import { initialize } from "redux-form";
import { import {
ReduxActionTypes, ReduxActionTypes,
ReduxActionErrorTypes, ReduxActionErrorTypes,
ReduxAction, ReduxAction,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { API_EDITOR_FORM_NAME } from "constants/forms";
import { validateResponse } from "sagas/ErrorSagas"; import { validateResponse } from "sagas/ErrorSagas";
import CurlImportApi, { CurlImportRequest } from "api/ImportApi"; import CurlImportApi, { CurlImportRequest } from "api/ImportApi";
import { ApiResponse } from "api/ApiResponses"; import { ApiResponse } from "api/ApiResponses";
@ -13,14 +11,10 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import { AppToaster } from "components/editorComponents/ToastComponent"; import { AppToaster } from "components/editorComponents/ToastComponent";
import { ToastType } from "react-toastify"; import { ToastType } from "react-toastify";
import { CURL_IMPORT_SUCCESS } from "constants/messages"; import { CURL_IMPORT_SUCCESS } from "constants/messages";
import { API_EDITOR_ID_URL } from "constants/routes"; import { getCurrentApplicationId } from "selectors/editorSelectors";
import history from "utils/history";
import {
getCurrentApplicationId,
getCurrentPageId,
} from "selectors/editorSelectors";
import { fetchActions } from "actions/actionActions"; import { fetchActions } from "actions/actionActions";
import { CURL } from "constants/ApiConstants"; import { CURL } from "constants/ApiConstants";
import { changeApi } from "actions/apiPaneActions";
export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) { export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
const { type, pageId, name } = action.payload; const { type, pageId, name } = action.payload;
@ -35,12 +29,16 @@ export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
const response: ApiResponse = yield CurlImportApi.curlImport(request); const response: ApiResponse = yield CurlImportApi.curlImport(request);
const isValidResponse = yield validateResponse(response); const isValidResponse = yield validateResponse(response);
const applicationId = yield select(getCurrentApplicationId); const applicationId = yield select(getCurrentApplicationId);
const currentPageId = yield select(getCurrentPageId);
if (isValidResponse) { if (isValidResponse) {
AnalyticsUtil.logEvent("IMPORT_API", { AnalyticsUtil.logEvent("IMPORT_API", {
importSource: CURL, importSource: CURL,
}); });
yield put(fetchActions(applicationId));
const data = { ...response.data };
yield take(ReduxActionTypes.FETCH_ACTIONS_SUCCESS);
AppToaster.show({ AppToaster.show({
message: CURL_IMPORT_SUCCESS, message: CURL_IMPORT_SUCCESS,
type: ToastType.SUCCESS, type: ToastType.SUCCESS,
@ -49,12 +47,8 @@ export function* curlImportSaga(action: ReduxAction<CurlImportRequest>) {
type: ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS, type: ReduxActionTypes.SUBMIT_CURL_FORM_SUCCESS,
payload: response.data, payload: response.data,
}); });
yield put(fetchActions(applicationId));
const data = { ...response.data }; yield put(changeApi(data.id));
yield put(initialize(API_EDITOR_FORM_NAME, data));
history.push(
API_EDITOR_ID_URL(applicationId, currentPageId, response.data.id),
);
} }
} catch (error) { } catch (error) {
yield put({ yield put({

View File

@ -1,4 +1,4 @@
import { all, put, takeEvery, select, call } from "redux-saga/effects"; import { all, put, takeEvery, select, call, take } from "redux-saga/effects";
import { change, initialize, getFormValues } from "redux-form"; import { change, initialize, getFormValues } from "redux-form";
import _ from "lodash"; import _ from "lodash";
import { import {
@ -18,7 +18,11 @@ import {
getDatasource, getDatasource,
getDatasourceDraft, getDatasourceDraft,
} from "selectors/entitiesSelector"; } from "selectors/entitiesSelector";
import { selectPlugin } from "actions/datasourceActions"; import {
selectPlugin,
createDatasource,
changeDatasource,
} from "actions/datasourceActions";
import { fetchPluginForm } from "actions/pluginActions"; import { fetchPluginForm } from "actions/pluginActions";
import { GenericApiResponse } from "api/ApiResponses"; import { GenericApiResponse } from "api/ApiResponses";
import DatasourcesApi, { import DatasourcesApi, {
@ -26,6 +30,7 @@ import DatasourcesApi, {
Datasource, Datasource,
} from "api/DatasourcesApi"; } from "api/DatasourcesApi";
import PluginApi, { DatasourceForm } from "api/PluginApi"; import PluginApi, { DatasourceForm } from "api/PluginApi";
import { import {
DATA_SOURCES_EDITOR_ID_URL, DATA_SOURCES_EDITOR_ID_URL,
DATA_SOURCES_EDITOR_URL, DATA_SOURCES_EDITOR_URL,
@ -37,6 +42,8 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import { AppToaster } from "components/editorComponents/ToastComponent"; import { AppToaster } from "components/editorComponents/ToastComponent";
import { ToastType } from "react-toastify"; import { ToastType } from "react-toastify";
import { getFormData } from "selectors/formSelectors"; import { getFormData } from "selectors/formSelectors";
import { changeApi, setDatasourceFieldText } from "actions/apiPaneActions";
import { getCurrentOrgId } from "selectors/organizationSelectors";
function* fetchDatasourcesSaga() { function* fetchDatasourcesSaga() {
try { try {
@ -60,8 +67,12 @@ function* createDatasourceSaga(
actionPayload: ReduxAction<CreateDatasourceConfig>, actionPayload: ReduxAction<CreateDatasourceConfig>,
) { ) {
try { try {
const organizationId = yield select(getCurrentOrgId);
const response: GenericApiResponse<Datasource> = yield DatasourcesApi.createDatasource( const response: GenericApiResponse<Datasource> = yield DatasourcesApi.createDatasource(
actionPayload.payload, {
...actionPayload.payload,
organizationId,
},
); );
const isValidResponse = yield validateResponse(response); const isValidResponse = yield validateResponse(response);
if (isValidResponse) { if (isValidResponse) {
@ -73,9 +84,7 @@ function* createDatasourceSaga(
type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS, type: ReduxActionTypes.CREATE_DATASOURCE_SUCCESS,
payload: response.data, payload: response.data,
}); });
yield put( yield put(change(API_EDITOR_FORM_NAME, "datasource", response.data));
change(API_EDITOR_FORM_NAME, "datasource.id", response.data.id),
);
} }
} catch (error) { } catch (error) {
yield put({ yield put({
@ -156,6 +165,7 @@ function* updateDatasourceSaga(actionPayload: ReduxAction<Datasource>) {
} }
function* testDatasourceSaga(actionPayload: ReduxAction<Datasource>) { function* testDatasourceSaga(actionPayload: ReduxAction<Datasource>) {
const organizationId = yield select(getCurrentOrgId);
const { initialValues, values } = yield select( const { initialValues, values } = yield select(
getFormData, getFormData,
DATASOURCE_DB_FORM, DATASOURCE_DB_FORM,
@ -168,7 +178,10 @@ function* testDatasourceSaga(actionPayload: ReduxAction<Datasource>) {
try { try {
const response: GenericApiResponse<Datasource> = yield DatasourcesApi.testDatasource( const response: GenericApiResponse<Datasource> = yield DatasourcesApi.testDatasource(
payload, {
...payload,
organizationId,
},
); );
const isValidResponse = yield validateResponse(response); const isValidResponse = yield validateResponse(response);
if (isValidResponse) { if (isValidResponse) {
@ -203,6 +216,7 @@ function* createDatasourceFromFormSaga(
) { ) {
try { try {
let formConfig; let formConfig;
const organizationId = yield select(getCurrentOrgId);
const initialValues = {}; const initialValues = {};
const parseConfig = (section: any): any => { const parseConfig = (section: any): any => {
return _.map(section.children, (subSection: any) => { return _.map(section.children, (subSection: any) => {
@ -247,7 +261,10 @@ function* createDatasourceFromFormSaga(
}; };
const response: GenericApiResponse<Datasource> = yield DatasourcesApi.createDatasource( const response: GenericApiResponse<Datasource> = yield DatasourcesApi.createDatasource(
payload, {
...payload,
organizationId,
},
); );
const isValidResponse = yield validateResponse(response); const isValidResponse = yield validateResponse(response);
if (isValidResponse) { if (isValidResponse) {
@ -344,6 +361,53 @@ function* formValueChangeSaga(
yield all([call(updateDraftsSaga)]); yield all([call(updateDraftsSaga)]);
} }
function* storeAsDatasourceSaga() {
const { values } = yield select(getFormData, API_EDITOR_FORM_NAME);
const applicationId = yield select(getCurrentApplicationId);
const pageId = yield select(getCurrentPageId);
const datasource = _.get(values, "datasource");
history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId));
yield put(createDatasource(datasource));
const createDatasourceSuccessAction = yield take(
ReduxActionTypes.CREATE_DATASOURCE_SUCCESS,
);
const createdDatasource = createDatasourceSuccessAction.payload;
yield put({
type: ReduxActionTypes.STORE_AS_DATASOURCE_UPDATE,
payload: {
pageId,
applicationId,
apiId: values.id,
datasourceId: createdDatasource.id,
},
});
yield put(changeDatasource(createdDatasource));
}
function* updateDatasourceSuccessSaga(action: ReduxAction<Datasource>) {
const state = yield select();
const actionRouteInfo = _.get(state, "ui.datasourcePane.actionRouteInfo");
const updatedDatasource = action.payload;
if (
actionRouteInfo &&
updatedDatasource.id === actionRouteInfo.datasourceId
) {
const { apiId } = actionRouteInfo;
yield put(setDatasourceFieldText(apiId, ""));
yield put(changeApi(apiId));
}
yield put({
type: ReduxActionTypes.STORE_AS_DATASOURCE_COMPLETE,
});
}
export function* watchDatasourcesSagas() { export function* watchDatasourcesSagas() {
yield all([ yield all([
takeEvery(ReduxActionTypes.FETCH_DATASOURCES_INIT, fetchDatasourcesSaga), takeEvery(ReduxActionTypes.FETCH_DATASOURCES_INIT, fetchDatasourcesSaga),
@ -356,6 +420,11 @@ export function* watchDatasourcesSagas() {
takeEvery(ReduxActionTypes.TEST_DATASOURCE_INIT, testDatasourceSaga), takeEvery(ReduxActionTypes.TEST_DATASOURCE_INIT, testDatasourceSaga),
takeEvery(ReduxActionTypes.DELETE_DATASOURCE_INIT, deleteDatasourceSaga), takeEvery(ReduxActionTypes.DELETE_DATASOURCE_INIT, deleteDatasourceSaga),
takeEvery(ReduxActionTypes.CHANGE_DATASOURCE, changeDatasourceSaga), takeEvery(ReduxActionTypes.CHANGE_DATASOURCE, changeDatasourceSaga),
takeEvery(ReduxActionTypes.STORE_AS_DATASOURCE_INIT, storeAsDatasourceSaga),
takeEvery(
ReduxActionTypes.UPDATE_DATASOURCE_SUCCESS,
updateDatasourceSuccessSaga,
),
// Intercepting the redux-form change actionType // Intercepting the redux-form change actionType
takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga), takeEvery(ReduxFormActionTypes.VALUE_CHANGE, formValueChangeSaga),
]); ]);

View File

@ -75,7 +75,8 @@ export function* fetchPageListSaga(
); );
const isValidResponse = yield validateResponse(response); const isValidResponse = yield validateResponse(response);
if (isValidResponse) { if (isValidResponse) {
const pages: PageListPayload = response.data.map(page => ({ const orgId = response.data.organizationId;
const pages: PageListPayload = response.data.pages.map(page => ({
pageName: page.name, pageName: page.name,
pageId: page.id, pageId: page.id,
isDefault: page.isDefault, isDefault: page.isDefault,
@ -87,6 +88,13 @@ export function* fetchPageListSaga(
applicationId, applicationId,
}, },
}); });
yield put({
type: ReduxActionTypes.SET_CURRENT_ORG_ID,
payload: {
orgId,
},
});
return; return;
} }
} catch (error) { } catch (error) {

View File

@ -19,7 +19,7 @@ function* fetchPluginsSaga() {
} }
} catch (error) { } catch (error) {
yield put({ yield put({
type: ReduxActionTypes.FETCH_PLUGINS_ERROR, type: ReduxActionErrorTypes.FETCH_PLUGINS_ERROR,
payload: { error }, payload: { error },
}); });
} }

View File

@ -30,7 +30,6 @@ import { initialize } from "redux-form";
import { getAction, getActionParams, getActionTimeout } from "./ActionSagas"; import { getAction, getActionParams, getActionTimeout } from "./ActionSagas";
import { AppState } from "reducers"; import { AppState } from "reducers";
import ActionAPI, { import ActionAPI, {
RestAction,
PaginationField, PaginationField,
ExecuteActionRequest, ExecuteActionRequest,
ActionApiResponse, ActionApiResponse,
@ -45,6 +44,7 @@ import AnalyticsUtil from "utils/AnalyticsUtil";
import { GenericApiResponse } from "api/ApiResponses"; import { GenericApiResponse } from "api/ApiResponses";
import { validateResponse } from "./ErrorSagas"; import { validateResponse } from "./ErrorSagas";
import { getQueryName } from "selectors/entitiesSelector"; import { getQueryName } from "selectors/entitiesSelector";
import { RestAction } from "entities/Action";
const getQueryDraft = (state: AppState, id: string) => { const getQueryDraft = (state: AppState, id: string) => {
const drafts = state.ui.apiPane.drafts; const drafts = state.ui.apiPane.drafts;

View File

@ -1,8 +1,7 @@
import { createSelector } from "reselect"; import { createSelector } from "reselect";
import { getActionsForCurrentPage } from "./entitiesSelector"; import { getActionDrafts, getActionsForCurrentPage } from "./entitiesSelector";
import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { ActionDataState } from "reducers/entityReducers/actionsReducer";
import { getEvaluatedDataTree } from "utils/DynamicBindingUtils"; import { getEvaluatedDataTree } from "utils/DynamicBindingUtils";
import { extraLibraries } from "jsExecution/JSExecutionManagerSingleton";
import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory"; import { DataTree, DataTreeFactory } from "entities/DataTree/dataTreeFactory";
import { getWidgets, getWidgetsMeta } from "sagas/selectors"; import { getWidgets, getWidgetsMeta } from "sagas/selectors";
import * as log from "loglevel"; import * as log from "loglevel";
@ -40,13 +39,15 @@ import { getPageList } from "./appViewSelectors";
// //
export const getUnevaluatedDataTree = createSelector( export const getUnevaluatedDataTree = createSelector(
getActionsForCurrentPage, getActionsForCurrentPage,
getActionDrafts,
getWidgets, getWidgets,
getWidgetsMeta, getWidgetsMeta,
getPageList, getPageList,
(actions, widgets, widgetsMeta, pageListPayload) => { (actions, actionDrafts, widgets, widgetsMeta, pageListPayload) => {
const pageList = pageListPayload || []; const pageList = pageListPayload || [];
return DataTreeFactory.create({ return DataTreeFactory.create({
actions, actions,
actionDrafts,
widgets, widgets,
widgetsMeta, widgetsMeta,
pageList, pageList,
@ -82,8 +83,6 @@ export const getDataTreeForAutocomplete = createSelector(
} }
}); });
} }
const libs: Record<string, any> = {}; return tree;
extraLibraries.forEach(config => (libs[config.accessor] = config.lib));
return { ...tree, ...cachedResponses, ...libs };
}, },
); );

View File

@ -154,11 +154,9 @@ export const getQueryActions = (state: AppState): ActionDataState => {
const getCurrentPageId = (state: AppState) => const getCurrentPageId = (state: AppState) =>
state.entities.pageList.currentPageId; state.entities.pageList.currentPageId;
export const getDatasourcePlugins = (state: AppState) => { export const getDatasourcePlugins = createSelector(getPlugins, plugins => {
return state.entities.plugins.list.filter( return plugins.filter(plugin => plugin?.allowUserDatasources ?? true);
plugin => plugin?.allowUserDatasources ?? true, });
);
};
export const getActionsForCurrentPage = createSelector( export const getActionsForCurrentPage = createSelector(
getCurrentPageId, getCurrentPageId,
@ -169,13 +167,14 @@ export const getActionsForCurrentPage = createSelector(
}, },
); );
export const getActionResponses = ( export const getActionDrafts = (state: AppState) => state.entities.actionDrafts;
state: AppState,
): Record<string, ActionResponse | undefined> => { export const getActionResponses = createSelector(getActions, actions => {
const responses: Record<string, ActionResponse | undefined> = {}; const responses: Record<string, ActionResponse | undefined> = {};
state.entities.actions.forEach(a => {
actions.forEach(a => {
responses[a.config.id] = a.data; responses[a.config.id] = a.data;
}); });
return responses; return responses;
}; });

View File

@ -1,6 +1,6 @@
import { getFormValues, isValid, getFormInitialValues } from "redux-form"; import { getFormValues, isValid, getFormInitialValues } from "redux-form";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { RestAction } from "api/ActionAPI"; import { RestAction } from "entities/Action";
type GetFormData = ( type GetFormData = (
state: AppState, state: AppState,

View File

@ -5,6 +5,8 @@ import { OrgRole, Org } from "constants/orgConstants";
export const getRolesFromState = (state: AppState) => { export const getRolesFromState = (state: AppState) => {
return state.ui.orgs.roles; return state.ui.orgs.roles;
}; };
export const getCurrentOrgId = (state: AppState) => state.ui.orgs.currentOrgId;
export const getOrgs = (state: AppState) => state.ui.orgs.list; export const getOrgs = (state: AppState) => state.ui.orgs.list;
export const getAllUsers = (state: AppState) => state.ui.orgs.orgUsers; export const getAllUsers = (state: AppState) => state.ui.orgs.orgUsers;
export const getAllRoles = (state: AppState) => state.ui.orgs.orgRoles; export const getAllRoles = (state: AppState) => state.ui.orgs.orgRoles;

View File

@ -47,15 +47,15 @@ export const getWidgetPropsForPropertyPane = createSelector(
const evaluatedWidget = _.find(evaluatedTree, { const evaluatedWidget = _.find(evaluatedTree, {
widgetId: widget.widgetId, widgetId: widget.widgetId,
}) as DataTreeWidget; }) as DataTreeWidget;
const widgetProperties = {
...widget,
};
if (evaluatedWidget.invalidProps) { if (evaluatedWidget.invalidProps) {
const { invalidProps, validationMessages } = evaluatedWidget; const { invalidProps, validationMessages } = evaluatedWidget;
return { widgetProperties.invalidProps = invalidProps;
...widget, widgetProperties.validationMessages = validationMessages;
invalidProps,
validationMessages,
};
} }
return widget; return widgetProperties;
}, },
); );

Some files were not shown because too many files have changed in this diff Show More