Merge branch 'release' into fix/#3860-input-widget-validation

This commit is contained in:
Paul Li 2021-04-27 09:19:50 -04:00
commit 17dd8d525e
294 changed files with 11348 additions and 2084 deletions

10
.github/config.json vendored
View File

@ -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",

View File

@ -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
View File

@ -2,6 +2,7 @@
.idea
*.iml
.env
.vscode/*
# test coverage
coverage-summary.json
coverage-summary.json

View File

@ -43,3 +43,5 @@ storybook-static/*
build-storybook.log
.eslintcache
.vscode
TODO

View File

@ -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)

View File

@ -2,6 +2,9 @@
"env": {
"cypress/globals": true
},
"rules": {
"cypress/no-unnecessary-waiting": 0
},
"extends": [
"plugin:cypress/recommended"
]

View File

@ -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",

View File

@ -114,7 +114,7 @@
"inputType": "TEXT",
"label": "Endpoint",
"widgetName": "EndpointInput",
"defaultText": "todos",
"defaultText": "offers",
"type": "INPUT_WIDGET",
"isLoading": false,
"parentColumnSpace": 71.75,

View 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"
}
}
}
]
}
}

Binary file not shown.

View 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");
});
});
});

View File

@ -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();
});
});

View File

@ -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");
});
});

View File

@ -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
});

View File

@ -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()

View File

@ -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

View File

@ -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);
});
});

View File

@ -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
});
});

View File

@ -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");

View File

@ -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",

View File

@ -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.]",
);
});
});

View File

@ -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",

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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: {

View File

@ -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",

View File

@ -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(() => {

View File

@ -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,
};
};

View File

@ -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 = (

View 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,
});

View File

@ -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],

View File

@ -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 = (

View 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);
});
});

View File

@ -8,3 +8,9 @@ export const updateWidgetName = (widgetId: string, newName: string) => {
},
};
};
export const hidePropertyPane = () => {
return {
type: ReduxActionTypes.HIDE_PROPERTY_PANE,
};
};

View File

@ -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 = (

View File

@ -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 {

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -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;

View File

@ -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`

View File

@ -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")};

View File

@ -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 ? (

View File

@ -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(),

View File

@ -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;

View File

@ -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>

View File

@ -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";

View File

@ -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}>

View 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;

View File

@ -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);
});
});

View File

@ -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();
});
});

View File

@ -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);
}}

View File

@ -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}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View 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;

View File

@ -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;

View File

@ -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 };
};

View File

@ -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;

View File

@ -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);
});
});

View File

@ -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 &&

View File

@ -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);
});
});

View File

@ -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);
},
});

View File

@ -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;

View File

@ -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>&#8984;</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>

View File

@ -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>
);

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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}

View File

@ -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>,
{

View File

@ -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) => {

View File

@ -47,6 +47,7 @@ export interface ControlFunctions {
openNextPanel: (props: any) => void;
deleteProperties: (propertyPaths: string[]) => void;
theme: EditorTheme;
hideEvaluatedValue?: boolean;
}
export default BaseControl;

View File

@ -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 }]),
});

View File

@ -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"

View File

@ -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}
/>
);
}

View File

@ -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

View File

@ -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);
};

View File

@ -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";

View File

@ -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]: {},
};

View File

@ -1,9 +1,5 @@
export default [
{
dependencies: {
"actionConfiguration.body": [
"actionConfiguration.pluginSpecifiedTemplates[0].value",
],
},
},
];
export default {
"actionConfiguration.body": [
"actionConfiguration.pluginSpecifiedTemplates[0].value",
],
};

View File

@ -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",

View File

@ -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;

View File

@ -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 = {

View File

@ -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;

View File

@ -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",

View File

@ -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,
};

View 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 };

View File

@ -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 {

View File

@ -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;

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