Merge branch 'release' into fix/#3860-input-widget-validation
10
.github/config.json
vendored
|
|
@ -185,6 +185,11 @@
|
|||
"color": "79e53b",
|
||||
"description": "An unexpected or annoying bug"
|
||||
},
|
||||
"List Widget": {
|
||||
"name": "List Widget",
|
||||
"color": "79e53b",
|
||||
"description": "Issues Related to the list widget"
|
||||
},
|
||||
"Map Widget": {
|
||||
"name": "Map Widget",
|
||||
"color": "7eef7a",
|
||||
|
|
@ -564,6 +569,11 @@
|
|||
"type": "hasLabel",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"label": "List Widget",
|
||||
"type": "hasLabel",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"label": "Map Widget",
|
||||
"type": "hasLabel",
|
||||
|
|
|
|||
2
.github/workflows/client-build.yml
vendored
|
|
@ -99,7 +99,7 @@ jobs:
|
|||
|
||||
- name: Run the jest tests
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: hetunandu/Jest-Coverage-Diff@fix/new-delete-file
|
||||
uses: hetunandu/Jest-Coverage-Diff@feature/better-report-comments
|
||||
with:
|
||||
fullCoverageDiff: false
|
||||
runCommand: cd app/client && REACT_APP_ENVIRONMENT=${{steps.vars.outputs.REACT_APP_ENVIRONMENT}} yarn run test:unit
|
||||
|
|
|
|||
3
.gitignore
vendored
|
|
@ -2,6 +2,7 @@
|
|||
.idea
|
||||
*.iml
|
||||
.env
|
||||
.vscode/*
|
||||
|
||||
# test coverage
|
||||
coverage-summary.json
|
||||
coverage-summary.json
|
||||
|
|
|
|||
2
app/client/.gitignore
vendored
|
|
@ -43,3 +43,5 @@ storybook-static/*
|
|||
build-storybook.log
|
||||
|
||||
.eslintcache
|
||||
.vscode
|
||||
TODO
|
||||
|
|
@ -7,4 +7,3 @@
|
|||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
For details on setting up your development machine, please refer to the [Setup Guide](https://github.com/appsmithorg/appsmith/blob/release/contributions/ClientSetup.md)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
"env": {
|
||||
"cypress/globals": true
|
||||
},
|
||||
"rules": {
|
||||
"cypress/no-unnecessary-waiting": 0
|
||||
},
|
||||
"extends": [
|
||||
"plugin:cypress/recommended"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -58,7 +58,9 @@
|
|||
"chartType": "LINE_CHART",
|
||||
"chartName": "Sales on working days",
|
||||
"allowHorizontalScroll": false,
|
||||
"chartData": [{"seriesName":"Sales","data":""}],
|
||||
"chartData": {
|
||||
"some-random-id": {"seriesName":"Sales","data": []}
|
||||
},
|
||||
"xAxisName": "Last Week",
|
||||
"yAxisName": "Total Order Revenue $",
|
||||
"type": "CHART_WIDGET",
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
"inputType": "TEXT",
|
||||
"label": "Endpoint",
|
||||
"widgetName": "EndpointInput",
|
||||
"defaultText": "todos",
|
||||
"defaultText": "offers",
|
||||
"type": "INPUT_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 71.75,
|
||||
|
|
|
|||
161
app/client/cypress/fixtures/listdsl.json
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
{
|
||||
"dsl": {
|
||||
"widgetName": "MainContainer",
|
||||
"backgroundColor": "none",
|
||||
"rightColumn": 1224,
|
||||
"snapColumns": 16,
|
||||
"detachFromLayout": true,
|
||||
"widgetId": "0",
|
||||
"topRow": 0,
|
||||
"bottomRow": 1280,
|
||||
"containerStyle": "none",
|
||||
"snapRows": 33,
|
||||
"parentRowSpace": 1,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"canExtend": true,
|
||||
"version": 9,
|
||||
"minHeight": 1292,
|
||||
"parentColumnSpace": 1,
|
||||
"dynamicBindingPathList": [],
|
||||
"leftColumn": 0,
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"enhancements": true,
|
||||
"backgroundColor": "",
|
||||
"gridType": "vertical",
|
||||
"gridGap": 0,
|
||||
"items": "[\n {\n \"id\": 1,\n \"email\": \"michael.lawson@reqres.in\",\n \"first_name\": \"Michael\",\n \"last_name\": \"Lawson\",\n \"avatar\": \"https://reqres.in/img/faces/7-image.jpg\"\n },\n {\n \"id\": 2,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"first_name\": \"Lindsay\",\n \"last_name\": \"Ferguson\",\n \"avatar\": \"https://reqres.in/img/faces/8-image.jpg\"\n },\n {\n \"id\": 3,\n \"email\": \"brock.lesnar@reqres.in\",\n \"first_name\": \"Brock\",\n \"last_name\": \"Lesnar\",\n \"avatar\": \"https://reqres.in/img/faces/8-image.jpg\"\n }\n]",
|
||||
"widgetName": "List1",
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"widgetName": "Canvas1",
|
||||
"containerStyle": "none",
|
||||
"canExtend": false,
|
||||
"detachFromLayout": true,
|
||||
"dropDisabled": true,
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"backgroundColor": "white",
|
||||
"widgetName": "Container1",
|
||||
"containerStyle": "card",
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"widgetName": "Canvas2",
|
||||
"containerStyle": "none",
|
||||
"canExtend": false,
|
||||
"detachFromLayout": true,
|
||||
"children": [
|
||||
{
|
||||
"isVisible": true,
|
||||
"text": "Label",
|
||||
"textStyle": "LABEL",
|
||||
"textAlign": "LEFT",
|
||||
"widgetName": "Text1",
|
||||
"type": "TEXT_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 32,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 2,
|
||||
"rightColumn": 6,
|
||||
"topRow": 0,
|
||||
"bottomRow": 1,
|
||||
"parentId": "dinv2tsatk",
|
||||
"widgetId": "k6ct7dxg4w"
|
||||
},
|
||||
{
|
||||
"isVisible":true,
|
||||
"text":"Submit",
|
||||
"buttonStyle":"PRIMARY_BUTTON",
|
||||
"widgetName":"Button1",
|
||||
"isDisabled":false,
|
||||
"isDefaultClickDisabled":true,
|
||||
"version":1,
|
||||
"type":"BUTTON_WIDGET",
|
||||
"isLoading":false,
|
||||
"parentColumnSpace":29.25,
|
||||
"parentRowSpace":40,
|
||||
"leftColumn":6,
|
||||
"rightColumn":8,
|
||||
"topRow":1,
|
||||
"bottomRow":2,
|
||||
"parentId":"dinv2tsatk",
|
||||
"widgetId":"fuw9p7cuek"
|
||||
}
|
||||
],
|
||||
"minHeight": null,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 1,
|
||||
"parentRowSpace": 1,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": null,
|
||||
"topRow": 0,
|
||||
"bottomRow": null,
|
||||
"parentId": "4ruj7xl5ri",
|
||||
"widgetId": "dinv2tsatk"
|
||||
}
|
||||
],
|
||||
"dragDisabled": true,
|
||||
"isDeletable": false,
|
||||
"disablePropertyPane": true,
|
||||
"type": "CONTAINER_WIDGET",
|
||||
"isLoading": false,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 16,
|
||||
"topRow": 0,
|
||||
"bottomRow": 4,
|
||||
"parentId": "0pvmmqr77m",
|
||||
"widgetId": "4ruj7xl5ri"
|
||||
}
|
||||
],
|
||||
"minHeight": 400,
|
||||
"type": "CANVAS_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 1,
|
||||
"parentRowSpace": 1,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 592,
|
||||
"topRow": 0,
|
||||
"bottomRow": 400,
|
||||
"parentId": "5bwz8xcvhj",
|
||||
"widgetId": "0pvmmqr77m"
|
||||
}
|
||||
],
|
||||
"type": "LIST_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 74,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 8,
|
||||
"topRow": 0,
|
||||
"bottomRow": 10,
|
||||
"parentId": "0",
|
||||
"widgetId": "5bwz8xcvhj",
|
||||
"dynamicBindingPathList": [],
|
||||
"template": {
|
||||
"Text1": {
|
||||
"isVisible": true,
|
||||
"text": "Label",
|
||||
"textStyle": "LABEL",
|
||||
"textAlign": "LEFT",
|
||||
"widgetName": "Text1",
|
||||
"type": "TEXT_WIDGET",
|
||||
"isLoading": false,
|
||||
"parentColumnSpace": 32,
|
||||
"parentRowSpace": 40,
|
||||
"leftColumn": 0,
|
||||
"rightColumn": 4,
|
||||
"topRow": 0,
|
||||
"bottomRow": 1,
|
||||
"parentId": "dinv2tsatk",
|
||||
"widgetId": "k6ct7dxg4w"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
app/client/cypress/fixtures/testFile2.mov
Normal file
|
|
@ -1,33 +1,56 @@
|
|||
const dsl = require("../../../../fixtures/executionParamsDsl.json");
|
||||
const publishPage = require("../../../../locators/publishWidgetspage.json");
|
||||
const commonlocators = require("../../../../locators/commonlocators.json");
|
||||
const queryLocators = require("../../../../locators/QueryEditor.json");
|
||||
const datasource = require("../../../../locators/DatasourcesEditor.json");
|
||||
|
||||
describe("API Panel Test Functionality", function() {
|
||||
let datasourceName;
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
it("Will pass execution params", function() {
|
||||
// Create the Api
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.CreateAPI("MultiApi");
|
||||
cy.enterDatasourceAndPath(
|
||||
"https://jsonplaceholder.typicode.com/",
|
||||
"{{this.params.endpoint || 'posts'}}",
|
||||
);
|
||||
cy.WaitAutoSave();
|
||||
// Run it
|
||||
cy.RunAPI();
|
||||
beforeEach(() => {
|
||||
cy.startRoutesForDatasource();
|
||||
});
|
||||
it("Create a postgres datasource", function() {
|
||||
cy.NavigateToDatasourceEditor();
|
||||
cy.get(datasource.PostgreSQL).click();
|
||||
|
||||
cy.getPluginFormsAndCreateDatasource();
|
||||
|
||||
cy.fillPostgresDatasourceForm();
|
||||
|
||||
cy.testSaveDatasource();
|
||||
|
||||
cy.get("@createDatasource").then((httpResponse) => {
|
||||
datasourceName = httpResponse.response.body.data.name;
|
||||
});
|
||||
});
|
||||
it("Create and runs query", () => {
|
||||
cy.NavigateToQueryEditor();
|
||||
cy.contains(".t--datasource-name", datasourceName)
|
||||
.find(queryLocators.createQuery)
|
||||
.click();
|
||||
|
||||
cy.get(queryLocators.templateMenu).click();
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.focus()
|
||||
.type("select * from {{ this.params.tableName || 'users' }} limit 10", {
|
||||
force: true,
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
cy.WaitAutoSave();
|
||||
cy.runQuery();
|
||||
});
|
||||
it("Will pass execution params", function() {
|
||||
// Bind the table
|
||||
cy.SearchEntityandOpen("Table1");
|
||||
cy.testJsontext("tabledata", "{{MultiApi.data", false);
|
||||
cy.testJsontext("tabledata", "{{Query1.data}}");
|
||||
// Assert 'posts' data (default)
|
||||
cy.readTabledataPublish("0", "2").then((cellData) => {
|
||||
expect(cellData).to.be.equal(
|
||||
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
|
||||
);
|
||||
cy.readTabledataPublish("0", "1").then((cellData) => {
|
||||
expect(cellData).to.be.equal("Ximenez Kainz");
|
||||
});
|
||||
|
||||
// Choose static button
|
||||
cy.SearchEntityandOpen("StaticButton");
|
||||
// toggle js of onClick
|
||||
|
|
@ -37,8 +60,7 @@ describe("API Panel Test Functionality", function() {
|
|||
// Bind with MultiApi with static value
|
||||
cy.testJsontext(
|
||||
"onclick",
|
||||
"{{MultiApi.run(undefined, undefined, { endpoint: 'users",
|
||||
false,
|
||||
"{{Query1.run(undefined, undefined, { tableName: 'orders' })}}",
|
||||
);
|
||||
cy.get(commonlocators.editPropCrossButton).click({ force: true });
|
||||
|
||||
|
|
@ -51,8 +73,7 @@ describe("API Panel Test Functionality", function() {
|
|||
// Bind with MultiApi with dynamicValue value
|
||||
cy.testJsontext(
|
||||
"onclick",
|
||||
"{{MultiApi.run(undefined, undefined, { endpoint: EndpointInput.text",
|
||||
false,
|
||||
"{{Query1.run(undefined, undefined, { tableName: EndpointInput.text })}}",
|
||||
);
|
||||
|
||||
// Publish the app
|
||||
|
|
@ -60,10 +81,8 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.wait("@postExecute");
|
||||
|
||||
// Assert on load data in table
|
||||
cy.readTabledataPublish("0", "2").then((cellData) => {
|
||||
expect(cellData).to.be.equal(
|
||||
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
|
||||
);
|
||||
cy.readTabledataPublish("0", "1").then((cellData) => {
|
||||
expect(cellData).to.be.equal("Ximenez Kainz");
|
||||
});
|
||||
|
||||
// Click Static button
|
||||
|
|
@ -74,7 +93,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.wait(2000);
|
||||
// Assert statically bound "users" data
|
||||
cy.readTabledataPublish("1", "1").then((cellData) => {
|
||||
expect(cellData).to.be.equal("Ervin Howell");
|
||||
expect(cellData).to.be.equal("OUT_FOR_DELIVERY");
|
||||
});
|
||||
|
||||
// Click dynamic button
|
||||
|
|
@ -84,8 +103,8 @@ describe("API Panel Test Functionality", function() {
|
|||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(2000);
|
||||
// Assert dynamically bound "todos" data
|
||||
cy.readTabledataPublish("0", "2").then((cellData) => {
|
||||
expect(cellData).to.be.equal("delectus aut autem");
|
||||
cy.readTabledataPublish("0", "1").then((cellData) => {
|
||||
expect(cellData).to.be.equal("DISCOUNT");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,11 @@ describe("API Panel Test Functionality ", function() {
|
|||
.click({ force: true });
|
||||
cy.CopyAPIToHome();
|
||||
cy.GlobalSearchEntity("FirstAPICopy");
|
||||
// click on learn how link
|
||||
cy.get(".t--learn-how-apis-link").click();
|
||||
// this should open in a global search modal
|
||||
cy.get(commonlocators.globalSearchModal);
|
||||
cy.get("body").click(0, 0);
|
||||
cy.DeleteAPIFromSideBar();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
const dsl = require("../../../../fixtures/buttondsl.json");
|
||||
|
||||
describe("Debugger logs", function() {
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
it("Modifying widget properties should log the same", function() {
|
||||
cy.openPropertyPane("buttonwidget");
|
||||
cy.testJsontext("label", "Test");
|
||||
|
||||
cy.get(".t--debugger").click();
|
||||
cy.get(".t--debugger-log-state").contains("Test");
|
||||
});
|
||||
});
|
||||
|
|
@ -43,6 +43,29 @@ describe("FilePicker Widget Functionality", function() {
|
|||
cy.get("button").contains("1 files selected");
|
||||
});
|
||||
|
||||
it("It checks the deletion of filepicker works as expected", function() {
|
||||
cy.get(commonlocators.filePickerButton).click();
|
||||
cy.get(commonlocators.filePickerInput)
|
||||
.first()
|
||||
.attachFile("testFile.mov");
|
||||
cy.get(commonlocators.filePickerUploadButton).click();
|
||||
//eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
cy.get("button").contains("1 files selected");
|
||||
cy.get(commonlocators.filePickerButton).click();
|
||||
//eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(200);
|
||||
cy.get("button.uppy-Dashboard-Item-action--remove").click();
|
||||
cy.get("button.uppy-Dashboard-browse").click();
|
||||
cy.get(commonlocators.filePickerInput)
|
||||
.first()
|
||||
.attachFile("testFile2.mov");
|
||||
cy.get(commonlocators.filePickerUploadButton).click();
|
||||
//eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
cy.get("button").contains("1 files selected");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// put your clean up code if any
|
||||
});
|
||||
|
|
|
|||
|
|
@ -54,14 +54,12 @@ describe("GlobalSearch", function() {
|
|||
cy.get(commonlocators.globalSearchTrigger).click({ force: true });
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1000);
|
||||
cy.get(commonlocators.globalSearchClearInput).click({ force: true });
|
||||
cy.get(commonlocators.globalSearchInput).type("Page1");
|
||||
cy.get("body").type("{enter}");
|
||||
|
||||
cy.get(commonlocators.globalSearchTrigger).click({ force: true });
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1000);
|
||||
cy.get(commonlocators.globalSearchClearInput).click({ force: true });
|
||||
cy.get(commonlocators.globalSearchInput).type("SomeApi");
|
||||
cy.get("body").type("{enter}");
|
||||
cy.window()
|
||||
|
|
@ -90,14 +88,12 @@ describe("GlobalSearch", function() {
|
|||
cy.get(commonlocators.globalSearchTrigger).click({ force: true });
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1000); // modal open transition should be deterministic
|
||||
cy.get(commonlocators.globalSearchClearInput).click({ force: true });
|
||||
cy.get(commonlocators.globalSearchInput).type("Page1");
|
||||
cy.get("body").type("{enter}");
|
||||
|
||||
cy.get(commonlocators.globalSearchTrigger).click({ force: true });
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1000); // modal open transition should be deterministic
|
||||
cy.get(commonlocators.globalSearchClearInput).click({ force: true });
|
||||
cy.get(commonlocators.globalSearchInput).type(expectedDatasource.name);
|
||||
cy.get("body").type("{enter}");
|
||||
cy.location().should((loc) => {
|
||||
|
|
@ -111,7 +107,6 @@ describe("GlobalSearch", function() {
|
|||
cy.get(commonlocators.globalSearchTrigger).click({ force: true });
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(1000);
|
||||
cy.get(commonlocators.globalSearchClearInput).click({ force: true });
|
||||
cy.get(commonlocators.globalSearchInput).type("Page1");
|
||||
cy.get("body").type("{enter}");
|
||||
cy.window()
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ describe("Container Widget Functionality", function() {
|
|||
/**
|
||||
* @param{Text} Random Colour
|
||||
*/
|
||||
cy.testCodeMirror(this.data.colour);
|
||||
cy.get(widgetsPage.backgroundcolorPicker)
|
||||
.first()
|
||||
.click({ force: true });
|
||||
cy.xpath(widgetsPage.greenColor).click();
|
||||
cy.get(widgetsPage.containerD)
|
||||
.should("have.css", "background-color")
|
||||
.and("eq", this.data.rgbValue);
|
||||
.and("eq", "rgb(3, 179, 101)");
|
||||
/**
|
||||
* @param{toggleButton Css} Assert to be checked
|
||||
*/
|
||||
|
|
@ -41,7 +44,7 @@ describe("Container Widget Functionality", function() {
|
|||
cy.get(widgetsPage.containerD)
|
||||
.eq(0)
|
||||
.should("have.css", "background-color")
|
||||
.and("eq", this.data.rgbValue);
|
||||
.and("eq", "rgb(3, 179, 101)");
|
||||
});
|
||||
afterEach(() => {
|
||||
// put your clean up code if any
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
const datasource = require("../../../../locators/DatasourcesEditor.json");
|
||||
const queryLocators = require("../../../../locators/QueryEditor.json");
|
||||
const commonlocators = require("../../../../locators/commonlocators.json");
|
||||
|
||||
describe("Check datasource doc links", function() {
|
||||
let postgresDatasourceName;
|
||||
|
||||
beforeEach(() => {
|
||||
cy.startRoutesForDatasource();
|
||||
});
|
||||
|
||||
it("Create postgres datasource", function() {
|
||||
cy.NavigateToDatasourceEditor();
|
||||
cy.get(datasource.PostgreSQL).click();
|
||||
cy.generateUUID().then((uid) => {
|
||||
postgresDatasourceName = uid;
|
||||
|
||||
cy.get(".t--edit-datasource-name").click();
|
||||
cy.get(".t--edit-datasource-name input")
|
||||
.clear()
|
||||
.type(postgresDatasourceName, { force: true })
|
||||
.should("have.value", postgresDatasourceName)
|
||||
.blur();
|
||||
});
|
||||
cy.wait("@saveDatasource").should(
|
||||
"have.nested.property",
|
||||
"response.body.responseMeta.status",
|
||||
200,
|
||||
);
|
||||
cy.fillPostgresDatasourceForm();
|
||||
cy.testSaveDatasource();
|
||||
});
|
||||
|
||||
it("Check that documentation opens global modal", function() {
|
||||
cy.NavigateToQueryEditor();
|
||||
|
||||
cy.contains(".t--datasource-name", postgresDatasourceName)
|
||||
.find(queryLocators.createQuery)
|
||||
.click();
|
||||
|
||||
cy.get(".t--datasource-documentation-link").click();
|
||||
cy.get(commonlocators.globalSearchModal);
|
||||
cy.get("body").click(0, 0);
|
||||
});
|
||||
|
||||
it("Delete the query and datasources", function() {
|
||||
cy.get(queryLocators.deleteQuery).click();
|
||||
cy.wait("@deleteAction").should(
|
||||
"have.nested.property",
|
||||
"response.body.responseMeta.status",
|
||||
200,
|
||||
);
|
||||
|
||||
cy.deleteDatasource(postgresDatasourceName);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
const commonlocators = require("../../../locators/commonlocators.json");
|
||||
const widgetsPage = require("../../../locators/Widgets.json");
|
||||
const dsl = require("../../../fixtures/listdsl.json");
|
||||
const publishPage = require("../../../locators/publishWidgetspage.json");
|
||||
|
||||
describe("Container Widget Functionality", function() {
|
||||
const items = JSON.parse(dsl.dsl.children[0].items);
|
||||
|
||||
before(() => {
|
||||
cy.addDsl(dsl);
|
||||
});
|
||||
|
||||
it("checks if list shows correct no. of items", function() {
|
||||
cy.get(commonlocators.containerWidget).then(function($lis) {
|
||||
expect($lis).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("checks currentItem binding", function() {
|
||||
cy.SearchEntityandOpen("Text1");
|
||||
cy.getCodeMirror().then(($cm) => {
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.type(`{{currentItem.first_name}}`, {
|
||||
force: true,
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
});
|
||||
|
||||
cy.wait(1000);
|
||||
|
||||
cy.closePropertyPane();
|
||||
|
||||
cy.get(commonlocators.TextInside).then(function($lis) {
|
||||
expect($lis.eq(0)).to.contain(items[0].first_name);
|
||||
expect($lis.eq(1)).to.contain(items[1].first_name);
|
||||
});
|
||||
});
|
||||
|
||||
it("checks button action", function() {
|
||||
cy.SearchEntityandOpen("Button1");
|
||||
cy.getCodeMirror().then(($cm) => {
|
||||
cy.get(".CodeMirror textarea")
|
||||
.first()
|
||||
.type(`{{currentItem.first_name}}`, {
|
||||
force: true,
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
});
|
||||
cy.addAction("{{currentItem.first_name}}");
|
||||
|
||||
cy.PublishtheApp();
|
||||
|
||||
cy.get(`${widgetsPage.widgetBtn}`)
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.get(commonlocators.toastmsg).contains(items[0].first_name);
|
||||
});
|
||||
|
||||
it("it checks onListItem click action", function() {
|
||||
cy.get(publishPage.backToEditor).click({ force: true });
|
||||
|
||||
cy.SearchEntityandOpen("List1");
|
||||
cy.addAction("{{currentItem.first_name}}");
|
||||
|
||||
cy.PublishtheApp();
|
||||
|
||||
cy.get(
|
||||
"div[type='LIST_WIDGET'] .t--widget-containerwidget:first-child",
|
||||
).click();
|
||||
|
||||
cy.get(commonlocators.toastmsg).contains(items[0].first_name);
|
||||
});
|
||||
|
||||
it("it checks pagination", function() {
|
||||
// clicking on second pagination button
|
||||
cy.get(`${commonlocators.paginationButton}-2`).click();
|
||||
|
||||
// now we are on the second page which shows first the 3rd item in the list
|
||||
cy.get(commonlocators.TextInside).then(function($lis) {
|
||||
expect($lis.eq(0)).to.contain(items[2].first_name);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// put your clean up code if any
|
||||
});
|
||||
});
|
||||
|
|
@ -184,7 +184,7 @@ describe("API Panel Test Functionality", function() {
|
|||
);
|
||||
cy.WaitAutoSave();
|
||||
cy.RunAPI();
|
||||
cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get);
|
||||
cy.validateRequest(testdata.baseUrl, testdata.methods, testdata.Get, true);
|
||||
cy.ResponseStatusCheck("5000");
|
||||
cy.log("Response code check successful");
|
||||
cy.ResponseCheck("Invalid value for Content-Type");
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ describe("Add widget", function() {
|
|||
.focus()
|
||||
.type("select * from configs");
|
||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||
cy.wait(500);
|
||||
cy.WaitAutoSave();
|
||||
cy.get(queryEditor.runQuery).click();
|
||||
cy.wait("@postExecute").should(
|
||||
"have.nested.property",
|
||||
|
|
|
|||
|
|
@ -31,10 +31,8 @@ describe("Create a query with a empty datasource, run, save the query", function
|
|||
|
||||
cy.EvaluateCurrentValue("select * from users limit 10");
|
||||
cy.runQuery();
|
||||
cy.get(".react-tabs p")
|
||||
.last()
|
||||
.contains(
|
||||
"[Missing endpoint., Missing username for authentication., Missing password for authentication.]",
|
||||
);
|
||||
cy.get(".t--query-error").contains(
|
||||
"[Missing endpoint., Missing username for authentication., Missing password for authentication.]",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
"verticalCenter": ".t--icon-tab-CENTER",
|
||||
"verticalBottom": ".t--icon-tab-BOTTOM",
|
||||
"textColor": ".t--property-control-textcolor input",
|
||||
"backgroundcolorPicker": ".t--property-control-backgroundcolor input",
|
||||
"greenColor": "//div[@color='rgb(3, 179, 101)']",
|
||||
"toggleJsColor": ".t--property-control-textcolor .t--js-toggle",
|
||||
"backgroundColor": ".t--property-control-cellbackground input",
|
||||
|
|
|
|||
|
|
@ -100,11 +100,13 @@
|
|||
"filePickerUploadButton": ".uppy-StatusBar-actionBtn--upload",
|
||||
"filePickerOnFilesSelected": ".t--property-control-onfilesselected",
|
||||
"dataType": ".t--property-control-datatype .bp3-popover-target",
|
||||
"evaluateMsg": ".t--CodeEditor-evaluatedValue p",
|
||||
"evaluateMsg": ".t--evaluatedPopup-error",
|
||||
"globalSearchModal": ".t--global-search-modal",
|
||||
"globalSearchInput": ".t--global-search-input",
|
||||
"globalSearchTrigger": ".t--global-search-modal-trigger",
|
||||
"globalSearchClearInput": ".t--global-clear-input",
|
||||
"containerWidget": ".t--widget-containerwidget",
|
||||
"paginationButton": ".rc-pagination-item",
|
||||
"switchWidgetActive": ".t--switch-widget-active",
|
||||
"switchWidgetInActive": ".t--switch-widget-inactive",
|
||||
"switchWidgetLoading": ".t--switch-widget-loading"
|
||||
|
|
|
|||
|
|
@ -12,13 +12,11 @@ describe("Table functionality ", function() {
|
|||
// Navigate to add background colour and Text colour
|
||||
// Ensure the row colour gets overlapped on table colour
|
||||
});
|
||||
|
||||
it("Collapse the tabs of Property pane", function() {
|
||||
// Add a table
|
||||
// Click on the property pane
|
||||
// Collapse the General ,Action and Tab option
|
||||
});
|
||||
|
||||
it("Bind the column with same name", function() {
|
||||
// Add a table
|
||||
// Click on the property pane
|
||||
|
|
|
|||
|
|
@ -577,16 +577,26 @@ Cypress.Commands.add("SaveAndRunAPI", () => {
|
|||
cy.RunAPI();
|
||||
});
|
||||
|
||||
Cypress.Commands.add("validateRequest", (baseurl, path, verb) => {
|
||||
cy.xpath(apiwidget.Request)
|
||||
.should("be.visible")
|
||||
.click({ force: true });
|
||||
cy.xpath(apiwidget.RequestURL).contains(baseurl.concat(path));
|
||||
cy.xpath(apiwidget.RequestMethod).contains(verb);
|
||||
cy.xpath(apiwidget.Responsetab)
|
||||
.should("be.visible")
|
||||
.click({ force: true });
|
||||
});
|
||||
Cypress.Commands.add(
|
||||
"validateRequest",
|
||||
(baseurl, path, verb, error = false) => {
|
||||
cy.get(".react-tabs__tab")
|
||||
.contains("Logs")
|
||||
.click();
|
||||
|
||||
if (!error) {
|
||||
cy.get(".object-key")
|
||||
.last()
|
||||
.contains("request")
|
||||
.click();
|
||||
}
|
||||
cy.get(".string-value").contains(baseurl.concat(path));
|
||||
cy.get(".string-value").contains(verb);
|
||||
cy.xpath(apiwidget.Responsetab)
|
||||
.should("be.visible")
|
||||
.click({ force: true });
|
||||
},
|
||||
);
|
||||
|
||||
Cypress.Commands.add("SelectAction", (action) => {
|
||||
cy.get(ApiEditor.ApiVerb)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ module.exports = {
|
|||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node", "css"],
|
||||
moduleDirectories: ["node_modules", "src", "test"],
|
||||
transformIgnorePatterns: [
|
||||
"<rootDir>/node_modules/(?!codemirror|react-dnd|dnd-core|@babel|(@blueprintjs/core/lib/esnext)|(@blueprintjs/core/lib/esm)|@github)",
|
||||
"<rootDir>/node_modules/(?!codemirror|react-dnd|dnd-core|@babel|(@blueprintjs/core/lib/esnext)|(@blueprintjs/core/lib/esm)|@github|lodash-es)",
|
||||
],
|
||||
moduleNameMapper: {
|
||||
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js",
|
||||
|
|
@ -23,6 +23,7 @@ module.exports = {
|
|||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||
"<rootDir>/test/__mocks__/fileMock.js",
|
||||
"^worker-loader!": "<rootDir>/test/__mocks__/workerMock.js",
|
||||
"^!!raw-loader!": "<rootDir>/test/__mocks__/derivedMock.js",
|
||||
"test/(.*)": "<rootDir>/test/$1",
|
||||
},
|
||||
globals: {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
"popper.js": "^1.15.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prismjs": "^1.23.0",
|
||||
"rc-pagination": "^3.1.3",
|
||||
"re-reselect": "^3.4.0",
|
||||
"react": "^16.12.0",
|
||||
"react-base-table": "^1.9.1",
|
||||
|
|
@ -129,7 +130,8 @@
|
|||
"tinycolor2": "^1.4.1",
|
||||
"toposort": "^2.0.2",
|
||||
"ts-loader": "^6.0.4",
|
||||
"typescript": "^3.9.2",
|
||||
"tslib": "^2.1.0",
|
||||
"typescript": "^4.1.3",
|
||||
"unescape-js": "^1.1.4",
|
||||
"url-search-params-polyfill": "^8.0.0",
|
||||
"worker-loader": "^3.0.2"
|
||||
|
|
@ -147,6 +149,7 @@
|
|||
"start-prod": "REACT_APP_ENVIRONMENT=PRODUCTION craco start",
|
||||
"cytest": "REACT_APP_TESTING=TESTING REACT_APP_ENVIRONMENT=DEVELOPMENT craco start & ./node_modules/.bin/cypress open",
|
||||
"test:unit": "$(npm bin)/jest -b --colors --no-cache --coverage --collectCoverage=true --coverageDirectory='../../' --coverageReporters='json-summary'",
|
||||
"test:jest": "$(npm bin)/jest --watch",
|
||||
"storybook": "start-storybook -p 9009 -s public",
|
||||
"build-storybook": "build-storybook -s public"
|
||||
},
|
||||
|
|
@ -176,7 +179,7 @@
|
|||
"@storybook/preset-create-react-app": "^3.1.4",
|
||||
"@storybook/react": "^5.3.19",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"@testing-library/user-event": "^13.1.1",
|
||||
"@types/codemirror": "^0.0.96",
|
||||
"@types/deep-diff": "^1.0.0",
|
||||
|
|
@ -193,8 +196,8 @@
|
|||
"@types/styled-system": "^5.1.9",
|
||||
"@types/tern": "0.22.0",
|
||||
"@types/toposort": "^2.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.6.0",
|
||||
"@typescript-eslint/parser": "^4.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
||||
"@typescript-eslint/parser": "^4.15.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-styled-components": "^1.10.7",
|
||||
"craco-babel-loader": "^0.1.4",
|
||||
|
|
@ -210,6 +213,7 @@
|
|||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.21.3",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"factory.ts": "^0.5.1",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"mocha": "^7.1.0",
|
||||
"mocha-junit-reporter": "^1.23.3",
|
||||
|
|
|
|||
|
|
@ -36,6 +36,46 @@
|
|||
<div id="header-root"></div>
|
||||
<div id="root"></div>
|
||||
<script type="text/javascript">
|
||||
// Ref: https://github.com/Modernizr/Modernizr/blob/94592f279a410436530c7c06acc42a6e90c20150/feature-detects/storage/localstorage.js
|
||||
const getIsLocalStorageSupported = () => {
|
||||
try {
|
||||
window.localStorage.setItem("test", "testA");
|
||||
window.localStorage.removeItem("test");
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const isLocalStorageSupported = getIsLocalStorageSupported();
|
||||
|
||||
const handleLocalStorageNotSupportedError = () => {
|
||||
console.error("Localstorage storage is not supported on your device.");
|
||||
}
|
||||
|
||||
const localStorageUtil = {
|
||||
getItem: (key) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return;
|
||||
}
|
||||
return window.localStorage.getItem(key);
|
||||
},
|
||||
removeItem: (key) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return
|
||||
}
|
||||
return window.localStorage.removeItem(key);
|
||||
},
|
||||
setItem: (key, value) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return;
|
||||
}
|
||||
return window.localStorage.setItem(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
// Note: The following handler is necessary for when we have a new deployment.
|
||||
|
||||
// What happens?
|
||||
|
|
@ -60,11 +100,11 @@
|
|||
if(regex.test(url) && url.indexOf(window.location.host) > -1 && message.includes(chunkLoadErrorMessage)) {
|
||||
console.log("chunk load failed!", url);
|
||||
// First check if we've set the flag to not reload on chunk load fail
|
||||
const donotReload = window.localStorage.getItem("donotReloadOnChunkLoadFail");
|
||||
const donotReload = localStorageUtil.getItem("donotReloadOnChunkLoadFail");
|
||||
if(!donotReload){
|
||||
// If donotReload is set to false or undefined, we can set it to true and reload
|
||||
// This signifies that this is the first time this happened in this session.
|
||||
window.localStorage.setItem("donotReloadOnChunkLoadFail", true);
|
||||
localStorageUtil.setItem("donotReloadOnChunkLoadFail", true);
|
||||
window.location.assign(window.location.href);
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +115,7 @@
|
|||
window.addEventListener("load", (event) => {
|
||||
// This signifies that we've had a chunk load failure in this session
|
||||
// We clear the entry, as otherwise, we will have a repeated reload scenario.
|
||||
window.localStorage.removeItem("donotReloadOnChunkLoadFail");
|
||||
localStorageUtil.removeItem("donotReloadOnChunkLoadFail");
|
||||
|
||||
document.getElementById("loader").style.width = "100vw";
|
||||
setTimeout(() => {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import {
|
|||
ReduxActionTypes,
|
||||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
EvaluationReduxAction,
|
||||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { Action } from "entities/Action";
|
||||
import { batchAction } from "actions/batchActions";
|
||||
|
|
@ -27,10 +29,12 @@ export type FetchActionsPayload = {
|
|||
|
||||
export const fetchActions = (
|
||||
applicationId: string,
|
||||
): ReduxAction<FetchActionsPayload> => {
|
||||
postEvalActions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||
): EvaluationReduxAction<unknown> => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_ACTIONS_INIT,
|
||||
payload: { applicationId },
|
||||
postEvalActions,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const updateWidgetPropertyRequest = (
|
|||
export interface BatchPropertyUpdatePayload {
|
||||
modify?: Record<string, unknown>; //Key value pairs of paths and values to update
|
||||
remove?: string[]; //Array of paths to delete
|
||||
triggerPaths?: string[]; // Array of paths in the modify and remove list which are trigger paths
|
||||
}
|
||||
|
||||
export const batchUpdateWidgetProperty = (
|
||||
|
|
|
|||
31
app/client/src/actions/debuggerActions.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { Message } from "entities/AppsmithConsole";
|
||||
|
||||
export const debuggerLogInit = (payload: Message) => ({
|
||||
type: ReduxActionTypes.DEBUGGER_LOG_INIT,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const debuggerLog = (payload: Message) => ({
|
||||
type: ReduxActionTypes.DEBUGGER_LOG,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const clearLogs = () => ({
|
||||
type: ReduxActionTypes.CLEAR_DEBUGGER_LOGS,
|
||||
});
|
||||
|
||||
export const showDebugger = (payload?: boolean) => ({
|
||||
type: ReduxActionTypes.SHOW_DEBUGGER,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const errorLog = (payload: Message) => ({
|
||||
type: ReduxActionTypes.DEBUGGER_ERROR_LOG,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const updateErrorLog = (payload: Message) => ({
|
||||
type: ReduxActionTypes.DEBUGGER_UPDATE_ERROR_LOG,
|
||||
payload,
|
||||
});
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
import { FetchPageRequest, PageLayout, SavePageResponse } from "api/PageApi";
|
||||
import { WidgetOperation } from "widgets/BaseWidget";
|
||||
import { WidgetType } from "constants/WidgetConstants";
|
||||
import {
|
||||
EvaluationReduxAction,
|
||||
ReduxAction,
|
||||
ReduxActionTypes,
|
||||
ReduxActionWithoutPayload,
|
||||
UpdateCanvasPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { WidgetOperation } from "widgets/BaseWidget";
|
||||
import { FetchPageRequest, PageLayout, SavePageResponse } from "api/PageApi";
|
||||
import { APP_MODE, UrlDataState } from "reducers/entityReducers/appReducer";
|
||||
import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
|
||||
|
||||
export interface FetchPageListPayload {
|
||||
applicationId: string;
|
||||
|
|
@ -46,18 +47,14 @@ export const fetchPublishedPage = (pageId: string, bustCache = false) => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const fetchPageSuccess = (
|
||||
postEvalActions: ReduxAction<unknown>[],
|
||||
): EvaluationReduxAction<unknown> => {
|
||||
export const fetchPageSuccess = (): ReduxActionWithoutPayload => {
|
||||
return {
|
||||
type: ReduxActionTypes.FETCH_PAGE_SUCCESS,
|
||||
payload: {},
|
||||
postEvalActions,
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchPublishedPageSuccess = (
|
||||
postEvalActions: ReduxAction<unknown>[],
|
||||
postEvalActions: Array<ReduxAction<unknown> | ReduxActionWithoutPayload>,
|
||||
): EvaluationReduxAction<undefined> => ({
|
||||
type: ReduxActionTypes.FETCH_PUBLISHED_PAGE_SUCCESS,
|
||||
postEvalActions,
|
||||
|
|
@ -230,12 +227,23 @@ export type WidgetAddChildren = {
|
|||
}>;
|
||||
};
|
||||
|
||||
export type WidgetUpdateProperty = {
|
||||
widgetId: string;
|
||||
propertyPath: string;
|
||||
propertyValue: any;
|
||||
};
|
||||
|
||||
export const updateWidget = (
|
||||
operation: WidgetOperation,
|
||||
widgetId: string,
|
||||
payload: any,
|
||||
): ReduxAction<
|
||||
WidgetAddChild | WidgetMove | WidgetResize | WidgetDelete | WidgetAddChildren
|
||||
| WidgetAddChild
|
||||
| WidgetMove
|
||||
| WidgetResize
|
||||
| WidgetDelete
|
||||
| WidgetAddChildren
|
||||
| WidgetUpdateProperty
|
||||
> => {
|
||||
return {
|
||||
type: ReduxActionTypes["WIDGET_" + operation],
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import { PluginFormPayload } from "api/PluginApi";
|
||||
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||
|
||||
export const fetchPlugins = (): ReduxActionWithoutPayload => ({
|
||||
type: ReduxActionTypes.FETCH_PLUGINS_REQUEST,
|
||||
|
|
@ -17,6 +18,7 @@ export type PluginFormsPayload = {
|
|||
formConfigs: Record<string, any[]>;
|
||||
editorConfigs: Record<string, any[]>;
|
||||
settingConfigs: Record<string, any[]>;
|
||||
dependencies: Record<string, DependencyMap>;
|
||||
};
|
||||
|
||||
export const fetchPluginFormConfigsSuccess = (
|
||||
|
|
|
|||
11
app/client/src/actions/propertyPaneActions.test.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import * as actions from "./propertyPaneActions";
|
||||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
|
||||
describe("property pane action actions", () => {
|
||||
it("should create an action hide Property Pane", () => {
|
||||
const expectedAction = {
|
||||
type: ReduxActionTypes.HIDE_PROPERTY_PANE,
|
||||
};
|
||||
expect(actions.hidePropertyPane()).toEqual(expectedAction);
|
||||
});
|
||||
});
|
||||
|
|
@ -8,3 +8,9 @@ export const updateWidgetName = (widgetId: string, newName: string) => {
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const hidePropertyPane = () => {
|
||||
return {
|
||||
type: ReduxActionTypes.HIDE_PROPERTY_PANE,
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import {
|
|||
ReduxActionTypes,
|
||||
ReduxAction,
|
||||
ReduxActionErrorTypes,
|
||||
ReduxActionWithoutPayload,
|
||||
} from "constants/ReduxActionConstants";
|
||||
import {
|
||||
ExecuteActionPayload,
|
||||
ExecuteErrorPayload,
|
||||
PageAction,
|
||||
} from "constants/AppsmithActionConstants/ActionConstants";
|
||||
import { BatchAction, batchAction } from "actions/batchActions";
|
||||
import PerformanceTracker, {
|
||||
|
|
@ -30,11 +30,8 @@ export const executeActionError = (
|
|||
};
|
||||
};
|
||||
|
||||
export const executePageLoadActions = (
|
||||
payload: PageAction[][],
|
||||
): ReduxAction<PageAction[][]> => ({
|
||||
export const executePageLoadActions = (): ReduxActionWithoutPayload => ({
|
||||
type: ReduxActionTypes.EXECUTE_PAGE_LOAD_ACTIONS,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const disableDragAction = (
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Api from "api/Api";
|
|||
import { AxiosPromise } from "axios";
|
||||
import { GenericApiResponse } from "api/ApiResponses";
|
||||
import { PluginType } from "entities/Action";
|
||||
import { DependencyMap } from "utils/DynamicBindingUtils";
|
||||
|
||||
export interface Plugin {
|
||||
id: string;
|
||||
|
|
@ -21,6 +22,7 @@ export interface PluginFormPayload {
|
|||
form: any[];
|
||||
editor: any[];
|
||||
setting: any[];
|
||||
dependencies: DependencyMap;
|
||||
}
|
||||
|
||||
class PluginsApi extends Api {
|
||||
|
|
|
|||
3
app/client/src/assets/icons/ads/bug.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="18" viewBox="0 0 20 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1935 0C8.53666 0 7.19351 1.34315 7.19351 3H13.3474C13.3474 1.34315 12.0042 0 10.3474 0H10.1935ZM15.0329 4.2H4.96715L1.67954 2.06384L0.399787 3.93616L4.11659 6.3512V8.625H0V10.875H4.11659V11.8462C4.11659 12.2648 4.15839 12.6736 4.23804 13.0688L0.399787 15.5627L1.67954 17.4351L5.11276 15.2043C6.21093 16.8875 8.11076 18 10.2704 18C12.3244 18 14.1434 16.9937 15.2612 15.4473L18.3205 17.4351L19.6002 15.5627L16.2329 13.3748C16.3579 12.886 16.4243 12.3738 16.4243 11.8462V10.875H20V8.625H16.4243V5.99976L19.6002 3.93616L18.3205 2.06384L15.0329 4.2Z" fill="#716E6E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 719 B |
3
app/client/src/assets/icons/ads/cancel.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 0C3.14027 0 0 3.14027 0 7C0 10.8597 3.14027 14 7 14C10.8597 14 14 10.8597 14 7C14 3.14027 10.8597 0 7 0ZM7 12.25C4.10525 12.25 1.75 9.89475 1.75 7C1.75 5.89515 2.08496 4.85067 2.72185 3.95943L10.0406 11.2781C9.14933 11.915 8.10485 12.25 7 12.25ZM11.2781 10.0406L3.95943 2.72185C4.85067 2.08496 5.89515 1.75 7 1.75C9.89475 1.75 12.25 4.10525 12.25 7C12.25 8.10485 11.915 9.14933 11.2781 10.0406Z" fill="#858282"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 528 B |
4
app/client/src/assets/icons/ads/cross.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 1L1 13" stroke="#716E6E" stroke-width="1.5"/>
|
||||
<path d="M13 13L1 1" stroke="#716E6E" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 221 B |
4
app/client/src/assets/icons/ads/open.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="2.5" y="2.5" width="11" height="11" rx="1.5" stroke="#858282" stroke-opacity="0.6"/>
|
||||
<path d="M6 10L10 6M10 6H6.92308M10 6V9.07692" stroke="#858282" stroke-opacity="0.6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 331 B |
10
app/client/src/assets/icons/ads/wand.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.879 0.418515C10.879 0.649535 11.067 0.83703 11.2976 0.83703C11.5286 0.83703 11.7161 1.02411 11.7161 1.25554C11.7161 1.48698 11.5286 1.67406 11.2976 1.67406C11.067 1.67406 10.879 1.86155 10.879 2.09257C10.879 2.32401 10.6915 2.51109 10.4605 2.51109C10.2295 2.51109 10.042 2.32401 10.042 2.09257C10.042 1.86155 9.8541 1.67406 9.6235 1.67406C9.39248 1.67406 9.20498 1.48698 9.20498 1.25554C9.20498 1.02411 9.39248 0.83703 9.6235 0.83703C9.8541 0.83703 10.042 0.649535 10.042 0.418515C10.042 0.187076 10.2295 0 10.4605 0C10.6915 0 10.879 0.187076 10.879 0.418515ZM11.0968 2.91647L13.8833 5.70297L3.58631 16L0.799805 13.2135L11.0968 2.91647ZM11.1158 6.71379L10.1201 5.71815L11.1158 4.7225L12.1114 5.71815L11.1158 6.71379ZM2.58583 13.2519L3.58147 14.2475L10.2271 7.60191L9.23142 6.60627L2.58583 13.2519ZM4.21032 5.71869C4.55622 5.71869 4.83809 5.99993 4.83809 6.34646C4.83809 6.69362 5.11933 6.97423 5.46586 6.97423C5.8124 6.97423 6.09364 6.69362 6.09364 6.34646C6.09364 5.99993 6.37551 5.71869 6.72141 5.71869C7.06794 5.71869 7.34918 5.43807 7.34918 5.09092C7.34918 4.74376 7.06794 4.46314 6.72141 4.46314C6.37551 4.46314 6.09364 4.1819 6.09364 3.83537C6.09364 3.48821 5.8124 3.2076 5.46586 3.2076C5.11933 3.2076 4.83809 3.48821 4.83809 3.83537C4.83809 4.1819 4.55622 4.46314 4.21032 4.46314C3.86379 4.46314 3.58255 4.74376 3.58255 5.09092C3.58255 5.43807 3.86379 5.71869 4.21032 5.71869ZM13.2079 9.38031C13.2079 9.66909 13.4428 9.90345 13.731 9.90345C14.0198 9.90345 14.2542 10.1373 14.2542 10.4266C14.2542 10.7159 14.0198 10.9497 13.731 10.9497C13.4428 10.9497 13.2079 11.1841 13.2079 11.4729C13.2079 11.7622 12.9735 11.996 12.6847 11.996C12.396 11.996 12.1616 11.7622 12.1616 11.4729C12.1616 11.1841 11.9267 10.9497 11.6384 10.9497C11.3497 10.9497 11.1153 10.7159 11.1153 10.4266C11.1153 10.1373 11.3497 9.90345 11.6384 9.90345C11.9267 9.90345 12.1616 9.66909 12.1616 9.38031C12.1616 9.09101 12.396 8.85717 12.6847 8.85717C12.9735 8.85717 13.2079 9.09101 13.2079 9.38031ZM15.3003 7.39249C15.185 7.39249 15.091 7.29874 15.091 7.18323C15.091 7.06751 14.9973 6.97398 14.8818 6.97398C14.7663 6.97398 14.6725 7.06751 14.6725 7.18323C14.6725 7.29874 14.5786 7.39249 14.4633 7.39249C14.3478 7.39249 14.254 7.48603 14.254 7.60175C14.254 7.71747 14.3478 7.81101 14.4633 7.81101C14.5786 7.81101 14.6725 7.90475 14.6725 8.02026C14.6725 8.13598 14.7663 8.22952 14.8818 8.22952C14.9973 8.22952 15.091 8.13598 15.091 8.02026C15.091 7.90475 15.185 7.81101 15.3003 7.81101C15.4158 7.81101 15.5096 7.71747 15.5096 7.60175C15.5096 7.48603 15.4158 7.39249 15.3003 7.39249Z" fill="#E22C2C"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
4
app/client/src/assets/icons/ads/warning-triangle.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="72" height="73" viewBox="0 0 72 73" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M42.3253 4.44421L71.3309 63.0473C72.8988 66.2151 71.6018 70.0542 68.434 71.6221C67.5514 72.059 66.5798 72.2862 65.595 72.2862H6.4C2.86538 72.2862 0 69.4209 0 65.8863C0 64.8666 0.243617 63.8618 0.710563 62.9553L30.9 4.35225C32.5187 1.21006 36.3782 -0.0249646 39.5204 1.59374C40.7358 2.21989 41.7188 3.21883 42.3253 4.44421Z" fill="#E8E8E8"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M35.9033 53.8863C38.1125 53.8863 39.9033 55.6772 39.9033 57.8863C39.9033 60.0955 38.1125 61.8863 35.9033 61.8863C33.6942 61.8863 31.9033 60.0955 31.9033 57.8863C31.9033 55.6772 33.6942 53.8863 35.9033 53.8863ZM35.9033 23.4863C37.9863 23.4863 39.6749 25.1749 39.6749 27.2579C39.6749 27.3318 39.6728 27.4057 39.6684 27.4794L38.4443 48.2905C38.3651 49.6358 37.251 50.6863 35.9033 50.6863C34.5556 50.6863 33.4415 49.6358 33.3624 48.2905L32.1382 27.4794C32.0159 25.4 33.6024 23.6152 35.6818 23.4928C35.7556 23.4885 35.8294 23.4863 35.9033 23.4863Z" fill="#979797"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
app/client/src/assets/icons/widget/list.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="20" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.79999 11C9.46273 11 10 11.5372 10 12.2V18.8C10 19.4628 9.46273 20 8.79999 20H1.20001C0.53727 20 0 19.4628 0 18.8V12.2C0 11.5372 0.53727 11 1.20001 11H8.79999ZM20 16V19H12V16H20ZM6.24258 15.5072C6.22756 15.5186 6.21421 15.5321 6.20297 15.5472L4.85718 17.3627C4.7914 17.4514 4.66612 17.47 4.57739 17.4042C4.5678 17.3971 4.55889 17.3891 4.55072 17.3804L3.52838 16.2915C3.45278 16.211 3.32619 16.207 3.24567 16.2826C3.23763 16.2901 3.23023 16.2984 3.22354 16.3071L1.74039 18.2513C1.67339 18.3392 1.69026 18.4646 1.77808 18.5316C1.81293 18.5582 1.85555 18.5726 1.89938 18.5726H8.42206C8.53252 18.5726 8.62204 18.4831 8.62204 18.3727C8.62204 18.3289 8.60772 18.2864 8.58124 18.2516L6.52283 15.5453C6.45596 15.4574 6.3305 15.4403 6.24258 15.5072ZM24 11V14H12V11H24ZM8.79999 0C9.46273 0 10 0.537211 10 1.19995V7.80005C10 8.46279 9.46273 9 8.79999 9H1.20001C0.53727 9 0 8.46279 0 7.80005V1.19995C0 0.537211 0.53727 0 1.20001 0H8.79999ZM20 5V8H12V5H20ZM6.24258 4.5072C6.22756 4.51862 6.21421 4.53209 6.20297 4.54724L4.85718 6.36267C4.7914 6.4514 4.66612 6.46995 4.57739 6.40417C4.5678 6.39706 4.55889 6.38907 4.55072 6.38037L3.52838 5.2915C3.45278 5.21098 3.32619 5.20699 3.24567 5.28259C3.23763 5.29014 3.23023 5.29837 3.22354 5.30713L1.74039 7.25134C1.67339 7.33917 1.69026 7.46463 1.77808 7.53162C1.81293 7.55821 1.85555 7.57263 1.89938 7.57263H8.42206C8.53252 7.57263 8.62204 7.48314 8.62204 7.37268C8.62204 7.32894 8.60772 7.2864 8.58124 7.25159L6.52283 4.54529C6.45596 4.45737 6.3305 4.44033 6.24258 4.5072ZM24 0V3H12V0H24Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,6 +1,10 @@
|
|||
import React, { forwardRef, Ref } from "react";
|
||||
import { ReactComponent as DeleteIcon } from "assets/icons/ads/delete.svg";
|
||||
import { ReactComponent as BookIcon } from "assets/icons/ads/book.svg";
|
||||
import { ReactComponent as BugIcon } from "assets/icons/ads/bug.svg";
|
||||
import { ReactComponent as CancelIcon } from "assets/icons/ads/cancel.svg";
|
||||
import { ReactComponent as CrossIcon } from "assets/icons/ads/cross.svg";
|
||||
import { ReactComponent as OpenIcon } from "assets/icons/ads/open.svg";
|
||||
import { ReactComponent as UserIcon } from "assets/icons/ads/user.svg";
|
||||
import { ReactComponent as GeneralIcon } from "assets/icons/ads/general.svg";
|
||||
import { ReactComponent as BillingIcon } from "assets/icons/ads/billing.svg";
|
||||
|
|
@ -11,6 +15,7 @@ import { ReactComponent as SuccessIcon } from "assets/icons/ads/success.svg";
|
|||
import { ReactComponent as SearchIcon } from "assets/icons/ads/search.svg";
|
||||
import { ReactComponent as CloseIcon } from "assets/icons/ads/close.svg";
|
||||
import { ReactComponent as WarningIcon } from "assets/icons/ads/warning.svg";
|
||||
import { ReactComponent as WarningTriangleIcon } from "assets/icons/ads/warning-triangle.svg";
|
||||
import { ReactComponent as DownArrow } from "assets/icons/ads/down_arrow.svg";
|
||||
import { ReactComponent as ShareIcon } from "assets/icons/ads/share.svg";
|
||||
import { ReactComponent as RocketIcon } from "assets/icons/ads/launch.svg";
|
||||
|
|
@ -37,6 +42,7 @@ import { ReactComponent as RightArrowIcon } from "assets/icons/ads/right-arrow.s
|
|||
import { ReactComponent as DatasourceIcon } from "assets/icons/ads/datasource.svg";
|
||||
import { ReactComponent as PlayIcon } from "assets/icons/ads/play.svg";
|
||||
import { ReactComponent as DesktopIcon } from "assets/icons/ads/desktop.svg";
|
||||
import { ReactComponent as WandIcon } from "assets/icons/ads/wand.svg";
|
||||
import { ReactComponent as MobileIcon } from "assets/icons/ads/mobile.svg";
|
||||
import { ReactComponent as TabletIcon } from "assets/icons/ads/tablet.svg";
|
||||
import { ReactComponent as FluidIcon } from "assets/icons/ads/fluid.svg";
|
||||
|
|
@ -95,7 +101,11 @@ export const sizeHandler = (size?: IconSize) => {
|
|||
|
||||
export const IconCollection = [
|
||||
"book",
|
||||
"bug",
|
||||
"cancel",
|
||||
"cross",
|
||||
"delete",
|
||||
"open",
|
||||
"user",
|
||||
"general",
|
||||
"billing",
|
||||
|
|
@ -114,6 +124,7 @@ export const IconCollection = [
|
|||
"view-all",
|
||||
"view-less",
|
||||
"warning",
|
||||
"warning-triangle",
|
||||
"downArrow",
|
||||
"context-menu",
|
||||
"duplicate",
|
||||
|
|
@ -133,6 +144,7 @@ export const IconCollection = [
|
|||
"datasource",
|
||||
"play",
|
||||
"desktop",
|
||||
"wand",
|
||||
"mobile",
|
||||
"tablet",
|
||||
"fluid",
|
||||
|
|
@ -154,17 +166,29 @@ export const IconWrapper = styled.span<IconProps>`
|
|||
svg {
|
||||
width: ${(props) => sizeHandler(props.size)}px;
|
||||
height: ${(props) => sizeHandler(props.size)}px;
|
||||
${(props) =>
|
||||
!props.keepColors
|
||||
? `
|
||||
path {
|
||||
fill: ${(props) => props.fillColor || props.theme.colors.icon.normal};
|
||||
fill: ${props.fillColor || props.theme.colors.icon.normal};
|
||||
}
|
||||
}
|
||||
circle {
|
||||
fill: ${props.fillColor || props.theme.colors.icon.normal};
|
||||
}
|
||||
`
|
||||
: ""}
|
||||
${(props) => (props.invisible ? `visibility: hidden;` : null)};
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
${(props) =>
|
||||
!props.keepColors
|
||||
? `
|
||||
path {
|
||||
fill: ${(props) => props.theme.colors.icon.hover};
|
||||
fill: ${props.theme.colors.icon.hover};
|
||||
}
|
||||
`
|
||||
: ""}
|
||||
}
|
||||
|
||||
&:active {
|
||||
|
|
@ -182,6 +206,7 @@ export type IconProps = {
|
|||
className?: string;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
fillColor?: string;
|
||||
keepColors?: boolean;
|
||||
};
|
||||
|
||||
const Icon = forwardRef(
|
||||
|
|
@ -191,9 +216,21 @@ const Icon = forwardRef(
|
|||
case "book":
|
||||
returnIcon = <BookIcon />;
|
||||
break;
|
||||
case "bug":
|
||||
returnIcon = <BugIcon />;
|
||||
break;
|
||||
case "cancel":
|
||||
returnIcon = <CancelIcon />;
|
||||
break;
|
||||
case "cross":
|
||||
returnIcon = <CrossIcon />;
|
||||
break;
|
||||
case "delete":
|
||||
returnIcon = <DeleteIcon />;
|
||||
break;
|
||||
case "open":
|
||||
returnIcon = <OpenIcon />;
|
||||
break;
|
||||
case "user":
|
||||
returnIcon = <UserIcon />;
|
||||
break;
|
||||
|
|
@ -233,6 +270,9 @@ const Icon = forwardRef(
|
|||
case "rocket":
|
||||
returnIcon = <RocketIcon />;
|
||||
break;
|
||||
case "wand":
|
||||
returnIcon = <WandIcon />;
|
||||
break;
|
||||
case "workspace":
|
||||
returnIcon = <WorkspaceIcon />;
|
||||
break;
|
||||
|
|
@ -263,6 +303,9 @@ const Icon = forwardRef(
|
|||
case "warning":
|
||||
returnIcon = <WarningIcon />;
|
||||
break;
|
||||
case "warning-triangle":
|
||||
returnIcon = <WarningTriangleIcon />;
|
||||
break;
|
||||
case "arrow-left":
|
||||
returnIcon = <ArrowLeft />;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -17,9 +17,6 @@ const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>`
|
|||
user-select: none;
|
||||
border-radius: 0px;
|
||||
height: 100%;
|
||||
.${Classes.ICON} {
|
||||
margin-right: ${(props) => props.theme.spaces[3]}px;
|
||||
}
|
||||
.react-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -104,6 +101,9 @@ const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>`
|
|||
const TabTitleWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.${Classes.ICON} {
|
||||
margin-right: ${(props) => props.theme.spaces[3]}px;
|
||||
}
|
||||
`;
|
||||
|
||||
const TabTitle = styled.span`
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ const StyledInput = styled((props) => {
|
|||
dataType={dataType}
|
||||
/>
|
||||
) : (
|
||||
<input {...inputProps} />
|
||||
<input ref={inputRef} {...inputProps} />
|
||||
);
|
||||
})<TextInputProps & { inputStyle: boxReturnType; isValid: boolean }>`
|
||||
width: ${(props) => (props.fill ? "100%" : "320px")};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { toast, ToastOptions, ToastContainer } from "react-toastify";
|
|||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ReduxActionType } from "constants/ReduxActionConstants";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { Colors } from "constants/Colors";
|
||||
import DebugButton from "components/editorComponents/Debugger/DebugCTA";
|
||||
|
||||
type ToastProps = ToastOptions &
|
||||
CommonComponentProps & {
|
||||
|
|
@ -59,7 +61,7 @@ const ToastBody = styled.div<{
|
|||
justify-content: space-between;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
.${Classes.ICON} {
|
||||
div > .${Classes.ICON} {
|
||||
cursor: auto;
|
||||
margin-right: ${(props) => props.theme.spaces[3]}px;
|
||||
margin-top: ${(props) => props.theme.spaces[1] / 2}px;
|
||||
|
|
@ -104,6 +106,10 @@ const FlexContainer = styled.div`
|
|||
align-items: flex-start;
|
||||
`;
|
||||
|
||||
const StyledDebugButton = styled(DebugButton)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const ToastComponent = (props: ToastProps & { undoAction?: () => void }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
|
@ -116,14 +122,19 @@ const ToastComponent = (props: ToastProps & { undoAction?: () => void }) => {
|
|||
>
|
||||
<FlexContainer>
|
||||
{props.variant === Variant.success ? (
|
||||
<Icon name="success" size={IconSize.XXL} />
|
||||
<Icon name="success" size={IconSize.XXL} fillColor={Colors.GREEN} />
|
||||
) : props.variant === Variant.warning ? (
|
||||
<Icon name="warning" size={IconSize.XXL} />
|
||||
) : null}
|
||||
{props.variant === Variant.danger ? (
|
||||
<Icon name="error" size={IconSize.XXL} />
|
||||
) : null}
|
||||
<Text type={TextType.P1}>{props.text}</Text>
|
||||
<div>
|
||||
<Text type={TextType.P1}>{props.text}</Text>
|
||||
{props.variant === Variant.danger ? (
|
||||
<StyledDebugButton source={"TOAST"} />
|
||||
) : null}
|
||||
</div>
|
||||
</FlexContainer>
|
||||
<div className="undo-section">
|
||||
{props.onUndo || props.dispatchableAction ? (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import _, { isString } from "lodash";
|
||||
import _, { get } from "lodash";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme";
|
||||
import { getAppsmithConfigs } from "configs";
|
||||
import { ChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget";
|
||||
import { AllChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget";
|
||||
import log from "loglevel";
|
||||
|
||||
export interface CustomFusionChartConfig {
|
||||
|
|
@ -43,7 +43,7 @@ FusionCharts.options.license({
|
|||
|
||||
export interface ChartComponentProps {
|
||||
chartType: ChartType;
|
||||
chartData: ChartData[];
|
||||
chartData: AllChartData;
|
||||
customFusionChartConfig: CustomFusionChartConfig;
|
||||
xAxisName: string;
|
||||
yAxisName: string;
|
||||
|
|
@ -73,7 +73,8 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
|
||||
getChartType = () => {
|
||||
const { chartType, allowHorizontalScroll, chartData } = this.props;
|
||||
const isMSChart = chartData.length > 1;
|
||||
const dataLength = Object.keys(chartData).length;
|
||||
const isMSChart = dataLength > 1;
|
||||
switch (chartType) {
|
||||
case "PIE_CHART":
|
||||
return "pie2d";
|
||||
|
|
@ -107,9 +108,11 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
};
|
||||
|
||||
getChartData = () => {
|
||||
const chartData: ChartData[] = this.props.chartData;
|
||||
const chartData: AllChartData = this.props.chartData;
|
||||
const dataLength = Object.keys(chartData).length;
|
||||
|
||||
if (chartData.length === 0) {
|
||||
// if datalength is zero, just pass a empty datum
|
||||
if (dataLength === 0) {
|
||||
return [
|
||||
{
|
||||
label: "",
|
||||
|
|
@ -118,14 +121,13 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
];
|
||||
}
|
||||
|
||||
let data: ChartDataPoint[] = chartData[0].data;
|
||||
if (isString(chartData[0].data)) {
|
||||
try {
|
||||
data = JSON.parse(chartData[0].data);
|
||||
} catch (e) {
|
||||
data = [];
|
||||
}
|
||||
const firstKey = Object.keys(chartData)[0] as string;
|
||||
let data = get(chartData, `${firstKey}.data`, []) as ChartDataPoint[];
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
data = [];
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return [
|
||||
{
|
||||
|
|
@ -134,6 +136,7 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
},
|
||||
];
|
||||
}
|
||||
|
||||
return data.map((item) => {
|
||||
return {
|
||||
label: item.x,
|
||||
|
|
@ -142,22 +145,30 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
});
|
||||
};
|
||||
|
||||
getChartCategoriesMutliSeries = (chartData: ChartData[]) => {
|
||||
getChartCategoriesMutliSeries = (chartData: AllChartData) => {
|
||||
const categories: string[] = [];
|
||||
for (let index = 0; index < chartData.length; index++) {
|
||||
const data: ChartDataPoint[] = chartData[index].data;
|
||||
|
||||
Object.keys(chartData).forEach((key: string) => {
|
||||
let data = get(chartData, `${key}.data`, []) as ChartDataPoint[];
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
data = [];
|
||||
}
|
||||
|
||||
for (let dataIndex = 0; dataIndex < data.length; dataIndex++) {
|
||||
const category = data[dataIndex].x;
|
||||
if (!categories.includes(category)) {
|
||||
categories.push(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return categories;
|
||||
};
|
||||
|
||||
getChartCategories = (chartData: ChartData[]) => {
|
||||
getChartCategories = (chartData: AllChartData) => {
|
||||
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
|
||||
|
||||
if (categories.length === 0) {
|
||||
return [
|
||||
{
|
||||
|
|
@ -192,9 +203,18 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
});
|
||||
};
|
||||
|
||||
getChartDataset = (chartData: ChartData[]) => {
|
||||
/**
|
||||
* creates dataset need by fusion chart from widget object-data
|
||||
*
|
||||
* @param chartData
|
||||
* @returns
|
||||
*/
|
||||
getChartDataset = (chartData: AllChartData) => {
|
||||
const categories: string[] = this.getChartCategoriesMutliSeries(chartData);
|
||||
return chartData.map((item: ChartData) => {
|
||||
|
||||
const dataset = Object.keys(chartData).map((key: string) => {
|
||||
const item = get(chartData, `${key}`);
|
||||
|
||||
const seriesChartData: Array<Record<
|
||||
string,
|
||||
unknown
|
||||
|
|
@ -204,6 +224,8 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
data: seriesChartData,
|
||||
};
|
||||
});
|
||||
|
||||
return dataset;
|
||||
};
|
||||
|
||||
getChartConfig = () => {
|
||||
|
|
@ -219,10 +241,9 @@ class ChartComponent extends React.Component<ChartComponentProps> {
|
|||
};
|
||||
|
||||
getChartDataSource = () => {
|
||||
if (
|
||||
this.props.chartData.length <= 1 ||
|
||||
this.props.chartType === "PIE_CHART"
|
||||
) {
|
||||
const dataLength = Object.keys(this.props.chartData).length;
|
||||
|
||||
if (dataLength <= 1 || this.props.chartType === "PIE_CHART") {
|
||||
return {
|
||||
chart: this.getChartConfig(),
|
||||
data: this.getChartData(),
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ const StyledContainerComponent = styled.div<
|
|||
background: ${(props) => props.backgroundColor};
|
||||
|
||||
${(props) => (!props.isVisible ? invisible : "")};
|
||||
opacity: ${(props) => (props.resizeDisabled ? "0.5" : "1")};
|
||||
pointer-events: ${(props) => (props.resizeDisabled ? "none" : "inherit")};
|
||||
overflow: hidden;
|
||||
${(props) => (props.shouldScrollContents ? scrollContents : "")}
|
||||
}`;
|
||||
|
|
@ -32,6 +34,7 @@ const StyledContainerComponent = styled.div<
|
|||
const ContainerComponent = (props: ContainerComponentProps) => {
|
||||
const containerStyle = props.containerStyle || "card";
|
||||
const containerRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.shouldScrollContents) {
|
||||
const supportsNativeSmoothScroll =
|
||||
|
|
@ -69,6 +72,7 @@ export interface ContainerComponentProps extends ComponentProps {
|
|||
className?: string;
|
||||
backgroundColor?: Color;
|
||||
shouldScrollContents?: boolean;
|
||||
resizeDisabled?: boolean;
|
||||
}
|
||||
|
||||
export default ContainerComponent;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactNode } from "react";
|
||||
import React, { CSSProperties, ReactNode, useMemo } from "react";
|
||||
import { BaseStyle } from "widgets/BaseWidget";
|
||||
import { WIDGET_PADDING } from "constants/WidgetConstants";
|
||||
import { generateClassName } from "utils/generators";
|
||||
|
|
@ -23,27 +23,35 @@ export const PositionedContainer = (props: PositionedContainerProps) => {
|
|||
const padding = WIDGET_PADDING;
|
||||
const openPropertyPane = useClickOpenPropPane();
|
||||
|
||||
// memoized classname
|
||||
const containerClassName = useMemo(() => {
|
||||
return (
|
||||
generateClassName(props.widgetId) +
|
||||
" positioned-widget " +
|
||||
`t--widget-${props.widgetType
|
||||
.split("_")
|
||||
.join("")
|
||||
.toLowerCase()}`
|
||||
);
|
||||
}, [props.widgetType, props.widgetId]);
|
||||
const containerStyle: CSSProperties = useMemo(() => {
|
||||
return {
|
||||
position: "absolute",
|
||||
left: x,
|
||||
top: y,
|
||||
height: props.style.componentHeight + (props.style.heightUnit || "px"),
|
||||
width: props.style.componentWidth + (props.style.widthUnit || "px"),
|
||||
padding: padding + "px",
|
||||
};
|
||||
}, [props.style]);
|
||||
|
||||
return (
|
||||
<PositionedWidget
|
||||
onClickCapture={openPropertyPane}
|
||||
style={{
|
||||
position: "absolute",
|
||||
left: x,
|
||||
top: y,
|
||||
height: props.style.componentHeight + (props.style.heightUnit || "px"),
|
||||
width: props.style.componentWidth + (props.style.widthUnit || "px"),
|
||||
padding: padding + "px",
|
||||
}}
|
||||
style={containerStyle}
|
||||
id={props.widgetId}
|
||||
//Before you remove: This is used by property pane to reference the element
|
||||
className={
|
||||
generateClassName(props.widgetId) +
|
||||
" " +
|
||||
`t--widget-${props.widgetType
|
||||
.split("_")
|
||||
.join("")
|
||||
.toLowerCase()}`
|
||||
}
|
||||
className={containerClassName}
|
||||
>
|
||||
{props.children}
|
||||
</PositionedWidget>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import React, { RefObject, ReactNode, useEffect, useRef } from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import { ComponentProps } from "./BaseComponent";
|
||||
import { TabsWidgetProps, TabContainerWidgetProps } from "widgets/TabsWidget";
|
||||
import {
|
||||
TabsWidgetProps,
|
||||
TabContainerWidgetProps,
|
||||
} from "widgets/Tabs/TabsWidget";
|
||||
import { generateClassName, getCanvasClassName } from "utils/generators";
|
||||
import { getBorderCSSShorthand } from "constants/DefaultTheme";
|
||||
import ScrollIndicator from "components/ads/ScrollIndicator";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState, useRef, RefObject, useCallback } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { withRouter, RouteComponentProps } from "react-router";
|
||||
import { BaseText } from "components/designSystems/blueprint/TextComponent";
|
||||
|
|
@ -12,24 +12,26 @@ import ReadOnlyEditor from "components/editorComponents/ReadOnlyEditor";
|
|||
import { getActionResponses } from "selectors/entitiesSelector";
|
||||
import { Colors } from "constants/Colors";
|
||||
import _ from "lodash";
|
||||
import { RequestView } from "./RequestView";
|
||||
import { useLocalStorage } from "utils/hooks/localstorage";
|
||||
import {
|
||||
CHECK_REQUEST_BODY,
|
||||
createMessage,
|
||||
SHOW_REQUEST,
|
||||
} from "constants/messages";
|
||||
import { CHECK_REQUEST_BODY, createMessage } from "constants/messages";
|
||||
import { TabComponent } from "components/ads/Tabs";
|
||||
import Text, { Case, TextType } from "components/ads/Text";
|
||||
import Text, { TextType } from "components/ads/Text";
|
||||
import Icon from "components/ads/Icon";
|
||||
import { Classes, Variant } from "components/ads/common";
|
||||
import { EditorTheme } from "./CodeEditor/EditorConfig";
|
||||
import Callout from "components/ads/Callout";
|
||||
import DebuggerLogs from "./Debugger/DebuggerLogs";
|
||||
import ErrorLogs from "./Debugger/Errors";
|
||||
import Resizer, { ResizerCSS } from "./Debugger/Resizer";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { DebugButton } from "./Debugger/DebugCTA";
|
||||
|
||||
const ResponseContainer = styled.div`
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 50%;
|
||||
${ResizerCSS}
|
||||
// Initial height of bottom tabs
|
||||
height: 60%;
|
||||
// Minimum height of bottom tabs as it can be resized
|
||||
min-height: 36px;
|
||||
background-color: ${(props) => props.theme.colors.apiPane.responseBody.bg};
|
||||
|
||||
.react-tabs__tab-panel {
|
||||
|
|
@ -121,14 +123,7 @@ const NoResponseContainer = styled.div`
|
|||
const FailedMessage = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const ShowRequestText = styled.a`
|
||||
display: flex;
|
||||
margin-left: ${(props) => props.theme.spaces[1] + 1}px;
|
||||
.${Classes.ICON} {
|
||||
margin-left: ${(props) => props.theme.spaces[1] + 1}px;
|
||||
}
|
||||
margin-left: 5px;
|
||||
`;
|
||||
|
||||
interface ReduxStateProps {
|
||||
|
|
@ -137,7 +132,10 @@ interface ReduxStateProps {
|
|||
}
|
||||
|
||||
type Props = ReduxStateProps &
|
||||
RouteComponentProps<APIEditorRouteParams> & { theme?: EditorTheme };
|
||||
RouteComponentProps<APIEditorRouteParams> & {
|
||||
theme?: EditorTheme;
|
||||
apiName: string;
|
||||
};
|
||||
|
||||
export const EMPTY_RESPONSE: ActionResponse = {
|
||||
statusCode: "",
|
||||
|
|
@ -173,12 +171,20 @@ const ApiResponseView = (props: Props) => {
|
|||
isRunning = props.isRunning[apiId];
|
||||
hasFailed = response.statusCode ? response.statusCode[0] !== "2" : false;
|
||||
}
|
||||
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
||||
|
||||
const [requestDebugVisible, setRequestDebugVisible] = useLocalStorage(
|
||||
"requestDebugVisible",
|
||||
"true",
|
||||
);
|
||||
|
||||
const onDebugClick = useCallback(() => {
|
||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||
source: "API",
|
||||
});
|
||||
setSelectedIndex(1);
|
||||
}, []);
|
||||
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
const tabs = [
|
||||
{
|
||||
|
|
@ -187,28 +193,20 @@ const ApiResponseView = (props: Props) => {
|
|||
panelComponent: (
|
||||
<ResponseTabWrapper>
|
||||
{hasFailed && !isRunning && requestDebugVisible && (
|
||||
<Callout
|
||||
text={createMessage(CHECK_REQUEST_BODY)}
|
||||
label={
|
||||
<FailedMessage>
|
||||
<ShowRequestText
|
||||
href={"#!"}
|
||||
onClick={() => {
|
||||
setSelectedIndex(1);
|
||||
}}
|
||||
>
|
||||
<Text type={TextType.H6} case={Case.UPPERCASE}>
|
||||
{createMessage(SHOW_REQUEST)}
|
||||
</Text>
|
||||
<Icon name="right-arrow" />
|
||||
</ShowRequestText>
|
||||
</FailedMessage>
|
||||
}
|
||||
variant={Variant.warning}
|
||||
fill
|
||||
closeButton
|
||||
onClose={() => setRequestDebugVisible(false)}
|
||||
/>
|
||||
<>
|
||||
<Callout
|
||||
text={createMessage(CHECK_REQUEST_BODY)}
|
||||
label={
|
||||
<FailedMessage>
|
||||
<DebugButton onClick={onDebugClick} />
|
||||
</FailedMessage>
|
||||
}
|
||||
variant={Variant.danger}
|
||||
fill
|
||||
closeButton
|
||||
onClose={() => setRequestDebugVisible(false)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{_.isEmpty(response.statusCode) ? (
|
||||
<NoResponseContainer>
|
||||
|
|
@ -230,25 +228,20 @@ const ApiResponseView = (props: Props) => {
|
|||
),
|
||||
},
|
||||
{
|
||||
key: "request",
|
||||
title: "Request",
|
||||
panelComponent: (
|
||||
<RequestView
|
||||
requestURL={response.request?.url || ""}
|
||||
requestHeaders={response.request?.headers || {}}
|
||||
requestMethod={response.request?.httpMethod || ""}
|
||||
requestBody={
|
||||
_.isObject(response.request?.body)
|
||||
? JSON.stringify(response.request?.body, null, 2)
|
||||
: response.request?.body || ""
|
||||
}
|
||||
/>
|
||||
),
|
||||
key: "error-logs",
|
||||
title: "Errors",
|
||||
panelComponent: <ErrorLogs />,
|
||||
},
|
||||
{
|
||||
key: "logs",
|
||||
title: "Logs",
|
||||
panelComponent: <DebuggerLogs searchQuery={props.apiName} />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ResponseContainer>
|
||||
<ResponseContainer ref={panelRef}>
|
||||
<Resizer panelRef={panelRef} />
|
||||
<SectionDivider />
|
||||
{isRunning && (
|
||||
<LoadingOverlayScreen theme={props.theme}>
|
||||
|
|
|
|||
74
app/client/src/components/editorComponents/CloseEditor.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import TooltipComponent from "components/ads/Tooltip";
|
||||
import { BUILDER_PAGE_URL } from "constants/routes";
|
||||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import { Position } from "@blueprintjs/core";
|
||||
import Text, { TextType } from "components/ads/Text";
|
||||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
} from "selectors/editorSelectors";
|
||||
import Icon, { IconSize } from "components/ads/Icon";
|
||||
import PerformanceTracker, {
|
||||
PerformanceTransactionName,
|
||||
} from "utils/PerformanceTracker";
|
||||
|
||||
const IconContainer = styled.div`
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
margin-right: 16px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
path {
|
||||
fill: ${(props) => props.theme.colors.apiPane.closeIcon};
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.colors.apiPane.iconHoverBg};
|
||||
}
|
||||
`;
|
||||
|
||||
const CloseEditor = () => {
|
||||
const applicationId = useSelector(getCurrentApplicationId);
|
||||
const pageId = useSelector(getCurrentPageId);
|
||||
|
||||
const history = useHistory();
|
||||
const handleClose = (e: React.MouseEvent) => {
|
||||
PerformanceTracker.startTracking(
|
||||
PerformanceTransactionName.CLOSE_SIDE_PANE,
|
||||
{ path: location.pathname },
|
||||
);
|
||||
e.stopPropagation();
|
||||
history.push(BUILDER_PAGE_URL(applicationId, pageId));
|
||||
};
|
||||
|
||||
return (
|
||||
<TooltipComponent
|
||||
minimal
|
||||
position={Position.BOTTOM_LEFT}
|
||||
content={
|
||||
<Text type={TextType.P3} style={{ color: "#ffffff" }}>
|
||||
Close
|
||||
</Text>
|
||||
}
|
||||
minWidth="auto !important"
|
||||
>
|
||||
<IconContainer onClick={handleClose}>
|
||||
<Icon
|
||||
name="close-modal"
|
||||
size={IconSize.LARGE}
|
||||
className="close-modal-icon"
|
||||
/>
|
||||
</IconContainer>
|
||||
</TooltipComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default CloseEditor;
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import CodeEditor from "./index";
|
||||
import store from "store";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
import React from "react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
import EvaluatedValuePopup from "./EvaluatedValuePopup";
|
||||
import { ThemeProvider } from "styled-components";
|
||||
import { theme, light } from "constants/DefaultTheme";
|
||||
import {
|
||||
EditorSize,
|
||||
EditorTheme,
|
||||
TabBehaviour,
|
||||
EditorModes,
|
||||
} from "./EditorConfig";
|
||||
|
||||
describe("CodeEditor", () => {
|
||||
it("should check EvaluatedValuePopup's hideEvaluatedValue is false when hideEvaluatedValue is passed as false to codeditor", () => {
|
||||
const finalTheme = { ...theme, colors: { ...theme.colors, ...light } };
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={finalTheme}>
|
||||
<CodeEditor
|
||||
input={{
|
||||
value: "",
|
||||
onChange: () => {
|
||||
//
|
||||
},
|
||||
}}
|
||||
hideEvaluatedValue={false}
|
||||
additionalDynamicData={{}}
|
||||
mode={EditorModes.TEXT}
|
||||
theme={EditorTheme.LIGHT}
|
||||
size={EditorSize.COMPACT}
|
||||
tabBehaviour={TabBehaviour.INDENT}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
);
|
||||
const testInstance = testRenderer.root;
|
||||
|
||||
expect(
|
||||
testInstance.findByType(EvaluatedValuePopup).props.hideEvaluatedValue,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should check EvaluatedValuePopup's hideEvaluatedValue is true when hideEvaluatedValue is passed as true to codeditor", () => {
|
||||
const finalTheme = { ...theme, colors: { ...theme.colors, ...light } };
|
||||
|
||||
const testRenderer = TestRenderer.create(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={finalTheme}>
|
||||
<CodeEditor
|
||||
input={{
|
||||
value: "",
|
||||
onChange: () => {
|
||||
//
|
||||
},
|
||||
}}
|
||||
hideEvaluatedValue={true}
|
||||
additionalDynamicData={{}}
|
||||
mode={EditorModes.TEXT}
|
||||
theme={EditorTheme.LIGHT}
|
||||
size={EditorSize.COMPACT}
|
||||
tabBehaviour={TabBehaviour.INDENT}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
);
|
||||
const testInstance = testRenderer.root;
|
||||
|
||||
expect(
|
||||
testInstance.findByType(EvaluatedValuePopup).props.hideEvaluatedValue,
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import store from "store";
|
||||
import React from "react";
|
||||
import { Provider } from "react-redux";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import EvaluatedValuePopup from "./EvaluatedValuePopup";
|
||||
import { ThemeProvider, theme } from "constants/DefaultTheme";
|
||||
import { EditorTheme } from "./EditorConfig";
|
||||
|
||||
describe("EvaluatedValuePopup", () => {
|
||||
it("should render evaluated popup when hideEvaluatedValue is false", () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<EvaluatedValuePopup
|
||||
theme={EditorTheme.LIGHT}
|
||||
isOpen={true}
|
||||
hasError={false}
|
||||
hideEvaluatedValue={false}
|
||||
>
|
||||
<div>children</div>
|
||||
</EvaluatedValuePopup>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
);
|
||||
const input = screen.queryByTestId("evaluated-value-popup-title");
|
||||
|
||||
expect(input).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should not render evaluated popup when hideEvaluatedValue is true", () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<EvaluatedValuePopup
|
||||
theme={EditorTheme.LIGHT}
|
||||
isOpen={true}
|
||||
hasError={false}
|
||||
hideEvaluatedValue={true}
|
||||
>
|
||||
<div>children</div>
|
||||
</EvaluatedValuePopup>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
);
|
||||
const input = screen.queryByTestId("evaluated-value-popup-title");
|
||||
|
||||
expect(input).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -7,6 +7,10 @@ import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig
|
|||
import { theme } from "constants/DefaultTheme";
|
||||
import { Placement } from "popper.js";
|
||||
import ScrollIndicator from "components/ads/ScrollIndicator";
|
||||
import DebugButton from "components/editorComponents/Debugger/DebugCTA";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
import Tooltip from "components/ads/Tooltip";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
|
|
@ -97,6 +101,10 @@ const StyledTitle = styled.p`
|
|||
margin: 8px 0;
|
||||
`;
|
||||
|
||||
const StyledDebugButton = styled(DebugButton)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
theme: EditorTheme;
|
||||
isOpen: boolean;
|
||||
|
|
@ -106,6 +114,8 @@ interface Props {
|
|||
children: JSX.Element;
|
||||
error?: string;
|
||||
useValidationMessage?: boolean;
|
||||
hideEvaluatedValue?: boolean;
|
||||
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
||||
}
|
||||
|
||||
interface PopoverContentProps {
|
||||
|
|
@ -117,12 +127,57 @@ interface PopoverContentProps {
|
|||
theme: EditorTheme;
|
||||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
hideEvaluatedValue?: boolean;
|
||||
preparedStatementViewer: boolean;
|
||||
}
|
||||
|
||||
const PreparedStatementViewerContainer = styled.span`
|
||||
.${Classes.POPOVER_TARGET} {
|
||||
display: inline-block;
|
||||
}
|
||||
`;
|
||||
|
||||
const PreparedStatementParameter = styled.span`
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
color: #333;
|
||||
`;
|
||||
|
||||
type PreparedStatementValue = {
|
||||
value: string;
|
||||
parameters: Record<string, number | string>;
|
||||
};
|
||||
export const PreparedStatementViewer = (props: {
|
||||
evaluatedValue: PreparedStatementValue;
|
||||
}) => {
|
||||
const { value, parameters } = props.evaluatedValue;
|
||||
const stringSegments = value.split(/\$\d/);
|
||||
const $params = [...value.matchAll(/\$\d/g)].map((matches) => matches[0]);
|
||||
const paramsWithTooltips = $params.map((param) => (
|
||||
<Tooltip content={<span>{parameters[param]}</span>} key={param}>
|
||||
<PreparedStatementParameter key={param}>
|
||||
{param}
|
||||
</PreparedStatementParameter>
|
||||
</Tooltip>
|
||||
));
|
||||
|
||||
return (
|
||||
<PreparedStatementViewerContainer>
|
||||
{stringSegments.map((segment, index) => (
|
||||
<span key={segment}>
|
||||
{segment}
|
||||
{paramsWithTooltips[index]}
|
||||
</span>
|
||||
))}
|
||||
</PreparedStatementViewerContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const CurrentValueViewer = (props: {
|
||||
theme: EditorTheme;
|
||||
evaluatedValue: any;
|
||||
hideLabel?: boolean;
|
||||
preparedStatementViewer?: boolean;
|
||||
}) => {
|
||||
const currentValueWrapperRef = React.createRef<HTMLDivElement>();
|
||||
const codeWrapperRef = React.createRef<HTMLPreElement>();
|
||||
|
|
@ -138,19 +193,31 @@ export const CurrentValueViewer = (props: {
|
|||
_.isObject(props.evaluatedValue) ||
|
||||
Array.isArray(props.evaluatedValue)
|
||||
) {
|
||||
const reactJsonProps = {
|
||||
theme: props.theme === EditorTheme.DARK ? "summerfruit" : "rjv-default",
|
||||
name: null,
|
||||
enableClipboard: false,
|
||||
displayObjectSize: false,
|
||||
displayDataTypes: false,
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
},
|
||||
collapsed: 2,
|
||||
collapseStringsAfterLength: 20,
|
||||
};
|
||||
content = <ReactJson src={props.evaluatedValue} {...reactJsonProps} />;
|
||||
if (props.preparedStatementViewer) {
|
||||
content = (
|
||||
<CodeWrapper colorTheme={props.theme} ref={codeWrapperRef}>
|
||||
<PreparedStatementViewer
|
||||
evaluatedValue={props.evaluatedValue as PreparedStatementValue}
|
||||
/>
|
||||
<ScrollIndicator containerRef={codeWrapperRef} />
|
||||
</CodeWrapper>
|
||||
);
|
||||
} else {
|
||||
const reactJsonProps = {
|
||||
theme:
|
||||
props.theme === EditorTheme.DARK ? "summerfruit" : "rjv-default",
|
||||
name: null,
|
||||
enableClipboard: false,
|
||||
displayObjectSize: false,
|
||||
displayDataTypes: false,
|
||||
style: {
|
||||
fontSize: "12px",
|
||||
},
|
||||
collapsed: 2,
|
||||
collapseStringsAfterLength: 20,
|
||||
};
|
||||
content = <ReactJson src={props.evaluatedValue} {...reactJsonProps} />;
|
||||
}
|
||||
} else {
|
||||
content = (
|
||||
<CodeWrapper colorTheme={props.theme} ref={codeWrapperRef}>
|
||||
|
|
@ -164,7 +231,11 @@ export const CurrentValueViewer = (props: {
|
|||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
{!props.hideLabel && <StyledTitle>Evaluated Value</StyledTitle>}
|
||||
{!props.hideLabel && (
|
||||
<StyledTitle data-testid="evaluated-value-popup-title">
|
||||
Evaluated Value
|
||||
</StyledTitle>
|
||||
)}
|
||||
<CurrentValueWrapper colorTheme={props.theme}>
|
||||
<>
|
||||
{content}
|
||||
|
|
@ -177,6 +248,7 @@ export const CurrentValueViewer = (props: {
|
|||
|
||||
const PopoverContent = (props: PopoverContentProps) => {
|
||||
const typeTextRef = React.createRef<HTMLPreElement>();
|
||||
|
||||
return (
|
||||
<ContentWrapper
|
||||
onMouseEnter={props.onMouseEnter}
|
||||
|
|
@ -186,9 +258,15 @@ const PopoverContent = (props: PopoverContentProps) => {
|
|||
>
|
||||
{props.hasError && (
|
||||
<ErrorText>
|
||||
{props.useValidationMessage && props.error
|
||||
? props.error
|
||||
: `This value does not evaluate to type "${props.expected}". Transform it using JS inside '{{ }}'`}
|
||||
<span className="t--evaluatedPopup-error">
|
||||
{props.useValidationMessage && props.error
|
||||
? props.error
|
||||
: `This value does not evaluate to type "${props.expected}". Transform it using JS inside '{{ }}'`}
|
||||
</span>
|
||||
<StyledDebugButton
|
||||
className="evaluated-value"
|
||||
source={"EVALUATED_VALUE"}
|
||||
/>
|
||||
</ErrorText>
|
||||
)}
|
||||
{!props.hasError && props.expected && (
|
||||
|
|
@ -200,10 +278,13 @@ const PopoverContent = (props: PopoverContentProps) => {
|
|||
</TypeText>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<CurrentValueViewer
|
||||
theme={props.theme}
|
||||
evaluatedValue={props.evaluatedValue}
|
||||
/>
|
||||
{!props.hideEvaluatedValue && (
|
||||
<CurrentValueViewer
|
||||
theme={props.theme}
|
||||
evaluatedValue={props.evaluatedValue}
|
||||
preparedStatementViewer={props.preparedStatementViewer}
|
||||
/>
|
||||
)}
|
||||
</ContentWrapper>
|
||||
);
|
||||
};
|
||||
|
|
@ -226,7 +307,7 @@ const EvaluatedValuePopup = (props: Props) => {
|
|||
<Popper
|
||||
targetNode={wrapperRef.current || undefined}
|
||||
isOpen
|
||||
zIndex={15}
|
||||
zIndex={5}
|
||||
placement={placement}
|
||||
modifiers={{
|
||||
offset: {
|
||||
|
|
@ -242,6 +323,13 @@ const EvaluatedValuePopup = (props: Props) => {
|
|||
useValidationMessage={props.useValidationMessage}
|
||||
hasError={props.hasError}
|
||||
theme={props.theme}
|
||||
hideEvaluatedValue={props.hideEvaluatedValue}
|
||||
preparedStatementViewer={
|
||||
props.evaluationSubstitutionType
|
||||
? props.evaluationSubstitutionType ===
|
||||
EvaluationSubstitutionType.PARAMETER
|
||||
: false
|
||||
}
|
||||
onMouseLeave={() => {
|
||||
setContentHovered(false);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
|
|||
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
|
||||
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
||||
import _ from "lodash";
|
||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||
import {
|
||||
DataTree,
|
||||
EvaluationSubstitutionType,
|
||||
} from "entities/DataTree/dataTreeFactory";
|
||||
import { Skin } from "constants/DefaultTheme";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import "components/editorComponents/CodeEditor/modes";
|
||||
|
|
@ -84,6 +87,7 @@ export type EditorStyleProps = {
|
|||
hoverInteraction?: boolean;
|
||||
fill?: boolean;
|
||||
useValidationMessage?: boolean;
|
||||
evaluationSubstitutionType?: EvaluationSubstitutionType;
|
||||
};
|
||||
|
||||
export type EditorProps = EditorStyleProps &
|
||||
|
|
@ -92,6 +96,7 @@ export type EditorProps = EditorStyleProps &
|
|||
} & {
|
||||
additionalDynamicData?: Record<string, Record<string, unknown>>;
|
||||
promptMessage?: React.ReactNode | string;
|
||||
hideEvaluatedValue?: boolean;
|
||||
};
|
||||
|
||||
type Props = ReduxStateProps & EditorProps;
|
||||
|
|
@ -350,6 +355,8 @@ class CodeEditor extends Component<Props, State> {
|
|||
hoverInteraction,
|
||||
fill,
|
||||
useValidationMessage,
|
||||
hideEvaluatedValue,
|
||||
evaluationSubstitutionType,
|
||||
} = this.props;
|
||||
const hasError = !!(meta && meta.error);
|
||||
let evaluated = evaluatedValue;
|
||||
|
|
@ -395,6 +402,8 @@ class CodeEditor extends Component<Props, State> {
|
|||
hasError={hasError}
|
||||
error={meta?.error}
|
||||
useValidationMessage={useValidationMessage}
|
||||
hideEvaluatedValue={hideEvaluatedValue}
|
||||
evaluationSubstitutionType={evaluationSubstitutionType}
|
||||
>
|
||||
<EditorWrapper
|
||||
editorTheme={this.props.theme}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Button from "components/ads/Button";
|
||||
import { showDebugger } from "actions/debuggerActions";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { Classes, Variant } from "components/ads/common";
|
||||
import { getAppMode } from "selectors/applicationSelectors";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
&& {
|
||||
width: fit-content;
|
||||
margin-top: 4px;
|
||||
text-transform: none;
|
||||
${(props) => getTypographyByKey(props, "p2")}
|
||||
.${Classes.ICON} {
|
||||
margin-right: 5px;
|
||||
}
|
||||
&:hover {
|
||||
.${Classes.ICON} {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type DebugCTAProps = {
|
||||
className?: string;
|
||||
// For Analytics
|
||||
source?: string;
|
||||
};
|
||||
|
||||
const DebugCTA = (props: DebugCTAProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const appMode = useSelector(getAppMode);
|
||||
|
||||
if (appMode === "PUBLISHED") return null;
|
||||
|
||||
const onClick = () => {
|
||||
props.source &&
|
||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||
source: props.source,
|
||||
});
|
||||
dispatch(showDebugger(true));
|
||||
};
|
||||
|
||||
return <DebugButton className={props.className} onClick={onClick} />;
|
||||
};
|
||||
|
||||
type DebugButtonProps = {
|
||||
className?: string;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export const DebugButton = (props: DebugButtonProps) => {
|
||||
return (
|
||||
<StyledButton
|
||||
className={props.className}
|
||||
onClick={props.onClick}
|
||||
icon="bug"
|
||||
text="Debug"
|
||||
tag="button"
|
||||
variant={Variant.danger}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebugCTA;
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
import React, { useEffect, useRef, useState, useMemo } from "react";
|
||||
import styled from "styled-components";
|
||||
import { isUndefined } from "lodash";
|
||||
import { Severity } from "entities/AppsmithConsole";
|
||||
import FilterHeader from "./FilterHeader";
|
||||
import { BlankState, useFilteredLogs, usePagination } from "./helpers";
|
||||
import LogItem, { getLogItemProps } from "./LogItem";
|
||||
|
||||
const LIST_HEADER_HEIGHT = "38px";
|
||||
|
||||
const ContainerWrapper = styled.div`
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ListWrapper = styled.div`
|
||||
overflow: auto;
|
||||
height: calc(100% - ${LIST_HEADER_HEIGHT});
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
searchQuery: string;
|
||||
hasShortCut?: boolean;
|
||||
};
|
||||
|
||||
const LOGS_FILTER_OPTIONS = [
|
||||
{
|
||||
label: "All",
|
||||
value: "",
|
||||
},
|
||||
{ label: "Success", value: Severity.INFO },
|
||||
{ label: "Warnings", value: Severity.WARNING },
|
||||
{ label: "Errors", value: Severity.ERROR },
|
||||
];
|
||||
|
||||
const DebbuggerLogs = (props: Props) => {
|
||||
const [filter, setFilter] = useState("");
|
||||
const [searchQuery, setSearchQuery] = useState(props.searchQuery);
|
||||
const filteredLogs = useFilteredLogs(searchQuery, filter);
|
||||
const { paginatedData, next } = usePagination(filteredLogs);
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
const selectedFilter = useMemo(
|
||||
() => LOGS_FILTER_OPTIONS.find((option) => option.value === filter),
|
||||
[filter],
|
||||
);
|
||||
|
||||
const handleScroll = (e: Event) => {
|
||||
if ((e.target as HTMLDivElement).scrollTop === 0) {
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const list = listRef.current;
|
||||
if (!list) return;
|
||||
list.addEventListener("scroll", handleScroll);
|
||||
return () => list.removeEventListener("scroll", handleScroll);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const list = listRef.current;
|
||||
if (list) {
|
||||
setTimeout(() => {
|
||||
list.scrollTop = list.scrollHeight - list.clientHeight;
|
||||
}, 0);
|
||||
}
|
||||
}, [paginatedData.length]);
|
||||
|
||||
return (
|
||||
<ContainerWrapper>
|
||||
<FilterHeader
|
||||
options={LOGS_FILTER_OPTIONS}
|
||||
selected={selectedFilter || LOGS_FILTER_OPTIONS[0]}
|
||||
onChange={setSearchQuery}
|
||||
onSelect={(value) => !isUndefined(value) && setFilter(value)}
|
||||
defaultValue={props.searchQuery}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
|
||||
<ListWrapper className="debugger-list" ref={listRef}>
|
||||
{!paginatedData.length ? (
|
||||
<BlankState hasShortCut={!!props.hasShortCut} />
|
||||
) : (
|
||||
paginatedData.map((e, index: number) => {
|
||||
const logItemProps = getLogItemProps(e);
|
||||
|
||||
return (
|
||||
<LogItem
|
||||
key={`debugger-${index}`}
|
||||
{...logItemProps}
|
||||
expand={index === paginatedData.length - 1}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</ListWrapper>
|
||||
</ContainerWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
// Set default props
|
||||
DebbuggerLogs.defaultProps = {
|
||||
searchQuery: "",
|
||||
};
|
||||
|
||||
export default DebbuggerLogs;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { CLICK_ON, createMessage, OPEN_THE_DEBUGGER } from "constants/messages";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { DebugButton } from "./DebugCTA";
|
||||
|
||||
const StyledButton = styled(DebugButton)`
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 15px 0px;
|
||||
color: ${(props) => props.theme.colors.debugger.messageTextColor};
|
||||
`;
|
||||
|
||||
const DebuggerMessage = (props: any) => {
|
||||
return (
|
||||
<Container>
|
||||
{createMessage(CLICK_ON)}
|
||||
<StyledButton className="message" onClick={props.onClick} />
|
||||
{createMessage(OPEN_THE_DEBUGGER)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebuggerMessage;
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
import React, { RefObject, useRef, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { TabComponent } from "components/ads/Tabs";
|
||||
import Icon, { IconSize } from "components/ads/Icon";
|
||||
import DebuggerLogs from "./DebuggerLogs";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { showDebugger } from "actions/debuggerActions";
|
||||
import Errors from "./Errors";
|
||||
import Resizer, { ResizerCSS } from "./Resizer";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
|
||||
const TABS_HEADER_HEIGHT = 36;
|
||||
|
||||
const Container = styled.div`
|
||||
${ResizerCSS}
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
height: 25%;
|
||||
min-height: ${TABS_HEADER_HEIGHT}px;
|
||||
background-color: ${(props) => props.theme.colors.debugger.background};
|
||||
|
||||
ul.react-tabs__tab-list {
|
||||
padding: 0px ${(props) => props.theme.spaces[12]}px;
|
||||
}
|
||||
.react-tabs__tab-panel {
|
||||
height: calc(100% - ${TABS_HEADER_HEIGHT}px);
|
||||
}
|
||||
|
||||
.close-debugger {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
`;
|
||||
|
||||
type DebuggerTabsProps = {
|
||||
defaultIndex: number;
|
||||
};
|
||||
|
||||
const DEBUGGER_TABS = [
|
||||
{
|
||||
key: "ERROR",
|
||||
title: "Errors",
|
||||
panelComponent: <Errors hasShortCut />,
|
||||
},
|
||||
{
|
||||
key: "LOGS",
|
||||
title: "Logs",
|
||||
panelComponent: <DebuggerLogs hasShortCut />,
|
||||
},
|
||||
];
|
||||
|
||||
const DebuggerTabs = (props: DebuggerTabsProps) => {
|
||||
const [selectedIndex, setSelectedIndex] = useState(props.defaultIndex);
|
||||
const dispatch = useDispatch();
|
||||
const panelRef: RefObject<HTMLDivElement> = useRef(null);
|
||||
const onTabSelect = (index: number) => {
|
||||
AnalyticsUtil.logEvent("DEBUGGER_TAB_SWITCH", {
|
||||
tabName: DEBUGGER_TABS[index].key,
|
||||
});
|
||||
|
||||
setSelectedIndex(index);
|
||||
};
|
||||
const onClose = () => dispatch(showDebugger(false));
|
||||
|
||||
return (
|
||||
<Container ref={panelRef}>
|
||||
<Resizer panelRef={panelRef} />
|
||||
<TabComponent
|
||||
selectedIndex={selectedIndex}
|
||||
onSelect={onTabSelect}
|
||||
tabs={DEBUGGER_TABS}
|
||||
/>
|
||||
<Icon
|
||||
className="close-debugger"
|
||||
name="cross"
|
||||
size={IconSize.SMALL}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default DebuggerTabs;
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import { DATA_SOURCES_EDITOR_ID_URL } from "constants/routes";
|
||||
import { PluginType } from "entities/Action";
|
||||
import { ENTITY_TYPE, SourceEntity } from "entities/AppsmithConsole";
|
||||
import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers";
|
||||
import { useNavigateToWidget } from "pages/Editor/Explorer/Widgets/WidgetEntity";
|
||||
import React, { useCallback } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
import {
|
||||
getCurrentApplicationId,
|
||||
getCurrentPageId,
|
||||
} from "selectors/editorSelectors";
|
||||
import {
|
||||
getAction,
|
||||
getAllWidgetsMap,
|
||||
getDatasource,
|
||||
} from "selectors/entitiesSelector";
|
||||
import { getSelectedWidget } from "selectors/ui";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import history from "utils/history";
|
||||
|
||||
const ActionLink = (props: EntityLinkProps) => {
|
||||
const applicationId = useSelector(getCurrentApplicationId);
|
||||
const action = useSelector((state: AppState) => getAction(state, props.id));
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (action) {
|
||||
const { pageId, pluginType, id } = action;
|
||||
const actionConfig = getActionConfig(pluginType);
|
||||
const url =
|
||||
applicationId && actionConfig?.getURL(applicationId, pageId, id);
|
||||
|
||||
if (url) {
|
||||
history.push(url);
|
||||
const actionType =
|
||||
action.pluginType === PluginType.API ? "API" : "QUERY";
|
||||
|
||||
AnalyticsUtil.logEvent("DEBUGGER_ENTITY_NAVIGATION", {
|
||||
entityType: actionType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Link
|
||||
name={props.name}
|
||||
onClick={onClick}
|
||||
entityType={props.type}
|
||||
uiComponent={props.uiComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const WidgetLink = (props: EntityLinkProps) => {
|
||||
const widgetMap = useSelector(getAllWidgetsMap);
|
||||
const selectedWidgetId = useSelector(getSelectedWidget);
|
||||
const { navigateToWidget } = useNavigateToWidget();
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
const widget = widgetMap[props.id];
|
||||
if (!widget) return;
|
||||
|
||||
navigateToWidget(
|
||||
props.id,
|
||||
widget.type,
|
||||
widget.pageId,
|
||||
props.id === selectedWidgetId,
|
||||
widget.parentModalId,
|
||||
);
|
||||
AnalyticsUtil.logEvent("DEBUGGER_ENTITY_NAVIGATION", {
|
||||
entityType: "WIDGET",
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Link
|
||||
name={props.name}
|
||||
onClick={onClick}
|
||||
entityType={props.type}
|
||||
uiComponent={props.uiComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const DatasourceLink = (props: EntityLinkProps) => {
|
||||
const datasource = useSelector((state: AppState) =>
|
||||
getDatasource(state, props.id),
|
||||
);
|
||||
const pageId = useSelector(getCurrentPageId);
|
||||
const appId = useSelector(getCurrentApplicationId);
|
||||
|
||||
const onClick = () => {
|
||||
if (datasource) {
|
||||
history.push(DATA_SOURCES_EDITOR_ID_URL(appId, pageId, datasource.id));
|
||||
AnalyticsUtil.logEvent("DEBUGGER_ENTITY_NAVIGATION", {
|
||||
entityType: "DATASOURCE",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Link
|
||||
name={props.name}
|
||||
onClick={onClick}
|
||||
entityType={props.type}
|
||||
uiComponent={props.uiComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Link = (props: {
|
||||
name: string;
|
||||
onClick: any;
|
||||
entityType: ENTITY_TYPE;
|
||||
uiComponent: DebuggerLinkUI;
|
||||
}) => {
|
||||
const onClick = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.stopPropagation();
|
||||
props.onClick();
|
||||
};
|
||||
|
||||
switch (props.uiComponent) {
|
||||
case DebuggerLinkUI.ENTITY_TYPE:
|
||||
return (
|
||||
<span className="debugger-entity">
|
||||
[<span onClick={onClick}>{props.name}</span>]
|
||||
</span>
|
||||
);
|
||||
case DebuggerLinkUI.ENTITY_NAME:
|
||||
return (
|
||||
<span className="debugger-entity-link" onClick={onClick}>
|
||||
{props.name}.{props.entityType.toLowerCase()}
|
||||
</span>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const entityTypeLinkMap = {
|
||||
[ENTITY_TYPE.WIDGET]: WidgetLink,
|
||||
[ENTITY_TYPE.ACTION]: ActionLink,
|
||||
[ENTITY_TYPE.DATASOURCE]: DatasourceLink,
|
||||
};
|
||||
|
||||
const EntityLink = (props: EntityLinkProps) => {
|
||||
const Component = entityTypeLinkMap[props.type];
|
||||
return <Component {...props} />;
|
||||
};
|
||||
|
||||
type EntityLinkProps = {
|
||||
uiComponent: DebuggerLinkUI;
|
||||
} & SourceEntity;
|
||||
|
||||
export enum DebuggerLinkUI {
|
||||
ENTITY_TYPE,
|
||||
ENTITY_NAME,
|
||||
}
|
||||
|
||||
export default EntityLink;
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import React from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import styled from "styled-components";
|
||||
import { getDebuggerErrors } from "selectors/debuggerSelectors";
|
||||
import LogItem, { getLogItemProps } from "./LogItem";
|
||||
import { BlankState } from "./helpers";
|
||||
|
||||
const ContainerWrapper = styled.div`
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const ListWrapper = styled.div`
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const Errors = (props: { hasShortCut?: boolean }) => {
|
||||
const errors = useSelector(getDebuggerErrors);
|
||||
const expandId = useSelector((state: any) => state.ui.debugger.expandId);
|
||||
|
||||
return (
|
||||
<ContainerWrapper>
|
||||
<ListWrapper className="debugger-list">
|
||||
{!Object.values(errors).length ? (
|
||||
<BlankState hasShortCut={props.hasShortCut} />
|
||||
) : (
|
||||
Object.values(errors).map((e, index) => {
|
||||
const logItemProps = getLogItemProps(e);
|
||||
const id = Object.keys(errors)[index];
|
||||
|
||||
return (
|
||||
<LogItem
|
||||
key={`debugger-${index}`}
|
||||
{...logItemProps}
|
||||
expand={id === expandId}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</ListWrapper>
|
||||
</ContainerWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default Errors;
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import React, { MutableRefObject, useRef } from "react";
|
||||
import Dropdown, { DropdownOption } from "components/ads/Dropdown";
|
||||
import TextInput from "components/ads/TextInput";
|
||||
import styled from "styled-components";
|
||||
import Icon, { IconSize } from "components/ads/Icon";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { clearLogs } from "actions/debuggerActions";
|
||||
import { Classes } from "components/ads/common";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
flex-direction: row;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
margin-left: 30px;
|
||||
padding: 5px 0;
|
||||
& > div {
|
||||
width: 160px;
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
.debugger-search {
|
||||
height: 28px;
|
||||
width: 160px;
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.debugger-filter {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
position: relative;
|
||||
.${Classes.ICON} {
|
||||
position: absolute;
|
||||
right: 9px;
|
||||
top: 9px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type FilterHeaderProps = {
|
||||
options: DropdownOption[];
|
||||
selected: DropdownOption;
|
||||
onChange: (value: string) => void;
|
||||
onSelect: (value?: string) => void;
|
||||
defaultValue: string;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
const FilterHeader = (props: FilterHeaderProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const searchRef: MutableRefObject<HTMLInputElement | null> = useRef(null);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Icon
|
||||
name="cancel"
|
||||
size={IconSize.XL}
|
||||
onClick={() => dispatch(clearLogs())}
|
||||
/>
|
||||
<div className="input-container">
|
||||
<TextInput
|
||||
ref={searchRef}
|
||||
className="debugger-search"
|
||||
placeholder="Filter"
|
||||
onChange={props.onChange}
|
||||
defaultValue={props.defaultValue}
|
||||
/>
|
||||
{props.searchQuery && (
|
||||
<Icon
|
||||
name="close"
|
||||
onClick={() => {
|
||||
if (searchRef.current) {
|
||||
props.onChange("");
|
||||
searchRef.current.value = "";
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Dropdown
|
||||
className="debugger-filter"
|
||||
width={"100px"}
|
||||
height={"28px"}
|
||||
optionWidth={"100px"}
|
||||
options={props.options}
|
||||
showLabelOnly
|
||||
selected={props.selected}
|
||||
onSelect={props.onSelect}
|
||||
/>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilterHeader;
|
||||
281
app/client/src/components/editorComponents/Debugger/LogItem.tsx
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
import { Collapse, Position } from "@blueprintjs/core";
|
||||
import { Classes } from "components/ads/common";
|
||||
import Icon, { IconName, IconSize } from "components/ads/Icon";
|
||||
import { Message, Severity, SourceEntity } from "entities/AppsmithConsole";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import ReactJson from "react-json-view";
|
||||
import styled from "styled-components";
|
||||
import { isString } from "lodash";
|
||||
import EntityLink, { DebuggerLinkUI } from "./EntityLink";
|
||||
import { SeverityIcon, SeverityIconColor } from "./helpers";
|
||||
import { useDispatch } from "react-redux";
|
||||
import {
|
||||
setGlobalSearchQuery,
|
||||
toggleShowGlobalSearchModal,
|
||||
} from "actions/globalSearchActions";
|
||||
import Text, { TextType } from "components/ads/Text";
|
||||
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import TooltipComponent from "components/ads/Tooltip";
|
||||
import { createMessage, TROUBLESHOOT_ISSUE } from "constants/messages";
|
||||
|
||||
const Log = styled.div<{ collapsed: boolean }>`
|
||||
padding: 9px 30px;
|
||||
display: flex;
|
||||
|
||||
&.${Severity.INFO} {
|
||||
border-bottom: 1px solid
|
||||
${(props) => props.theme.colors.debugger.info.borderBottom};
|
||||
}
|
||||
|
||||
&.${Severity.ERROR} {
|
||||
background-color: ${(props) =>
|
||||
props.theme.colors.debugger.error.backgroundColor};
|
||||
border-bottom: 1px solid
|
||||
${(props) => props.theme.colors.debugger.error.borderBottom};
|
||||
}
|
||||
|
||||
&.${Severity.WARNING} {
|
||||
background-color: ${(props) =>
|
||||
props.theme.colors.debugger.warning.backgroundColor};
|
||||
border-bottom: 1px solid
|
||||
${(props) => props.theme.colors.debugger.warning.borderBottom};
|
||||
}
|
||||
|
||||
.bp3-popover-target {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.${Classes.ICON} {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.debugger-time {
|
||||
${(props) => getTypographyByKey(props, "h6")}
|
||||
line-height: 17px;
|
||||
color: ${(props) => props.theme.colors.debugger.time};
|
||||
margin-left: 10px;
|
||||
}
|
||||
.debugger-description {
|
||||
display: inline-block;
|
||||
margin-left: 7px;
|
||||
|
||||
.debugger-toggle {
|
||||
${(props) => props.collapsed && `transform: rotate(-90deg);`}
|
||||
}
|
||||
|
||||
.debugger-label {
|
||||
color: ${(props) => props.theme.colors.debugger.label};
|
||||
margin-left: 5px;
|
||||
${(props) => getTypographyByKey(props, "p2")}
|
||||
}
|
||||
.debugger-entity {
|
||||
color: ${(props) => props.theme.colors.debugger.entity};
|
||||
${(props) => getTypographyByKey(props, "h6")}
|
||||
margin-left: 6px;
|
||||
|
||||
& > span {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: ${(props) =>
|
||||
props.theme.colors.debugger.entity};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.debugger-timetaken {
|
||||
color: ${(props) => props.theme.colors.debugger.entity};
|
||||
margin-left: 5px;
|
||||
${(props) => getTypographyByKey(props, "p2")}
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.debugger-entity-link {
|
||||
margin-left: auto;
|
||||
${(props) => getTypographyByKey(props, "p2")}
|
||||
color: ${(props) => props.theme.colors.debugger.entityLink};
|
||||
text-decoration-line: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
const JsonWrapper = styled.div`
|
||||
padding-top: 4px;
|
||||
svg {
|
||||
color: ${(props) => props.theme.colors.debugger.jsonIcon} !important;
|
||||
height: 12px !important;
|
||||
width: 12px !important;
|
||||
vertical-align: baseline !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCollapse = styled(Collapse)`
|
||||
margin-top: 4px;
|
||||
|
||||
.debugger-message {
|
||||
${(props) => getTypographyByKey(props, "p2")}
|
||||
color: ${(props) => props.theme.colors.debugger.message};
|
||||
text-decoration-line: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.${Classes.ICON} {
|
||||
margin-left: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSearchIcon = styled(Icon)`
|
||||
&& {
|
||||
margin-left: 10px;
|
||||
vertical-align: middle;
|
||||
|
||||
&:hover {
|
||||
path {
|
||||
fill: ${(props) => props.fillColor};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const getLogItemProps = (e: Message) => {
|
||||
return {
|
||||
icon: SeverityIcon[e.severity] as IconName,
|
||||
iconColor: SeverityIconColor[e.severity],
|
||||
timestamp: e.timestamp,
|
||||
source: e.source,
|
||||
label: e.text,
|
||||
timeTaken: e.timeTaken ? `${e.timeTaken}ms` : "",
|
||||
severity: e.severity,
|
||||
text: e.text,
|
||||
message: e.message && isString(e.message) ? e.message : "",
|
||||
state: e.state,
|
||||
id: e.source ? e.source.id : undefined,
|
||||
};
|
||||
};
|
||||
|
||||
type LogItemProps = {
|
||||
icon: IconName;
|
||||
iconColor: string;
|
||||
timestamp: string;
|
||||
label: string;
|
||||
timeTaken: string;
|
||||
severity: Severity;
|
||||
text: string;
|
||||
message: string;
|
||||
state?: Record<string, any>;
|
||||
id?: string;
|
||||
source?: SourceEntity;
|
||||
expand?: boolean;
|
||||
};
|
||||
|
||||
const LogItem = (props: LogItemProps) => {
|
||||
const [isOpen, setIsOpen] = useState(!!props.expand);
|
||||
const reactJsonProps = {
|
||||
name: null,
|
||||
enableClipboard: false,
|
||||
displayObjectSize: false,
|
||||
displayDataTypes: false,
|
||||
style: {
|
||||
fontSize: "13px",
|
||||
},
|
||||
collapsed: 1,
|
||||
};
|
||||
const showToggleIcon = props.state || props.message;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const openHelpModal = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
const text = props.message || props.text;
|
||||
|
||||
AnalyticsUtil.logEvent("OPEN_OMNIBAR", {
|
||||
source: "DEBUGGER",
|
||||
searchTerm: text,
|
||||
});
|
||||
dispatch(setGlobalSearchQuery(text || ""));
|
||||
dispatch(toggleShowGlobalSearchModal());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Log
|
||||
className={props.severity}
|
||||
collapsed={!isOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<Icon name={props.icon} size={IconSize.XL} keepColors />
|
||||
<span className="debugger-time">{props.timestamp}</span>
|
||||
<div className="debugger-description">
|
||||
{showToggleIcon && (
|
||||
<Icon
|
||||
className={`${Classes.ICON} debugger-toggle`}
|
||||
name={"downArrow"}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
size={IconSize.XXS}
|
||||
/>
|
||||
)}
|
||||
{props.source && (
|
||||
<EntityLink
|
||||
type={props.source.type}
|
||||
name={props.source.name}
|
||||
id={props.source.id}
|
||||
uiComponent={DebuggerLinkUI.ENTITY_TYPE}
|
||||
/>
|
||||
)}
|
||||
<span className="debugger-label">{props.text}</span>
|
||||
{props.timeTaken && (
|
||||
<span className="debugger-timetaken">{props.timeTaken}</span>
|
||||
)}
|
||||
{props.severity !== Severity.INFO && (
|
||||
<TooltipComponent
|
||||
minimal
|
||||
position={Position.BOTTOM_LEFT}
|
||||
content={
|
||||
<Text type={TextType.P3} style={{ color: "#ffffff" }}>
|
||||
{createMessage(TROUBLESHOOT_ISSUE)}
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
<StyledSearchIcon
|
||||
className={Classes.ICON}
|
||||
name={"wand"}
|
||||
size={IconSize.MEDIUM}
|
||||
fillColor={props.iconColor}
|
||||
onClick={openHelpModal}
|
||||
/>
|
||||
</TooltipComponent>
|
||||
)}
|
||||
|
||||
{showToggleIcon && (
|
||||
<StyledCollapse isOpen={isOpen} keepChildrenMounted>
|
||||
{props.message && (
|
||||
<div>
|
||||
<span className="debugger-message" onClick={openHelpModal}>
|
||||
{props.message}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{props.state && (
|
||||
<JsonWrapper
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="t--debugger-log-state"
|
||||
>
|
||||
<ReactJson src={props.state} {...reactJsonProps} />
|
||||
</JsonWrapper>
|
||||
)}
|
||||
</StyledCollapse>
|
||||
)}
|
||||
</div>
|
||||
{props.source && (
|
||||
<EntityLink
|
||||
type={props.source.type}
|
||||
name={props.source.name}
|
||||
id={props.source.id}
|
||||
uiComponent={DebuggerLinkUI.ENTITY_NAME}
|
||||
/>
|
||||
)}
|
||||
</Log>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogItem;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { Layers } from "constants/Layers";
|
||||
import React, { useState, useEffect, RefObject } from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
export const ResizerCSS = css`
|
||||
width: calc(100vw - ${(props) => props.theme.sidebarWidth});
|
||||
z-index: ${Layers.debugger};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Top = styled.div`
|
||||
position: absolute;
|
||||
cursor: ns-resize;
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
`;
|
||||
|
||||
type ResizerProps = {
|
||||
panelRef: RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
const Resizer = (props: ResizerProps) => {
|
||||
const [mouseDown, setMouseDown] = useState(false);
|
||||
|
||||
const handleResize = (movementY: number) => {
|
||||
const panel = props.panelRef.current;
|
||||
if (!panel) return;
|
||||
|
||||
const { height } = panel.getBoundingClientRect();
|
||||
const updatedHeight = height - movementY;
|
||||
const headerHeightNumber = 35;
|
||||
const minHeight = parseInt(
|
||||
window.getComputedStyle(panel).minHeight.replace("px", ""),
|
||||
);
|
||||
|
||||
if (
|
||||
updatedHeight < window.innerHeight - headerHeightNumber &&
|
||||
updatedHeight > minHeight
|
||||
) {
|
||||
panel.style.height = `${height - movementY}px`;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
handleResize(e.movementY);
|
||||
};
|
||||
|
||||
if (mouseDown) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}, [mouseDown]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleMouseUp = () => setMouseDown(false);
|
||||
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleMouseDown = () => {
|
||||
setMouseDown(true);
|
||||
};
|
||||
|
||||
return <Top onMouseDown={handleMouseDown} />;
|
||||
};
|
||||
|
||||
export default Resizer;
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
import { Message, Severity } from "entities/AppsmithConsole";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
import styled from "styled-components";
|
||||
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||
import {
|
||||
createMessage,
|
||||
NO_LOGS,
|
||||
OPEN_THE_DEBUGGER,
|
||||
PRESS,
|
||||
} from "constants/messages";
|
||||
|
||||
const BlankStateWrapper = styled.div`
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: ${(props) => props.theme.colors.debugger.blankState.color};
|
||||
${(props) => getTypographyByKey(props, "p1")}
|
||||
|
||||
.debugger-shortcut {
|
||||
color: ${(props) => props.theme.colors.debugger.blankState.shortcut};
|
||||
${(props) => getTypographyByKey(props, "h5")}
|
||||
}
|
||||
`;
|
||||
|
||||
export const BlankState = (props: { hasShortCut?: boolean }) => {
|
||||
return (
|
||||
<BlankStateWrapper>
|
||||
{props.hasShortCut ? (
|
||||
<span>
|
||||
{createMessage(PRESS)}
|
||||
<span className="debugger-shortcut">Cmd + D</span>
|
||||
{createMessage(OPEN_THE_DEBUGGER)}
|
||||
</span>
|
||||
) : (
|
||||
<span>{createMessage(NO_LOGS)}</span>
|
||||
)}
|
||||
</BlankStateWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const SeverityIcon: Record<Severity, string> = {
|
||||
[Severity.INFO]: "success",
|
||||
[Severity.ERROR]: "error",
|
||||
[Severity.WARNING]: "warning",
|
||||
};
|
||||
|
||||
export const SeverityIconColor: Record<Severity, string> = {
|
||||
[Severity.INFO]: "#03B365",
|
||||
[Severity.ERROR]: "#F22B2B",
|
||||
[Severity.WARNING]: "rgb(224, 179, 14)",
|
||||
};
|
||||
|
||||
export const useFilteredLogs = (query: string, filter?: any) => {
|
||||
let logs = useSelector((state: AppState) => state.ui.debugger.logs);
|
||||
|
||||
if (filter) {
|
||||
logs = logs.filter((log: Message) => log.severity === filter);
|
||||
}
|
||||
|
||||
if (query) {
|
||||
logs = logs.filter((log: Message) => {
|
||||
if (log.source?.name)
|
||||
return (
|
||||
log.source?.name.toUpperCase().indexOf(query.toUpperCase()) !== -1
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return logs;
|
||||
};
|
||||
|
||||
export const usePagination = (data: Message[], itemsPerPage = 50) => {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [paginatedData, setPaginatedData] = useState<Message[]>([]);
|
||||
const maxPage = Math.ceil(data.length / itemsPerPage);
|
||||
|
||||
useEffect(() => {
|
||||
const data = currentData();
|
||||
setPaginatedData(data);
|
||||
}, [currentPage, data.length]);
|
||||
|
||||
const currentData = useCallback(() => {
|
||||
const end = currentPage * itemsPerPage;
|
||||
return data.slice(0, end);
|
||||
}, [data]);
|
||||
|
||||
const next = useCallback(() => {
|
||||
setCurrentPage((currentPage) => {
|
||||
const newCurrentPage = Math.min(currentPage + 1, maxPage);
|
||||
return newCurrentPage <= 0 ? 1 : newCurrentPage;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { next, paginatedData };
|
||||
};
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
import { Classes } from "components/ads/common";
|
||||
import Icon, { IconSize } from "components/ads/Icon";
|
||||
import React from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useSelector } from "store";
|
||||
import styled from "styled-components";
|
||||
import DebuggerTabs from "./DebuggerTabs";
|
||||
import { AppState } from "reducers";
|
||||
import { showDebugger as showDebuggerAction } from "actions/debuggerActions";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||
|
||||
const Container = styled.div<{ errorCount: number }>`
|
||||
background-color: ${(props) =>
|
||||
props.theme.colors.debugger.floatingButton.background};
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
cursor: pointer;
|
||||
padding: 19px;
|
||||
color: ${(props) => props.theme.colors.debugger.floatingButton.color};
|
||||
border-radius: 50px;
|
||||
box-shadow: ${(props) => props.theme.colors.debugger.floatingButton.shadow};
|
||||
|
||||
.${Classes.ICON} {
|
||||
&:hover {
|
||||
path {
|
||||
fill: ${(props) => props.theme.colors.icon.normal};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.debugger-count {
|
||||
color: ${Colors.WHITE};
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
${(props) => getTypographyByKey(props, "h6")}
|
||||
height: 20px;
|
||||
padding: 6px;
|
||||
background-color: ${(props) =>
|
||||
!!props.errorCount
|
||||
? props.theme.colors.debugger.floatingButton.errorCount
|
||||
: props.theme.colors.debugger.floatingButton.noErrorCount};
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
const Debugger = () => {
|
||||
const dispatch = useDispatch();
|
||||
const errorCount = useSelector(
|
||||
(state: AppState) => Object.keys(state.ui.debugger.errors).length,
|
||||
);
|
||||
const showDebugger = useSelector(
|
||||
(state: AppState) => state.ui.debugger.isOpen,
|
||||
);
|
||||
|
||||
const onClick = () => {
|
||||
AnalyticsUtil.logEvent("OPEN_DEBUGGER", {
|
||||
source: "CANVAS",
|
||||
});
|
||||
dispatch(showDebuggerAction(true));
|
||||
};
|
||||
|
||||
if (!showDebugger)
|
||||
return (
|
||||
<Container
|
||||
onClick={onClick}
|
||||
errorCount={errorCount}
|
||||
className="t--debugger"
|
||||
>
|
||||
<Icon name="bug" size={IconSize.XXXL} />
|
||||
<div className="debugger-count">{errorCount}</div>
|
||||
</Container>
|
||||
);
|
||||
return <DebuggerTabs defaultIndex={errorCount ? 0 : 1} />;
|
||||
};
|
||||
|
||||
export default Debugger;
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import React from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import TestRenderer from "react-test-renderer";
|
||||
import TouchBackend from "react-dnd-touch-backend";
|
||||
|
||||
import DragLayerComponent from "./DragLayerComponent";
|
||||
import { RenderModes, WidgetTypes } from "constants/WidgetConstants";
|
||||
import { ThemeProvider, theme } from "constants/DefaultTheme";
|
||||
|
||||
describe("DragLayerComponent", () => {
|
||||
it("it checks noPad prop", () => {
|
||||
const dummyWidget = {
|
||||
type: WidgetTypes.CANVAS_WIDGET,
|
||||
widgetId: "0",
|
||||
widgetName: "canvas",
|
||||
parentColumnSpace: 1,
|
||||
parentRowSpace: 1,
|
||||
parentRowHeight: 0,
|
||||
canDropTargetExtend: false,
|
||||
parentColumnWidth: 0,
|
||||
leftColumn: 0,
|
||||
visible: true,
|
||||
rightColumn: 0,
|
||||
topRow: 0,
|
||||
bottomRow: 0,
|
||||
version: 17,
|
||||
isLoading: false,
|
||||
renderMode: RenderModes.CANVAS,
|
||||
children: [],
|
||||
noPad: true,
|
||||
onBoundsUpdate: () => {
|
||||
//
|
||||
},
|
||||
isOver: true,
|
||||
parentWidgetId: "parent",
|
||||
force: true,
|
||||
};
|
||||
const testRenderer = TestRenderer.create(
|
||||
<ThemeProvider theme={theme}>
|
||||
<DndProvider
|
||||
backend={TouchBackend}
|
||||
options={{
|
||||
enableMouseEvents: true,
|
||||
}}
|
||||
>
|
||||
<DragLayerComponent {...dummyWidget} />
|
||||
</DndProvider>
|
||||
</ThemeProvider>,
|
||||
);
|
||||
const testInstance = testRenderer.root;
|
||||
|
||||
expect(testInstance.findByType(DragLayerComponent).props.noPad).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -12,16 +12,17 @@ import { getNearestParentCanvas } from "utils/generators";
|
|||
const WrappedDragLayer = styled.div<{
|
||||
columnWidth: number;
|
||||
rowHeight: number;
|
||||
noPad: boolean;
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
}>`
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
left: 0;
|
||||
top: 0;
|
||||
left: ${CONTAINER_GRID_PADDING}px;
|
||||
top: ${CONTAINER_GRID_PADDING}px;
|
||||
height: calc(100% - ${CONTAINER_GRID_PADDING}px);
|
||||
width: calc(100% - ${CONTAINER_GRID_PADDING}px);
|
||||
left: ${(props) => (props.noPad ? "0" : `${CONTAINER_GRID_PADDING}px;`)};
|
||||
top: ${(props) => (props.noPad ? "0" : `${CONTAINER_GRID_PADDING}px;`)};
|
||||
height: ${(props) =>
|
||||
props.noPad ? `100%` : `calc(100% - ${CONTAINER_GRID_PADDING}px)`};
|
||||
width: ${(props) =>
|
||||
props.noPad ? `100%` : `calc(100% - ${CONTAINER_GRID_PADDING}px)`};
|
||||
|
||||
background-image: radial-gradient(
|
||||
circle,
|
||||
|
|
@ -47,6 +48,7 @@ type DragLayerProps = {
|
|||
isResizing?: boolean;
|
||||
parentWidgetId: string;
|
||||
force: boolean;
|
||||
noPad: boolean;
|
||||
};
|
||||
|
||||
const DragLayerComponent = (props: DragLayerProps) => {
|
||||
|
|
@ -133,9 +135,9 @@ const DragLayerComponent = (props: DragLayerProps) => {
|
|||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
When the parent offsets are not updated, we don't need to show the dropzone, as the dropzone
|
||||
will be rendered at an incorrect coordinates.
|
||||
will be rendered at an incorrect coordinates.
|
||||
We can be sure that the parent offset has been calculated
|
||||
when the coordiantes are not [0,0].
|
||||
*/
|
||||
|
|
@ -146,6 +148,7 @@ const DragLayerComponent = (props: DragLayerProps) => {
|
|||
columnWidth={props.parentColumnWidth}
|
||||
rowHeight={props.parentRowHeight}
|
||||
ref={dropTargetMask}
|
||||
noPad={props.noPad}
|
||||
>
|
||||
{props.visible &&
|
||||
props.isOver &&
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { canDrag } from "./DraggableComponent";
|
||||
|
||||
describe("DraggableComponent", () => {
|
||||
it("it tests draggable canDrag helper function", () => {
|
||||
expect(canDrag(false, false, { dragDisabled: false })).toBe(true);
|
||||
expect(canDrag(true, false, { dragDisabled: false })).toBe(false);
|
||||
expect(canDrag(false, true, { dragDisabled: false })).toBe(false);
|
||||
expect(canDrag(false, false, { dragDisabled: true })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -38,6 +38,22 @@ type DraggableComponentProps = WidgetProps;
|
|||
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
/**
|
||||
* can drag helper function for react-dnd hook
|
||||
*
|
||||
* @param isResizing
|
||||
* @param isDraggingDisabled
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const canDrag = (
|
||||
isResizing: boolean,
|
||||
isDraggingDisabled: boolean,
|
||||
props: any,
|
||||
) => {
|
||||
return !isResizing && !isDraggingDisabled && !props.dragDisabled;
|
||||
};
|
||||
|
||||
const DraggableComponent = (props: DraggableComponentProps) => {
|
||||
// Dispatch hook handy to toggle property pane
|
||||
const showPropertyPane = useShowPropertyPane();
|
||||
|
|
@ -119,7 +135,7 @@ const DraggableComponent = (props: DraggableComponentProps) => {
|
|||
},
|
||||
canDrag: () => {
|
||||
// Dont' allow drag if we're resizing or the drag of `DraggableComponent` is disabled
|
||||
return !isResizing && !isDraggingDisabled;
|
||||
return canDrag(isResizing, isDraggingDisabled, props);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type DropTargetComponentProps = WidgetProps & {
|
|||
snapColumnSpace: number;
|
||||
snapRowSpace: number;
|
||||
minHeight: number;
|
||||
noPad?: boolean;
|
||||
};
|
||||
|
||||
const StyledDropTarget = styled.div`
|
||||
|
|
@ -65,7 +66,7 @@ export const DropTargetContext: Context<{
|
|||
persistDropTargetRows?: (widgetId: string, row: number) => void;
|
||||
}> = createContext({});
|
||||
|
||||
export const DropTargetComponent = memo((props: DropTargetComponentProps) => {
|
||||
export const DropTargetComponent = (props: DropTargetComponentProps) => {
|
||||
const canDropTargetExtend = props.canExtend;
|
||||
|
||||
const snapRows = getCanvasSnapRows(props.bottomRow, props.canExtend);
|
||||
|
|
@ -244,7 +245,8 @@ export const DropTargetComponent = memo((props: DropTargetComponentProps) => {
|
|||
focusWidget && focusWidget(props.parentId);
|
||||
}
|
||||
}
|
||||
e.stopPropagation();
|
||||
// commenting this out to allow propagation of click events
|
||||
// e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
const height = canDropTargetExtend
|
||||
|
|
@ -258,13 +260,15 @@ export const DropTargetComponent = memo((props: DropTargetComponentProps) => {
|
|||
? "1px solid #DDDDDD"
|
||||
: "1px solid transparent";
|
||||
|
||||
const dropRef = !props.dropDisabled ? drop : undefined;
|
||||
|
||||
return (
|
||||
<DropTargetContext.Provider
|
||||
value={{ updateDropTargetRows, persistDropTargetRows }}
|
||||
>
|
||||
<StyledDropTarget
|
||||
onClick={handleFocus}
|
||||
ref={drop}
|
||||
ref={dropRef}
|
||||
style={{
|
||||
height,
|
||||
border,
|
||||
|
|
@ -287,11 +291,14 @@ export const DropTargetComponent = memo((props: DropTargetComponentProps) => {
|
|||
parentRows={rows}
|
||||
parentCols={props.snapColumns}
|
||||
isResizing={isChildResizing}
|
||||
noPad={props.noPad || false}
|
||||
force={isDragging && !isOver && !props.parentId}
|
||||
/>
|
||||
</StyledDropTarget>
|
||||
</DropTargetContext.Provider>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default DropTargetComponent;
|
||||
const MemoizedDropTargetComponent = memo(DropTargetComponent);
|
||||
|
||||
export default MemoizedDropTargetComponent;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { isMac } from "utils/helpers";
|
|||
|
||||
const StyledHelpBar = styled.div`
|
||||
padding: 0 ${(props) => props.theme.spaces[4]}px;
|
||||
margin: ${(props) => props.theme.spaces[2]}px;
|
||||
.placeholder-text {
|
||||
${(props) => getTypographyByKey(props, "p2")}
|
||||
}
|
||||
|
|
@ -21,6 +22,10 @@ const StyledHelpBar = styled.div`
|
|||
height: 28px;
|
||||
flex: 1;
|
||||
max-width: 350px;
|
||||
border: 1.5px solid transparent;
|
||||
&:hover {
|
||||
border: 1.5px solid ${(props) => props.theme.colors.tertiary.light};
|
||||
}
|
||||
`;
|
||||
|
||||
const modText = () => (isMac() ? <span>⌘</span> : "ctrl");
|
||||
|
|
@ -35,6 +40,7 @@ const HelpBar = ({ toggleShowModal }: Props) => {
|
|||
<StyledHelpBar
|
||||
onClick={toggleShowModal}
|
||||
className="t--global-search-modal-trigger"
|
||||
data-cy="global-search-modal-trigger"
|
||||
>
|
||||
<Text type={TextType.P2}>{HELPBAR_PLACEHOLDER()}</Text>
|
||||
<Text type={TextType.P3} italic>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import styled from "styled-components";
|
|||
import NoSearchDataImage from "assets/images/no_search_data.png";
|
||||
import { NO_SEARCH_DATA_TEXT } from "constants/messages";
|
||||
import { getTypographyByKey } from "constants/DefaultTheme";
|
||||
import { ReactComponent as DiscordIcon } from "assets/icons/help/discord.svg";
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
|
|
@ -18,12 +19,42 @@ const Container = styled.div`
|
|||
.no-data-title {
|
||||
margin-top: ${(props) => props.theme.spaces[3]}px;
|
||||
}
|
||||
|
||||
.discord {
|
||||
margin-top: ${(props) => props.theme.spaces[3]}px;
|
||||
}
|
||||
|
||||
.discord-link {
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDiscordIcon = styled(DiscordIcon)`
|
||||
path {
|
||||
fill: #5c6bc0;
|
||||
}
|
||||
vertical-align: -7px;
|
||||
`;
|
||||
|
||||
const ResultsNotFound = () => (
|
||||
<Container>
|
||||
<img alt="No data" src={NoSearchDataImage} />
|
||||
<div className="no-data-title">{NO_SEARCH_DATA_TEXT}</div>
|
||||
<div className="no-data-title">{NO_SEARCH_DATA_TEXT()}</div>
|
||||
<span className="discord">
|
||||
🤖 Join our{" "}
|
||||
<span
|
||||
className="discord-link"
|
||||
onClick={() => {
|
||||
window.open("https://discord.gg/rBTTVJp", "_blank");
|
||||
}}
|
||||
>
|
||||
<StyledDiscordIcon width={24} height={22} color="red" />
|
||||
Discord Server
|
||||
</span>{" "}
|
||||
for more help.
|
||||
</span>
|
||||
</Container>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import RecentIcon from "assets/icons/ads/recent.svg";
|
|||
|
||||
const StyledContainer = styled.div`
|
||||
width: 750px;
|
||||
height: 45vh;
|
||||
height: 60vh;
|
||||
background: ${(props) => props.theme.colors.globalSearch.containerBackground};
|
||||
box-shadow: ${(props) => props.theme.colors.globalSearch.containerShadow};
|
||||
display: flex;
|
||||
|
|
@ -97,7 +97,12 @@ const GlobalSearch = () => {
|
|||
const defaultDocs = useDefaultDocumentationResults(modalOpen);
|
||||
const params = useParams<ExplorerURLParams>();
|
||||
const dispatch = useDispatch();
|
||||
const toggleShow = () => dispatch(toggleShowGlobalSearchModal());
|
||||
const toggleShow = () => {
|
||||
if (modalOpen) {
|
||||
setQuery("");
|
||||
}
|
||||
dispatch(toggleShowGlobalSearchModal());
|
||||
};
|
||||
const [query, setQueryInState] = useState("");
|
||||
const setQuery = useCallback((query: string) => {
|
||||
setQueryInState(query);
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ const recentEntitiesSelector = (state: AppState) =>
|
|||
|
||||
const useResentEntities = () => {
|
||||
const widgetsMap = useSelector(getAllWidgetsMap);
|
||||
let recentEntities = useSelector(recentEntitiesSelector);
|
||||
recentEntities = recentEntities.slice(1);
|
||||
const recentEntities = useSelector(recentEntitiesSelector);
|
||||
const actions = useSelector(getActions);
|
||||
const reducerDatasources = useSelector((state: AppState) => {
|
||||
return state.entities.datasources.list;
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ export const ResizableComponent = memo((props: ResizableComponentProps) => {
|
|||
onStart={handleResizeStart}
|
||||
onStop={updateSize}
|
||||
snapGrid={{ x: props.parentColumnSpace, y: props.parentRowSpace }}
|
||||
enable={!isDragging && isWidgetFocused}
|
||||
enable={!isDragging && isWidgetFocused && !props.resizeDisabled}
|
||||
isColliding={isColliding}
|
||||
>
|
||||
<VisibilityContainer
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export const WidgetNameComponent = (props: WidgetNameComponentProps) => {
|
|||
currentActivity = Activities.ACTIVE;
|
||||
|
||||
return showWidgetName ? (
|
||||
<PositionStyle>
|
||||
<PositionStyle data-testid="t--settings-controls-positioned-wrapper">
|
||||
<ControlGroup>
|
||||
<SettingsControl
|
||||
toggleSettings={togglePropertyEditor}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { mockCodemirrorRender } from "test/__mocks__/CodeMirrorEditorMock";
|
|||
import { PluginType } from "entities/Action";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
const TestForm = (props: any) => <div>{props.children}</div>;
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ describe("DynamicTextFieldControl", () => {
|
|||
id={"test"}
|
||||
isValid={true}
|
||||
pluginId="123"
|
||||
evaluationSubstitutionType={EvaluationSubstitutionType.TEMPLATE}
|
||||
/>
|
||||
</ReduxFormDecorator>,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@ import {
|
|||
getQueryParams,
|
||||
} from "utils/AppsmithUtils";
|
||||
import { actionPathFromName } from "components/formControls/utils";
|
||||
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 60%;
|
||||
.dynamic-text-field {
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
|
|
@ -64,6 +66,7 @@ class DynamicTextControl extends BaseControl<
|
|||
placeholderText,
|
||||
actionName,
|
||||
configProperty,
|
||||
evaluationSubstitutionType,
|
||||
} = this.props;
|
||||
const dataTreePath = actionPathFromName(actionName, configProperty);
|
||||
const isNewQuery =
|
||||
|
|
@ -104,6 +107,7 @@ class DynamicTextControl extends BaseControl<
|
|||
mode={mode}
|
||||
tabBehaviour={TabBehaviour.INDENT}
|
||||
placeholder={placeholderText}
|
||||
evaluationSubstitutionType={evaluationSubstitutionType}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
|
|
@ -117,6 +121,7 @@ export interface DynamicTextFieldProps extends ControlProps {
|
|||
pluginId: string;
|
||||
responseType: string;
|
||||
placeholderText?: string;
|
||||
evaluationSubstitutionType: EvaluationSubstitutionType;
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: AppState, props: DynamicTextFieldProps) => {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export interface ControlFunctions {
|
|||
openNextPanel: (props: any) => void;
|
||||
deleteProperties: (propertyPaths: string[]) => void;
|
||||
theme: EditorTheme;
|
||||
hideEvaluatedValue?: boolean;
|
||||
}
|
||||
|
||||
export default BaseControl;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { get, has, isString } from "lodash";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls";
|
||||
import styled from "constants/DefaultTheme";
|
||||
|
|
@ -13,6 +13,8 @@ import {
|
|||
TabBehaviour,
|
||||
} from "components/editorComponents/CodeEditor/EditorConfig";
|
||||
import { Size, Category } from "components/ads/Button";
|
||||
import { AllChartData, ChartData } from "widgets/ChartWidget";
|
||||
import { generateReactKey } from "utils/generators";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background-color: ${(props) =>
|
||||
|
|
@ -76,16 +78,15 @@ const Box = styled.div`
|
|||
`;
|
||||
|
||||
type RenderComponentProps = {
|
||||
index: number;
|
||||
item: {
|
||||
seriesName: string;
|
||||
data: Array<{ x: string; y: string }> | string;
|
||||
};
|
||||
index: string;
|
||||
item: ChartData;
|
||||
length: number;
|
||||
isValid: boolean;
|
||||
validationMessage: string;
|
||||
deleteOption: (index: number) => void;
|
||||
updateOption: (index: number, key: string, value: string) => void;
|
||||
validationMessage: {
|
||||
data: string;
|
||||
seriesName: string;
|
||||
};
|
||||
deleteOption: (index: string) => void;
|
||||
updateOption: (index: string, key: string, value: string) => void;
|
||||
evaluated: {
|
||||
seriesName: string;
|
||||
data: Array<{ x: string; y: string }> | any;
|
||||
|
|
@ -100,9 +101,10 @@ function DataControlComponent(props: RenderComponentProps) {
|
|||
item,
|
||||
index,
|
||||
length,
|
||||
isValid,
|
||||
evaluated,
|
||||
validationMessage,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<StyledOptionControlWrapper orientation={"VERTICAL"}>
|
||||
<ActionHolder>
|
||||
|
|
@ -160,7 +162,9 @@ function DataControlComponent(props: RenderComponentProps) {
|
|||
}}
|
||||
evaluatedValue={evaluated?.data}
|
||||
meta={{
|
||||
error: isValid ? "" : "There is an error",
|
||||
error: has(validationMessage, "data")
|
||||
? get(validationMessage, "data")
|
||||
: "",
|
||||
touched: true,
|
||||
}}
|
||||
theme={props.theme}
|
||||
|
|
@ -176,95 +180,55 @@ function DataControlComponent(props: RenderComponentProps) {
|
|||
}
|
||||
|
||||
class ChartDataControl extends BaseControl<ControlProps> {
|
||||
getValidations = (message: string, isValid: boolean, len: number) => {
|
||||
const validations: Array<{
|
||||
isValid: boolean;
|
||||
validationMessage: string;
|
||||
}> = [];
|
||||
let index = -1;
|
||||
let validationMessage = "";
|
||||
if (message.indexOf("##") !== -1) {
|
||||
const messages = message.split("##");
|
||||
index = Number(messages[0]);
|
||||
validationMessage = messages[1];
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (i === index) {
|
||||
validations.push({
|
||||
isValid: false,
|
||||
validationMessage: validationMessage,
|
||||
});
|
||||
} else {
|
||||
validations.push({
|
||||
isValid: true,
|
||||
validationMessage: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
return validations;
|
||||
};
|
||||
|
||||
getEvaluatedValue = () => {
|
||||
if (Array.isArray(this.props.evaluatedValue)) {
|
||||
return this.props.evaluatedValue;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
render() {
|
||||
const chartData: Array<{ seriesName: string; data: string }> = _.isString(
|
||||
this.props.propertyValue,
|
||||
)
|
||||
? []
|
||||
const chartData: AllChartData = isString(this.props.propertyValue)
|
||||
? {}
|
||||
: this.props.propertyValue;
|
||||
const dataLength = chartData.length;
|
||||
const { validationMessage, isValid } = this.props;
|
||||
const validations: Array<{
|
||||
isValid: boolean;
|
||||
validationMessage: string;
|
||||
}> = this.getValidations(
|
||||
validationMessage || "",
|
||||
isValid,
|
||||
chartData.length,
|
||||
);
|
||||
|
||||
const evaluatedValue = this.getEvaluatedValue();
|
||||
const dataLength = Object.keys(chartData).length;
|
||||
const { validationMessage } = this.props;
|
||||
|
||||
const evaluatedValue = this.props.evaluatedValue;
|
||||
const firstKey = Object.keys(chartData)[0] as string;
|
||||
|
||||
if (this.props.widgetProperties.chartType === "PIE_CHART") {
|
||||
const data = chartData.length
|
||||
? chartData[0]
|
||||
const data = dataLength
|
||||
? get(chartData, `${firstKey}`)
|
||||
: {
|
||||
seriesName: "",
|
||||
data: "",
|
||||
data: [],
|
||||
};
|
||||
|
||||
return (
|
||||
<DataControlComponent
|
||||
index={0}
|
||||
index={firstKey}
|
||||
item={data}
|
||||
length={1}
|
||||
deleteOption={this.deleteOption}
|
||||
updateOption={this.updateOption}
|
||||
isValid={validations[0].isValid}
|
||||
validationMessage={validations[0].validationMessage}
|
||||
evaluated={evaluatedValue[0]}
|
||||
validationMessage={get(validationMessage, `${firstKey}`)}
|
||||
evaluated={get(evaluatedValue, `${firstKey}`)}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Wrapper>
|
||||
{chartData.map((data, index) => {
|
||||
{Object.keys(chartData).map((key: string) => {
|
||||
const data = get(chartData, `${key}`);
|
||||
|
||||
return (
|
||||
<DataControlComponent
|
||||
key={index}
|
||||
index={index}
|
||||
key={key}
|
||||
index={key}
|
||||
item={data}
|
||||
length={dataLength}
|
||||
deleteOption={this.deleteOption}
|
||||
updateOption={this.updateOption}
|
||||
isValid={validations[index].isValid}
|
||||
validationMessage={validations[index].validationMessage}
|
||||
evaluated={evaluatedValue[index]}
|
||||
validationMessage={get(validationMessage, `${key}`)}
|
||||
evaluated={get(evaluatedValue, `${key}`)}
|
||||
theme={this.props.theme}
|
||||
/>
|
||||
);
|
||||
|
|
@ -284,27 +248,28 @@ class ChartDataControl extends BaseControl<ControlProps> {
|
|||
);
|
||||
}
|
||||
|
||||
deleteOption = (index: number) => {
|
||||
this.deleteProperties([`${this.props.propertyName}[${index}]`]);
|
||||
deleteOption = (index: string) => {
|
||||
this.deleteProperties([`${this.props.propertyName}.${index}`]);
|
||||
};
|
||||
|
||||
updateOption = (
|
||||
index: number,
|
||||
index: string,
|
||||
propertyName: string,
|
||||
updatedValue: string,
|
||||
) => {
|
||||
this.updateProperty(
|
||||
`${this.props.propertyName}[${index}].${propertyName}`,
|
||||
`${this.props.propertyName}.${index}.${propertyName}`,
|
||||
updatedValue,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* it adds new series data object in the chartData
|
||||
*/
|
||||
addOption = () => {
|
||||
const chartData: Array<{
|
||||
seriesName: string;
|
||||
data: string;
|
||||
}> = this.props.propertyValue;
|
||||
this.updateProperty(`${this.props.propertyName}[${chartData.length}]`, {
|
||||
const randomString = generateReactKey();
|
||||
|
||||
this.updateProperty(`${this.props.propertyName}.${randomString}`, {
|
||||
seriesName: "",
|
||||
data: JSON.stringify([{ x: "label", y: 50 }]),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class DropDownControl extends BaseControl<DropDownControlProps> {
|
|||
options={this.props.options}
|
||||
selected={defaultSelected}
|
||||
onSelect={this.onItemSelect}
|
||||
width="231px"
|
||||
width="100%"
|
||||
showLabelOnly={true}
|
||||
optionWidth={
|
||||
this.props.optionWidth ? this.props.optionWidth : "231px"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export function InputText(props: {
|
|||
dataTreePath?: string;
|
||||
additionalAutocomplete?: Record<string, Record<string, unknown>>;
|
||||
theme?: EditorTheme;
|
||||
hideEvaluatedValue?: boolean;
|
||||
}) {
|
||||
const {
|
||||
errorMessage,
|
||||
|
|
@ -32,7 +33,9 @@ export function InputText(props: {
|
|||
placeholder,
|
||||
dataTreePath,
|
||||
evaluatedValue,
|
||||
hideEvaluatedValue,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<StyledDynamicInput>
|
||||
<CodeEditor
|
||||
|
|
@ -53,6 +56,7 @@ export function InputText(props: {
|
|||
size={EditorSize.EXTENDED}
|
||||
placeholder={placeholder}
|
||||
additionalDynamicData={props.additionalAutocomplete}
|
||||
hideEvaluatedValue={hideEvaluatedValue}
|
||||
/>
|
||||
</StyledDynamicInput>
|
||||
);
|
||||
|
|
@ -69,7 +73,10 @@ class InputTextControl extends BaseControl<InputControlProps> {
|
|||
dataTreePath,
|
||||
validationMessage,
|
||||
defaultValue,
|
||||
additionalAutoComplete,
|
||||
hideEvaluatedValue,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<InputText
|
||||
label={label}
|
||||
|
|
@ -81,6 +88,8 @@ class InputTextControl extends BaseControl<InputControlProps> {
|
|||
dataTreePath={dataTreePath}
|
||||
placeholder={placeholderText}
|
||||
theme={this.props.theme}
|
||||
additionalAutocomplete={additionalAutoComplete}
|
||||
hideEvaluatedValue={hideEvaluatedValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ class PrimaryColumnsControl extends BaseControl<ControlProps> {
|
|||
|
||||
this.props.openNextPanel({
|
||||
...originalColumn,
|
||||
widgetId: this.props.widgetProperties.widgetId,
|
||||
propPaneId: this.props.widgetProperties.widgetId,
|
||||
});
|
||||
};
|
||||
//Used to reorder columns
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import {
|
||||
StyledHiddenIcon,
|
||||
StyledInputGroup,
|
||||
StyledPropertyPaneButton,
|
||||
StyledVisibleIcon,
|
||||
StyledDragIcon,
|
||||
StyledDeleteIcon,
|
||||
StyledEditIcon,
|
||||
} from "./StyledControls";
|
||||
import styled from "constants/DefaultTheme";
|
||||
import { generateReactKey } from "utils/generators";
|
||||
|
|
@ -63,12 +62,15 @@ type RenderComponentProps = {
|
|||
deleteOption: (index: number) => void;
|
||||
updateOption: (index: number, value: string) => void;
|
||||
toggleVisibility?: (index: number) => void;
|
||||
onEdit?: (props: any) => void;
|
||||
};
|
||||
|
||||
function TabControlComponent(props: RenderComponentProps) {
|
||||
const { deleteOption, updateOption, item, index, toggleVisibility } = props;
|
||||
const { deleteOption, updateOption, item, index } = props;
|
||||
const debouncedUpdate = debounce(updateOption, 1000);
|
||||
const [visibility, setVisibility] = useState(item.isVisible);
|
||||
const handleChange = useCallback(() => props.onEdit && props.onEdit(index), [
|
||||
index,
|
||||
]);
|
||||
return (
|
||||
<ItemWrapper>
|
||||
<StyledDragIcon height={20} width={20} />
|
||||
|
|
@ -89,29 +91,12 @@ function TabControlComponent(props: RenderComponentProps) {
|
|||
deleteOption(index);
|
||||
}}
|
||||
/>
|
||||
{visibility || visibility === undefined ? (
|
||||
<StyledVisibleIcon
|
||||
className="t--show-tab-btn"
|
||||
height={20}
|
||||
width={20}
|
||||
marginRight={36}
|
||||
onClick={() => {
|
||||
setVisibility(!visibility);
|
||||
toggleVisibility && toggleVisibility(index);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<StyledHiddenIcon
|
||||
className="t--show-tab-btn"
|
||||
height={20}
|
||||
width={20}
|
||||
marginRight={36}
|
||||
onClick={() => {
|
||||
setVisibility(!visibility);
|
||||
toggleVisibility && toggleVisibility(index);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<StyledEditIcon
|
||||
className="t--edit-column-btn"
|
||||
height={20}
|
||||
width={20}
|
||||
onClick={handleChange}
|
||||
/>
|
||||
</ItemWrapper>
|
||||
);
|
||||
}
|
||||
|
|
@ -148,15 +133,37 @@ class TabControl extends BaseControl<ControlProps> {
|
|||
}
|
||||
}
|
||||
|
||||
updateItems = (items: Array<Record<string, unknown>>) => {
|
||||
this.updateProperty(this.props.propertyName, items);
|
||||
updateItems = (items: Array<Record<string, any>>) => {
|
||||
const tabsObj = items.reduce((obj: any, each: any, index: number) => {
|
||||
obj[each.id] = {
|
||||
...each,
|
||||
index,
|
||||
};
|
||||
return obj;
|
||||
}, {});
|
||||
this.updateProperty(this.props.propertyName, tabsObj);
|
||||
};
|
||||
|
||||
onEdit = (index: number) => {
|
||||
const tabs: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}> = Object.values(this.props.propertyValue);
|
||||
const tabToChange = tabs[index];
|
||||
this.props.openNextPanel({
|
||||
index,
|
||||
...tabToChange,
|
||||
propPaneId: this.props.widgetProperties.widgetId,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const tabs: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}> = _.isString(this.props.propertyValue) ? [] : this.props.propertyValue;
|
||||
}> = _.isString(this.props.propertyValue)
|
||||
? []
|
||||
: Object.values(this.props.propertyValue);
|
||||
return (
|
||||
<TabsWrapper>
|
||||
<DroppableComponent
|
||||
|
|
@ -167,6 +174,7 @@ class TabControl extends BaseControl<ControlProps> {
|
|||
updateOption={this.updateOption}
|
||||
updateItems={this.updateItems}
|
||||
toggleVisibility={this.toggleVisibility}
|
||||
onEdit={this.onEdit}
|
||||
/>
|
||||
<StyledPropertyPaneButtonWrapper>
|
||||
<StyledPropertyPaneButton
|
||||
|
|
@ -204,51 +212,52 @@ class TabControl extends BaseControl<ControlProps> {
|
|||
};
|
||||
|
||||
deleteOption = (index: number) => {
|
||||
let tabs: Array<Record<string, unknown>> = this.props.propertyValue.slice();
|
||||
if (tabs.length === 1) return;
|
||||
delete tabs[index];
|
||||
tabs = tabs.filter(Boolean);
|
||||
this.updateProperty(this.props.propertyName, tabs);
|
||||
const tabsArray: any = Object.values(this.props.propertyValue);
|
||||
const itemId = tabsArray[index].id;
|
||||
if (tabsArray && tabsArray.length === 1) return;
|
||||
const updatedArray = tabsArray.filter((eachItem: any, i: number) => {
|
||||
return i !== index;
|
||||
});
|
||||
const updatedObj = updatedArray.reduce(
|
||||
(obj: any, each: any, index: number) => {
|
||||
obj[each.id] = {
|
||||
...each,
|
||||
index,
|
||||
};
|
||||
return obj;
|
||||
},
|
||||
{},
|
||||
);
|
||||
this.deleteProperties([`${this.props.propertyName}.${itemId}.isVisible`]);
|
||||
this.updateProperty(this.props.propertyName, updatedObj);
|
||||
};
|
||||
|
||||
updateOption = (index: number, updatedLabel: string) => {
|
||||
const tabs: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
}> = this.props.propertyValue;
|
||||
const updatedTabs = tabs.map((tab, tabIndex) => {
|
||||
if (index === tabIndex) {
|
||||
return {
|
||||
...tab,
|
||||
label: updatedLabel,
|
||||
};
|
||||
}
|
||||
return tab;
|
||||
});
|
||||
this.updateProperty(this.props.propertyName, updatedTabs);
|
||||
const tabsArray: any = Object.values(this.props.propertyValue);
|
||||
const itemId = tabsArray[index].id;
|
||||
this.updateProperty(
|
||||
`${this.props.propertyName}.${itemId}.label`,
|
||||
updatedLabel,
|
||||
);
|
||||
};
|
||||
|
||||
addOption = () => {
|
||||
let tabs: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
widgetId: string;
|
||||
isVisible: boolean;
|
||||
}> = this.props.propertyValue;
|
||||
let tabs = this.props.propertyValue;
|
||||
const tabsArray = Object.values(tabs);
|
||||
const newTabId = generateReactKey({ prefix: "tab" });
|
||||
const newTabLabel = getNextEntityName(
|
||||
"Tab ",
|
||||
tabs.map((tab) => tab.label),
|
||||
tabsArray.map((tab: any) => tab.label),
|
||||
);
|
||||
tabs = [
|
||||
tabs = {
|
||||
...tabs,
|
||||
{
|
||||
[newTabId]: {
|
||||
id: newTabId,
|
||||
label: newTabLabel,
|
||||
widgetId: generateReactKey(),
|
||||
isVisible: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
this.updateProperty(this.props.propertyName, tabs);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ export const DEFAULT_API_ACTION_CONFIG: ApiActionConfig = {
|
|||
httpMethod: HTTP_METHODS[0],
|
||||
headers: EMPTY_KEY_VALUE_PAIRS.slice(),
|
||||
queryParameters: EMPTY_KEY_VALUE_PAIRS.slice(),
|
||||
body: "",
|
||||
pluginSpecifiedTemplates: [
|
||||
{
|
||||
// JSON smart substitution
|
||||
value: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const DEFAULT_PROVIDER_OPTION = "Business Software";
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import queryActionSettingsConfig from "constants/AppsmithActionConstants/formCon
|
|||
import apiActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/ApiSettingsConfig";
|
||||
import apiActionEditorConfig from "constants/AppsmithActionConstants/formConfig/ApiEditorConfigs";
|
||||
import saasActionSettingsConfig from "constants/AppsmithActionConstants/formConfig/GoogleSheetsSettingsConfig";
|
||||
import apiActionDependencyConfig from "constants/AppsmithActionConstants/formConfig/ApiDependencyConfigs";
|
||||
|
||||
export type ExecuteActionPayloadEvent = {
|
||||
type: EventType;
|
||||
|
|
@ -21,6 +22,11 @@ export type ExecuteActionPayload = {
|
|||
responseData?: Array<any>;
|
||||
};
|
||||
|
||||
// triggerPropertyName was added as a requirement for logging purposes
|
||||
export type WidgetExecuteActionPayload = ExecuteActionPayload & {
|
||||
triggerPropertyName?: string;
|
||||
};
|
||||
|
||||
export type ContentType =
|
||||
| "application/json"
|
||||
| "application/x-www-form-urlencoded";
|
||||
|
|
@ -118,3 +124,12 @@ export const defaultActionEditorConfigs: Record<PluginType, any> = {
|
|||
[PluginType.DB]: [],
|
||||
[PluginType.SAAS]: [],
|
||||
};
|
||||
|
||||
export const defaultActionDependenciesConfig: Record<
|
||||
PluginType,
|
||||
Record<string, string[]>
|
||||
> = {
|
||||
[PluginType.API]: apiActionDependencyConfig,
|
||||
[PluginType.DB]: {},
|
||||
[PluginType.SAAS]: {},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
export default [
|
||||
{
|
||||
dependencies: {
|
||||
"actionConfiguration.body": [
|
||||
"actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
export default {
|
||||
"actionConfiguration.body": [
|
||||
"actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,6 +12,23 @@ export default [
|
|||
label: "Body",
|
||||
configProperty: "actionConfiguration.body",
|
||||
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||
evaluationSubstitutionType: "SMART_SUBSTITUTE",
|
||||
hidden: {
|
||||
path: "actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
comparison: "EQUALS",
|
||||
value: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Body",
|
||||
configProperty: "actionConfiguration.body",
|
||||
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
|
||||
evaluationSubstitutionType: "TEMPLATE",
|
||||
hidden: {
|
||||
path: "actionConfiguration.pluginSpecifiedTemplates[0].value",
|
||||
comparison: "EQUALS",
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Query Parameters",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export const DATA_BIND_REGEX = /{{([\s\S]*?)}}/;
|
||||
export const DATA_BIND_REGEX_GLOBAL = /{{([\s\S]*?)}}/g;
|
||||
export const AUTOCOMPLETE_MATCH_REGEX = /{{\s*.*?\s*}}/g;
|
||||
export const QUOTED_BINDING_REGEX = /["']({{[\s\S]*?}})["']/g;
|
||||
|
|
|
|||
|
|
@ -924,6 +924,38 @@ type ColorType = {
|
|||
};
|
||||
scrollbar: string;
|
||||
scrollbarBG: string;
|
||||
debugger: {
|
||||
background: string;
|
||||
messageTextColor: string;
|
||||
time: string;
|
||||
label: string;
|
||||
entity: string;
|
||||
entityLink: string;
|
||||
floatingButton: {
|
||||
background: string;
|
||||
color: string;
|
||||
shadow: string;
|
||||
errorCount: string;
|
||||
noErrorCount: string;
|
||||
};
|
||||
blankState: {
|
||||
shortcut: string;
|
||||
color: string;
|
||||
};
|
||||
info: {
|
||||
borderBottom: string;
|
||||
};
|
||||
warning: {
|
||||
borderBottom: string;
|
||||
backgroundColor: string;
|
||||
};
|
||||
error: {
|
||||
borderBottom: string;
|
||||
backgroundColor: string;
|
||||
};
|
||||
jsonIcon: string;
|
||||
message: string;
|
||||
};
|
||||
helpModal: {
|
||||
itemHighlight: string;
|
||||
background: string;
|
||||
|
|
@ -1397,6 +1429,38 @@ export const dark: ColorType = {
|
|||
},
|
||||
scrollbar: getColorWithOpacity(Colors.LIGHT_GREY, 0.5),
|
||||
scrollbarBG: getColorWithOpacity(Colors.CODE_GRAY, 0.5),
|
||||
debugger: {
|
||||
background: darkShades[11],
|
||||
messageTextColor: "#D4D4D4",
|
||||
time: "#D4D4D4",
|
||||
label: "#D4D4D4",
|
||||
entity: "rgba(212, 212, 212, 0.5)",
|
||||
entityLink: "#D4D4D4",
|
||||
jsonIcon: "#9F9F9F",
|
||||
message: "#D4D4D4",
|
||||
floatingButton: {
|
||||
background: "#2b2b2b",
|
||||
color: "#d4d4d4",
|
||||
shadow: "0px 12px 28px -6px rgba(0, 0, 0, 0.32)",
|
||||
errorCount: "#F22B2B",
|
||||
noErrorCount: "#03B365",
|
||||
},
|
||||
blankState: {
|
||||
color: "#D4D4D4",
|
||||
shortcut: "#D4D4D4",
|
||||
},
|
||||
info: {
|
||||
borderBottom: "black",
|
||||
},
|
||||
warning: {
|
||||
borderBottom: "black",
|
||||
backgroundColor: "#29251A",
|
||||
},
|
||||
error: {
|
||||
borderBottom: "black",
|
||||
backgroundColor: "#291B1D",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const light: ColorType = {
|
||||
|
|
@ -1809,6 +1873,38 @@ export const light: ColorType = {
|
|||
},
|
||||
scrollbar: getColorWithOpacity(Colors.CHARCOAL, 0.5),
|
||||
scrollbarBG: "transparent",
|
||||
debugger: {
|
||||
background: "#FFFFFF",
|
||||
messageTextColor: "#716e6e",
|
||||
time: "#4b4848",
|
||||
label: "#4b4848",
|
||||
entity: "rgba(75, 72, 72, 0.7)",
|
||||
entityLink: "#6d6d6d",
|
||||
jsonIcon: "#a9a7a7",
|
||||
message: "#4b4848",
|
||||
floatingButton: {
|
||||
background: "#2b2b2b",
|
||||
color: "#d4d4d4",
|
||||
shadow: "0px 12px 28px -6px rgba(0, 0, 0, 0.32)",
|
||||
errorCount: "#F22B2B",
|
||||
noErrorCount: "#03B365",
|
||||
},
|
||||
blankState: {
|
||||
color: "#716e6e",
|
||||
shortcut: "black",
|
||||
},
|
||||
info: {
|
||||
borderBottom: "rgba(0, 0, 0, 0.05)",
|
||||
},
|
||||
warning: {
|
||||
borderBottom: "white",
|
||||
backgroundColor: "rgba(254, 184, 17, 0.1)",
|
||||
},
|
||||
error: {
|
||||
borderBottom: "white",
|
||||
backgroundColor: "rgba(242, 43, 43, 0.08)",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const theme: Theme = {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ const FIELD_VALUES: Record<
|
|||
CANVAS_WIDGET: {},
|
||||
ICON_WIDGET: {},
|
||||
SKELETON_WIDGET: {},
|
||||
TABS_MIGRATOR_WIDGET: {},
|
||||
CONTAINER_WIDGET: {
|
||||
backgroundColor: "string",
|
||||
isVisible: "boolean",
|
||||
|
|
@ -61,8 +62,6 @@ const FIELD_VALUES: Record<
|
|||
// onSelectionChange: "Function Call",
|
||||
},
|
||||
TABS_WIDGET: {
|
||||
tabs:
|
||||
"Array<{ label: string, id: string(unique), widgetId: string(unique) }>",
|
||||
selectedTab: "string",
|
||||
isVisible: "boolean",
|
||||
},
|
||||
|
|
@ -166,6 +165,11 @@ const FIELD_VALUES: Record<
|
|||
shouldScroll: "boolean",
|
||||
isVisible: "boolean",
|
||||
},
|
||||
LIST_WIDGET: {
|
||||
items: "Array<Object>",
|
||||
isVisible: "boolean",
|
||||
gridGap: "number",
|
||||
},
|
||||
};
|
||||
|
||||
export default FIELD_VALUES;
|
||||
|
|
|
|||
|
|
@ -83,6 +83,10 @@ export const HelpMap = {
|
|||
path: "",
|
||||
searchKey: "Tabs",
|
||||
},
|
||||
TABS_MIGRATOR_WIDGET: {
|
||||
path: "",
|
||||
searchKey: "",
|
||||
},
|
||||
MODAL_WIDGET: {
|
||||
path: "",
|
||||
searchKey: "",
|
||||
|
|
@ -103,6 +107,10 @@ export const HelpMap = {
|
|||
path: "/core-concepts/connecting-to-data-sources/connecting-to-databases",
|
||||
searchKey: "Connecting to databases",
|
||||
},
|
||||
LIST_WIDGET: {
|
||||
path: "/widget-reference/list",
|
||||
searchKey: "List",
|
||||
},
|
||||
SWITCH_WIDGET: {
|
||||
path: "/widget-reference/switch",
|
||||
searchKey: "Switch",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const Layers = {
|
|||
apiPane: Indices.Layer3,
|
||||
help: Indices.Layer4,
|
||||
dynamicAutoComplete: Indices.Layer5,
|
||||
debugger: Indices.Layer6,
|
||||
productUpdates: Indices.Layer7,
|
||||
max: Indices.LayerMax,
|
||||
};
|
||||
|
|
|
|||
58
app/client/src/constants/OmnibarDocumentationConstants.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { includes, some } from "lodash";
|
||||
|
||||
const omnibarDocumentationHelper = (linkURL: string) => {
|
||||
const documentationHeaders = [
|
||||
{
|
||||
text: ["mongodb", "mongo-plugin"],
|
||||
query: "MongoDB",
|
||||
},
|
||||
{
|
||||
text: ["redis"],
|
||||
query: "redis",
|
||||
},
|
||||
{
|
||||
text: ["redshift"],
|
||||
query: "redshift",
|
||||
},
|
||||
{
|
||||
text: ["amazon-s3"],
|
||||
query: "Amazon S3",
|
||||
},
|
||||
{
|
||||
text: ["querying-firestore"],
|
||||
query: "Firestore",
|
||||
},
|
||||
{
|
||||
text: ["querying-mysql"],
|
||||
query: "Mysql",
|
||||
},
|
||||
{
|
||||
text: ["querying-mssql"],
|
||||
query: "Mssql",
|
||||
},
|
||||
{
|
||||
text: ["querying-elasticsearch"],
|
||||
query: "elasticsearch",
|
||||
},
|
||||
{
|
||||
text: ["querying-postgres", "postgres-plugin"],
|
||||
query: "postgres",
|
||||
},
|
||||
{
|
||||
text: ["querying-dynamodb"],
|
||||
query: "dynamodb",
|
||||
},
|
||||
];
|
||||
const doc = documentationHeaders.find((headerItem) =>
|
||||
some(headerItem.text, (el) =>
|
||||
includes(linkURL.toLowerCase(), el.toLowerCase()),
|
||||
),
|
||||
);
|
||||
if (doc) {
|
||||
return doc.query;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export { omnibarDocumentationHelper };
|
||||
|
|
@ -33,6 +33,13 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
UPDATE_APP_LAYOUT: "UPDATE_APP_LAYOUT",
|
||||
UPDATE_APPLICATION_SUCCESS: "UPDATE_APPLICATION_SUCCESS",
|
||||
PUBLISH: "PUBLISH",
|
||||
DEBUGGER_LOG: "DEBUGGER_LOG",
|
||||
DEBUGGER_LOG_INIT: "DEBUGGER_LOG_INIT",
|
||||
DEBUGGER_ERROR_LOG: "DEBUGGER_ERROR_LOG",
|
||||
DEBUGGER_UPDATE_ERROR_LOG: "DEBUGGER_UPDATE_ERROR_LOG",
|
||||
DEBUGGER_UPDATE_ERROR_LOGS: "DEBUGGER_UPDATE_ERROR_LOGS",
|
||||
CLEAR_DEBUGGER_LOGS: "CLEAR_DEBUGGER_LOGS",
|
||||
SHOW_DEBUGGER: "SHOW_DEBUGGER",
|
||||
SET_THEME: "SET_THEME",
|
||||
FETCH_WIDGET_CARDS: "FETCH_WIDGET_CARDS",
|
||||
FETCH_WIDGET_CARDS_SUCCESS: "FETCH_WIDGET_CARDS_SUCCESS",
|
||||
|
|
@ -333,6 +340,7 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
UNDO_DELETE_WIDGET: "UNDO_DELETE_WIDGET",
|
||||
CUT_SELECTED_WIDGET: "CUT_SELECTED_WIDGET",
|
||||
WIDGET_ADD_CHILDREN: "WIDGET_ADD_CHILDREN",
|
||||
WIDGET_UPDATE_PROPERTY: "WIDGET_UPDATE_PROPERTY",
|
||||
SET_EVALUATED_TREE: "SET_EVALUATED_TREE",
|
||||
SET_EVALUATION_INVERSE_DEPENDENCY_MAP:
|
||||
"SET_EVALUATION_INVERSE_DEPENDENCY_MAP",
|
||||
|
|
@ -351,6 +359,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
RESET_UNREAD_RELEASES_COUNT: "RESET_UNREAD_RELEASES_COUNT",
|
||||
SET_LOADING_ENTITIES: "SET_LOADING_ENTITIES",
|
||||
RESET_CURRENT_APPLICATION: "RESET_CURRENT_APPLICATION",
|
||||
RESET_APPLICATION_WIDGET_STATE_REQUEST:
|
||||
"RESET_APPLICATION_WIDGET_STATE_REQUEST",
|
||||
SAAS_GET_OAUTH_ACCESS_TOKEN: "SAAS_GET_OAUTH_ACCESS_TOKEN",
|
||||
UPDATE_RECENT_ENTITY: "UPDATE_RECENT_ENTITY",
|
||||
RESTORE_RECENT_ENTITIES_REQUEST: "RESTORE_RECENT_ENTITIES_REQUEST",
|
||||
|
|
@ -490,7 +500,7 @@ export interface ReduxActionWithCallbacks<T, S, E> extends ReduxAction<T> {
|
|||
}
|
||||
|
||||
export interface EvaluationReduxAction<T> extends ReduxAction<T> {
|
||||
postEvalActions?: ReduxAction<any>[];
|
||||
postEvalActions?: Array<ReduxAction<any> | ReduxActionWithoutPayload>;
|
||||
}
|
||||
|
||||
export interface PromisePayload {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ export enum WidgetTypes {
|
|||
FILE_PICKER_WIDGET = "FILE_PICKER_WIDGET",
|
||||
VIDEO_WIDGET = "VIDEO_WIDGET",
|
||||
SKELETON_WIDGET = "SKELETON_WIDGET",
|
||||
LIST_WIDGET = "LIST_WIDGET",
|
||||
SWITCH_WIDGET = "SWITCH_WIDGET",
|
||||
TABS_MIGRATOR_WIDGET = "TABS_MIGRATOR_WIDGET",
|
||||
}
|
||||
|
||||
export type WidgetType = keyof typeof WidgetTypes;
|
||||
|
|
|
|||