From a5a0e0d85d904cb5c2c9f0c4640d79e9ec00a79b Mon Sep 17 00:00:00 2001 From: Paul Li Date: Tue, 4 May 2021 15:48:40 -0400 Subject: [PATCH 001/119] FEATURE-4135 : Table URL columns pretty link -- Add displayText field into property pane -- Display urls based on displayText -- Fix url alignment issue --- .../TableComponent/AutoToolTipComponent.tsx | 2 +- .../appsmith/TableComponent/Constants.ts | 2 ++ .../TableComponent/TableStyledWrappers.tsx | 3 +++ .../appsmith/TableComponent/TableUtilities.tsx | 10 +++++++++- .../TableWidget/TablePropertyPaneConfig.ts | 18 ++++++++++++++++++ app/client/src/widgets/TableWidget/index.tsx | 4 ++++ 6 files changed, 37 insertions(+), 2 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/AutoToolTipComponent.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/AutoToolTipComponent.tsx index f8f9d93fba..1380448883 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/AutoToolTipComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/AutoToolTipComponent.tsx @@ -85,7 +85,7 @@ function AutoToolTipComponent(props: Props) { updateToolTip(false); } }, [ref]); - if (props.columnType === ColumnTypes.URL) { + if (props.columnType === ColumnTypes.URL && props.title) { return ; } return ( diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts index a8dbb7d807..88c74bac35 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Constants.ts @@ -94,6 +94,7 @@ export interface CellLayoutProperties { buttonStyle?: string; buttonLabelColor?: string; buttonLabel?: string; + displayText?: string; } export interface TableColumnMetaProps { @@ -144,6 +145,7 @@ export interface ColumnProperties { inputFormat?: string; dropdownOptions?: string; onOptionChange?: string; + displayText?: string; } export const ConditionFunctions: { diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx index e516d2d432..75d98bbd82 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx @@ -413,6 +413,9 @@ export const CellWrapper = styled.div<{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + text-align: ${(props) => + props?.cellProperties?.horizontalAlignment && + TEXT_ALIGN[props?.cellProperties?.horizontalAlignment]}; } .hidden-icon { display: none; diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx index e639867e84..50eae8e479 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx @@ -112,7 +112,15 @@ export const renderCell = ( tableWidth={tableWidth} title={value.toString()} > - {value.toString()} + {value && + columnType === ColumnTypes.URL && + cellProperties.displayText ? ( + + {cellProperties.displayText} + + ) : ( + value.toString() + )} ); } diff --git a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts index 2273b8ed17..174bd36a60 100644 --- a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts +++ b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts @@ -201,6 +201,24 @@ export default [ isBindProperty: false, isTriggerProperty: false, }, + { + propertyName: "displayText", + label: "Display Text", + controlType: "INPUT_TEXT", + customJSControl: "COMPUTE_VALUE", + updateHook: updateDerivedColumnsHook, + hidden: (props: TableWidgetProps, propertyPath: string) => { + const baseProperty = getBasePropertyPath(propertyPath); + const columnType = get( + props, + `${baseProperty}.columnType`, + "", + ); + return columnType !== "url"; + }, + isBindProperty: false, + isTriggerProperty: false, + }, { propertyName: "computedValue", label: "Computed Value", diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx index ba4f1acb18..45340af266 100644 --- a/app/client/src/widgets/TableWidget/index.tsx +++ b/app/client/src/widgets/TableWidget/index.tsx @@ -120,6 +120,10 @@ class TableWidget extends BaseWidget { textSize: this.getPropertyValue(columnProperties.textSize, rowIndex), textColor: this.getPropertyValue(columnProperties.textColor, rowIndex), fontStyle: this.getPropertyValue(columnProperties.fontStyle, rowIndex), //Fix this + displayText: this.getPropertyValue( + columnProperties.displayText, + rowIndex, + ), }; return cellProperties; }; From 53b99c1a34fe2bbb1cec37ad70248c9d3cf86ec3 Mon Sep 17 00:00:00 2001 From: "vicky-primathon.in" Date: Wed, 5 May 2021 10:59:49 +0530 Subject: [PATCH 002/119] Fix email parsing issue in text widget --- .../designSystems/blueprint/EmailMatcher.tsx | 81 +++++++++++++++++++ .../designSystems/blueprint/TextComponent.tsx | 12 +-- 2 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 app/client/src/components/designSystems/blueprint/EmailMatcher.tsx diff --git a/app/client/src/components/designSystems/blueprint/EmailMatcher.tsx b/app/client/src/components/designSystems/blueprint/EmailMatcher.tsx new file mode 100644 index 0000000000..aa5a84bda3 --- /dev/null +++ b/app/client/src/components/designSystems/blueprint/EmailMatcher.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { ChildrenNode, Matcher, MatchResponse, Node } from "interweave"; +import { EmailProps, Email } from "interweave-autolink"; + +type EmailMatch = Pick; + +interface CombinePatternsOptions { + capture?: boolean; + flags?: string; + join?: string; + match?: string; + nonCapture?: boolean; +} + +function combinePatterns( + patterns: RegExp[], + options: CombinePatternsOptions = {}, +) { + let regex = patterns + .map((pattern) => pattern.source) + .join(options.join || ""); + + if (options.capture) { + regex = `(${regex})`; + } else if (options.nonCapture) { + regex = `(?:${regex})`; + } + + if (options.match) { + regex += options.match; + } + + return new RegExp(regex, options.flags || ""); +} + +const URL_HOST = combinePatterns( + [ + /(?:(?:[a-z0-9](?:[-a-z0-9_]*[a-z0-9])?)\.)*/, // Subdomain + /(?:(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)\.)/, // Domain + /(?:[a-z](?:[-a-z0-9]*[a-z0-9])?)/, // TLD + ], + { + capture: true, + }, +); + +const EMAIL_USERNAME_PART = /[.a-z0-9!#$%&?*+=_{|}~-]+/; + +const VALID_ALNUM_CHARS = /[a-z0-9]/; + +const EMAIL_USERNAME = combinePatterns( + [VALID_ALNUM_CHARS, EMAIL_USERNAME_PART, VALID_ALNUM_CHARS], + { + capture: true, + }, +); + +export const EMAIL_PATTERN = combinePatterns([EMAIL_USERNAME, URL_HOST], { + flags: "i", + join: "@", +}); + +export default class EmailMatcher extends Matcher { + replaceWith(children: ChildrenNode, props: EmailProps): Node { + return React.createElement(Email, props, children); + } + + asTag(): string { + return "a"; + } + + match(string: string): MatchResponse | null { + return this.doMatch(string, EMAIL_PATTERN, (matches) => ({ + email: matches[0], + emailParts: { + host: matches[2], + username: matches[1], + }, + })); + } +} diff --git a/app/client/src/components/designSystems/blueprint/TextComponent.tsx b/app/client/src/components/designSystems/blueprint/TextComponent.tsx index f359fec4de..de409d4edf 100644 --- a/app/client/src/components/designSystems/blueprint/TextComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/TextComponent.tsx @@ -4,7 +4,8 @@ import styled from "styled-components"; import { ComponentProps } from "components/designSystems/appsmith/BaseComponent"; import { TextAlign } from "widgets/TextWidget"; import Interweave from "interweave"; -import { UrlMatcher, EmailMatcher } from "interweave-autolink"; +import { UrlMatcher } from "interweave-autolink"; +import EmailMatcher, { EMAIL_PATTERN } from "./EmailMatcher"; import { FontStyleTypes, TextSize, @@ -83,6 +84,10 @@ class TextComponent extends React.Component { textColor, backgroundColor, } = this.props; + let matchers = [new UrlMatcher("url"), new EmailMatcher("email")]; + if (text && EMAIL_PATTERN.test(text)) { + matchers = [new EmailMatcher("email")]; + } return ( { backgroundColor={backgroundColor} className={this.props.isLoading ? "bp3-skeleton" : "bp3-ui-text"} > - + ); From ba2d9e1ed2558ff3f192a786e97781f8cc490096 Mon Sep 17 00:00:00 2001 From: "vicky-primathon.in" Date: Wed, 5 May 2021 11:30:33 +0530 Subject: [PATCH 003/119] Added cypress test to validate email parsing --- .../ClientSideTests/DisplayWidgets/Text_spec.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js index cf610c78f5..e0e0885887 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/DisplayWidgets/Text_spec.js @@ -36,7 +36,19 @@ describe("Text Widget Functionality", function() { .should("have.css", "font-size", "24px"); }); + it("Text Email Parsing Validation", function() { + cy.testCodeMirror("ab.end@domain.com"); + cy.wait("@updateLayout"); + cy.PublishtheApp(); + cy.get(commonlocators.headingTextStyle + " a").should( + "have.attr", + "href", + "mailto:ab.end@domain.com", + ); + }); + it("Text-TextStyle Label Validation", function() { + cy.testCodeMirror(this.data.TextLabelValue); //Changing the Text Style's and validating cy.ChangeTextStyle( this.data.TextLabel, From 4be80b665aba06fc410749be9bb335d3d35e5c12 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Wed, 5 May 2021 05:26:57 -0400 Subject: [PATCH 004/119] FEATURE-4135 : Table URL columns pretty link -- Remove the anchor tag wrapping display text -- Make display text case-sensitive --- .../appsmith/TableComponent/TableUtilities.tsx | 12 +++--------- app/client/src/widgets/TableWidget/index.tsx | 1 + 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx index 50eae8e479..b913aaf66f 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableUtilities.tsx @@ -112,15 +112,9 @@ export const renderCell = ( tableWidth={tableWidth} title={value.toString()} > - {value && - columnType === ColumnTypes.URL && - cellProperties.displayText ? ( - - {cellProperties.displayText} - - ) : ( - value.toString() - )} + {value && columnType === ColumnTypes.URL && cellProperties.displayText + ? cellProperties.displayText + : value.toString()} ); } diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx index 45340af266..ddbab2f98a 100644 --- a/app/client/src/widgets/TableWidget/index.tsx +++ b/app/client/src/widgets/TableWidget/index.tsx @@ -123,6 +123,7 @@ class TableWidget extends BaseWidget { displayText: this.getPropertyValue( columnProperties.displayText, rowIndex, + true, ), }; return cellProperties; From 8ea4638d00238ad48dd53e3bf1ecbe994e83d921 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Thu, 6 May 2021 10:29:09 -0400 Subject: [PATCH 005/119] FEATURE-4135 : Table URL columns pretty link -- Change controlType for display text to COMPUTE_VALUE --- app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts index 174bd36a60..7e95e07e0b 100644 --- a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts +++ b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts @@ -204,7 +204,7 @@ export default [ { propertyName: "displayText", label: "Display Text", - controlType: "INPUT_TEXT", + controlType: "COMPUTE_VALUE", customJSControl: "COMPUTE_VALUE", updateHook: updateDerivedColumnsHook, hidden: (props: TableWidgetProps, propertyPath: string) => { From c3960a7a4d5377cfd143a451ba2db67edd6edf3a Mon Sep 17 00:00:00 2001 From: bhavin Date: Fri, 7 May 2021 18:38:43 +0530 Subject: [PATCH 006/119] fix :extended maxDate and minDate limits to 100 years from now. --- .../src/components/propertyControls/DatePickerControl.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/client/src/components/propertyControls/DatePickerControl.tsx b/app/client/src/components/propertyControls/DatePickerControl.tsx index 96aa68f0d3..0151e46e44 100644 --- a/app/client/src/components/propertyControls/DatePickerControl.tsx +++ b/app/client/src/components/propertyControls/DatePickerControl.tsx @@ -46,11 +46,11 @@ class DatePickerControl extends BaseControl< year = this.now.get("year"); maxDate: Date = this.now .clone() - .set({ month: 11, date: 31, year: this.year + 20 }) + .set({ month: 11, date: 31, year: this.year + 100 }) .toDate(); minDate: Date = this.now .clone() - .set({ month: 0, date: 1, year: this.year - 20 }) + .set({ month: 0, date: 1, year: this.year - 100 }) .toDate(); constructor(props: DatePickerControlProps) { From 02d1231d60336dd41f06280c9ff38d8d38cb9cda Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sun, 9 May 2021 04:59:13 +0000 Subject: [PATCH 007/119] fix: upgrade flow-bin from 0.91.0 to 0.148.0 Snyk has created this PR to upgrade flow-bin from 0.91.0 to 0.148.0. See this package in npm: See this project in Snyk: https://app.snyk.io/org/nikhil-nu4/project/d17f040d-321b-4afe-b115-70b9f45067b1?utm_source=github&utm_medium=upgrade-pr --- app/client/package.json | 2 +- app/client/yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/client/package.json b/app/client/package.json index bc24cf83d9..dd17952c75 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -65,7 +65,7 @@ "eslint": "^7.11.0", "fast-deep-equal": "^3.1.1", "fast-xml-parser": "^3.17.5", - "flow-bin": "^0.91.0", + "flow-bin": "^0.148.0", "fuse.js": "^3.4.5", "fusioncharts": "^3.16.0", "history": "^4.10.1", diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 8af878b06b..5e3ae9aad2 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -8619,9 +8619,10 @@ flatten@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" -flow-bin@^0.91.0: - version "0.91.0" - resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.91.0.tgz#f5c89729f74b2ccbd47df6fbfadbdcc89cc1e478" +flow-bin@^0.148.0: + version "0.148.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.148.0.tgz#1d264606dbb4d6e6070cc98a775e21dcd64e6890" + integrity sha512-7Cx6BUm8UAlbqtYJNYXdMrh900MQhNV+SjtBxZuWN7UmlVG4tIRNzNLEOjNnj2DN2vcL1wfI5IlSUXnws/QCEw== flow-parser@0.*: version "0.135.0" From ca7594ac3f8aff86c2b145fb4b50ea7ac6b21d2d Mon Sep 17 00:00:00 2001 From: bhavin Date: Tue, 11 May 2021 13:07:16 +0530 Subject: [PATCH 008/119] fixed : deafult and min/max is allow same date --- .../components/designSystems/blueprint/DatePickerComponent2.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx b/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx index 94fa2764fd..9afd11d7bb 100644 --- a/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx +++ b/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx @@ -168,6 +168,7 @@ class DatePickerComponent extends React.Component< if ( this.props.minDate && parsedMinDate.isValid() && + !parsedCurrentDate.isSame(parsedMinDate, "day") && parsedCurrentDate.isBefore(parsedMinDate) ) { isValid = false; @@ -179,6 +180,7 @@ class DatePickerComponent extends React.Component< isValid && this.props.maxDate && parsedMaxDate.isValid() && + !parsedCurrentDate.isSame(parsedMaxDate, "day") && parsedCurrentDate.isAfter(parsedMaxDate) ) { isValid = false; From 285f7faf411d4031b24f3d46d0ec0ec59b440633 Mon Sep 17 00:00:00 2001 From: Tolulope Adetula <31691737+Tooluloope@users.noreply.github.com> Date: Wed, 12 May 2021 11:03:34 +0100 Subject: [PATCH 009/119] fix: add color picker to form component --- app/client/src/widgets/FormWidget.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/widgets/FormWidget.tsx b/app/client/src/widgets/FormWidget.tsx index 40af23180a..585320c45f 100644 --- a/app/client/src/widgets/FormWidget.tsx +++ b/app/client/src/widgets/FormWidget.tsx @@ -19,7 +19,7 @@ class FormWidget extends ContainerWidget { label: "Background Color", helpText: "Use a html color name, HEX, RGB or RGBA value", placeholderText: "#FFFFFF / Gray / rgb(255, 99, 71)", - controlType: "INPUT_TEXT", + controlType: "COLOR_PICKER", isBindProperty: true, isTriggerProperty: false, validation: VALIDATION_TYPES.TEXT, From 0015d72122fd868219c755975e239bfea170903e Mon Sep 17 00:00:00 2001 From: bhavin Date: Wed, 12 May 2021 21:01:17 +0530 Subject: [PATCH 010/119] updated max year for datepicker to +100, same as property pane datepicker --- .../components/designSystems/blueprint/DatePickerComponent2.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx b/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx index 94fa2764fd..749480237e 100644 --- a/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx +++ b/app/client/src/components/designSystems/blueprint/DatePickerComponent2.tsx @@ -107,7 +107,7 @@ class DatePickerComponent extends React.Component< ? new Date(this.props.maxDate) : now .clone() - .set({ month: 11, date: 31, year: year + 20 }) + .set({ month: 11, date: 31, year: year + 100 }) .toDate(); const isValid = this.state.selectedDate ? this.isValidDate(new Date(this.state.selectedDate)) From 083efdcdbee807c695edf5b2323937f5232f20c1 Mon Sep 17 00:00:00 2001 From: Tolulope Adetula <31691737+Tooluloope@users.noreply.github.com> Date: Thu, 13 May 2021 13:31:46 +0100 Subject: [PATCH 011/119] fix: Form widget random color test --- .../Entity_Explorer_DragAndDropWidget_spec.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js index a2d387bd31..534e39f531 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ExplorerTests/Entity_Explorer_DragAndDropWidget_spec.js @@ -1,5 +1,6 @@ const testdata = require("../../../../fixtures/testdata.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); const explorer = require("../../../../locators/explorerlocators.json"); const commonlocators = require("../../../../locators/commonlocators.json"); const formWidgetsPage = require("../../../../locators/FormWidgets.json"); @@ -29,10 +30,13 @@ describe("Entity explorer Drag and Drop widgets testcases", 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(formWidgetsPage.formD) .should("have.css", "background-color") - .and("eq", this.data.rgbValue); + .and("eq", "rgb(3, 179, 101)"); /** * @param{toggleButton Css} Assert to be checked */ From e7cd7148fa2bbcdbd97b671079a3a723073df1b7 Mon Sep 17 00:00:00 2001 From: Tolulope Adetula <31691737+Tooluloope@users.noreply.github.com> Date: Thu, 13 May 2021 14:08:07 +0100 Subject: [PATCH 012/119] update: fix test --- .../ClientSideTests/FormWidgets/FormWidget_spec.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js index 50bef3f6c9..70cef20567 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/FormWidgets/FormWidget_spec.js @@ -3,6 +3,7 @@ const formWidgetsPage = require("../../../../locators/FormWidgets.json"); const publish = require("../../../../locators/publishWidgetspage.json"); const dsl = require("../../../../fixtures/formdsl.json"); const pages = require("../../../../locators/Pages.json"); +const widgetsPage = require("../../../../locators/Widgets.json"); describe("Form Widget Functionality", function() { before(() => { @@ -23,10 +24,13 @@ describe("Form 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(formWidgetsPage.formD) .should("have.css", "background-color") - .and("eq", this.data.rgbValue); + .and("eq", "rgb(3, 179, 101)"); /** * @param{toggleButton Css} Assert to be checked */ @@ -40,7 +44,7 @@ describe("Form Widget Functionality", function() { it("Form Widget Functionality To Verify The Colour", function() { cy.get(formWidgetsPage.formD) .should("have.css", "background-color") - .and("eq", this.data.rgbValue); + .and("eq", "rgb(3, 179, 101)"); }); it("Form Widget Functionality To Unchecked Visible Widget", function() { cy.get(publish.backToEditor).click(); From bbecba125e6c9933715f3ca378cb3fad4e7b2f55 Mon Sep 17 00:00:00 2001 From: Satish Gandham Date: Tue, 18 May 2021 00:42:06 +0530 Subject: [PATCH 013/119] - Wrap the methods passed to editableText component with useCallback - Use destructing for props --- .../editorComponents/EditableText.tsx | 102 +++++++++++------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/app/client/src/components/editorComponents/EditableText.tsx b/app/client/src/components/editorComponents/EditableText.tsx index b8c2983a02..baa81e42c0 100644 --- a/app/client/src/components/editorComponents/EditableText.tsx +++ b/app/client/src/components/editorComponents/EditableText.tsx @@ -92,10 +92,25 @@ const TextContainer = styled.div<{ isValid: boolean; minimal: boolean }>` `; export function EditableText(props: EditableTextProps) { - const [isEditing, setIsEditing] = useState(!!props.isEditingDefault); - const [value, setStateValue] = useState(props.defaultValue); + const { + beforeUnmount, + className, + defaultValue, + editInteractionKind, + forceDefault, + hideEditIcon, + isEditingDefault, + isInvalid, + minimal, + onBlur, + onTextChanged, + placeholder, + updating, + valueTransform, + } = props; + const [isEditing, setIsEditing] = useState(!!isEditingDefault); + const [value, setStateValue] = useState(defaultValue); const inputValRef = useRef(""); - const { beforeUnmount } = props; const setValue = useCallback((value) => { inputValRef.current = value; @@ -103,16 +118,16 @@ export function EditableText(props: EditableTextProps) { }, []); useEffect(() => { - setValue(props.defaultValue); - }, [props.defaultValue]); + setValue(defaultValue); + }, [defaultValue]); useEffect(() => { - setIsEditing(!!props.isEditingDefault); - }, [props.defaultValue, props.isEditingDefault]); + setIsEditing(!!isEditingDefault); + }, [defaultValue, isEditingDefault]); useEffect(() => { - if (props.forceDefault === true) setValue(props.defaultValue); - }, [props.forceDefault, props.defaultValue]); + if (forceDefault === true) setValue(defaultValue); + }, [forceDefault, defaultValue]); // at times onTextChange is not fired // for example when the modal is closed on clicking the overlay @@ -128,58 +143,63 @@ export function EditableText(props: EditableTextProps) { e.preventDefault(); e.stopPropagation(); }; - const onChange = (_value: string) => { - props.onBlur && props.onBlur(); - const isInvalid = props.isInvalid ? props.isInvalid(_value) : false; - if (!isInvalid) { - props.onTextChanged(_value); - setIsEditing(false); - } else { - Toaster.show({ - text: "Invalid name", - variant: Variant.danger, - }); - } - }; + const onChange = useCallback( + (_value: string) => { + onBlur && onBlur(); + const _isInvalid = isInvalid ? isInvalid(_value) : false; + if (!_isInvalid) { + onTextChanged(_value); + setIsEditing(false); + } else { + Toaster.show({ + text: "Invalid name", + variant: Variant.danger, + }); + } + }, + [isInvalid], + ); - const onInputchange = (_value: string) => { - let finalVal: string = _value; - if (props.valueTransform) { - finalVal = props.valueTransform(_value); - } - setValue(finalVal); - }; + const onInputchange = useCallback( + (_value: string) => { + let finalVal: string = _value; + if (valueTransform) { + finalVal = valueTransform(_value); + } + setValue(finalVal); + }, + [valueTransform], + ); - const errorMessage = props.isInvalid && props.isInvalid(value); + const errorMessage = isInvalid && isInvalid(value); const error = errorMessage ? errorMessage : undefined; return ( - + - {!props.minimal && - !props.hideEditIcon && - !props.updating && - !isEditing && } + {!minimal && !hideEditIcon && !updating && !isEditing && ( + + )} From c76ef9e3d75a67beef737870304d6e5d31188324 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Tue, 11 May 2021 03:32:58 -0400 Subject: [PATCH 014/119] FEATURE-2111 : Show/Hide Table Controls -- Add the header options section for the visibility setting -- Render conditionally the header part based on the setting --- .../appsmith/TableComponent/Table.tsx | 94 +++++++++++-------- .../appsmith/TableComponent/TableHeader.tsx | 66 ++++++++----- .../appsmith/TableComponent/index.tsx | 20 ++++ .../mockResponses/WidgetConfigResponse.tsx | 5 + .../TableWidget/TablePropertyPaneConfig.ts | 51 ++++++++++ app/client/src/widgets/TableWidget/index.tsx | 5 + 6 files changed, 179 insertions(+), 62 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx index cfc1b0bb85..9e4ae470c9 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx @@ -60,6 +60,11 @@ interface TableProps { applyFilter: (filters: ReactTableFilter[]) => void; compactMode?: CompactMode; updateCompactMode: (compactMode: CompactMode) => void; + isVisibleCompactMode?: boolean; + isVisibleDownload?: boolean; + isVisibleFilters?: boolean; + isVisiblePagination?: boolean; + isVisibleSearch?: boolean; } const defaultColumn = { @@ -170,49 +175,60 @@ export function Table(props: TableProps) { triggerRowSelection={props.triggerRowSelection} width={props.width} > - - - - - - - + width={props.width} + > + + + + + )}
void; tableSizes: TableSizes; + isVisibleCompactMode?: boolean; + isVisibleDownload?: boolean; + isVisibleFilters?: boolean; + isVisiblePagination?: boolean; + isVisibleSearch?: boolean; } function TableHeader(props: TableHeaderProps) { return ( <> - - - - - - - {props.serverSidePaginationEnabled && ( + )} + {(props.isVisibleFilters || + props.isVisibleDownload || + props.isVisibleCompactMode) && ( + + {props.isVisibleFilters && ( + + )} + + {props.isVisibleDownload && ( + + )} + + {props.isVisibleCompactMode && ( + + )} + + )} + + {props.isVisiblePagination && props.serverSidePaginationEnabled && ( )} - {!props.serverSidePaginationEnabled && ( + {props.isVisiblePagination && !props.serverSidePaginationEnabled && ( {props.tableData?.length} Records diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/index.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/index.tsx index 2d7ebbda4c..5850f52bf0 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/index.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/index.tsx @@ -66,6 +66,11 @@ interface ReactTableComponentProps { columns: ReactTableColumnProps[]; compactMode?: CompactMode; updateCompactMode: (compactMode: CompactMode) => void; + isVisibleSearch?: boolean; + isVisibleFilters?: boolean; + isVisibleDownload?: boolean; + isVisibleCompactMode?: boolean; + isVisiblePagination?: boolean; } function ReactTableComponent(props: ReactTableComponentProps) { @@ -81,6 +86,11 @@ function ReactTableComponent(props: ReactTableComponentProps) { handleResizeColumn, height, isLoading, + isVisibleCompactMode, + isVisibleDownload, + isVisibleFilters, + isVisiblePagination, + isVisibleSearch, nextPageClick, onRowClick, pageNo, @@ -230,6 +240,11 @@ function ReactTableComponent(props: ReactTableComponentProps) { handleResizeColumn={handleResizeColumn} height={height} isLoading={isLoading} + isVisibleCompactMode={isVisibleCompactMode} + isVisibleDownload={isVisibleDownload} + isVisibleFilters={isVisibleFilters} + isVisiblePagination={isVisiblePagination} + isVisibleSearch={isVisibleSearch} nextPageClick={nextPageClick} pageNo={pageNo - 1} pageSize={pageSize || 1} @@ -262,6 +277,11 @@ export default React.memo(ReactTableComponent, (prev, next) => { prev.handleResizeColumn === next.handleResizeColumn && prev.height === next.height && prev.isLoading === next.isLoading && + prev.isVisibleCompactMode === next.isVisibleCompactMode && + prev.isVisibleDownload === next.isVisibleDownload && + prev.isVisibleFilters === next.isVisibleFilters && + prev.isVisiblePagination === next.isVisiblePagination && + prev.isVisibleSearch === next.isVisibleSearch && prev.nextPageClick === next.nextPageClick && prev.onRowClick === next.onRowClick && prev.pageNo === next.pageNo && diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 521ce177cf..7028c1334d 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -263,6 +263,11 @@ const WidgetConfigResponse: WidgetConfigReducerState = { step: 62, status: 75, }, + isVisibleSearch: true, + isVisibleFilters: true, + isVisibleDownload: true, + isVisibleCompactMode: true, + isVisiblePagination: true, version: 1, }, DROP_DOWN_WIDGET: { diff --git a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts index 2273b8ed17..7a2cae150c 100644 --- a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts +++ b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts @@ -679,6 +679,57 @@ export default [ }, ], }, + { + sectionName: "Header options", + children: [ + { + helpText: "Toggle visibility of the search box", + propertyName: "isVisibleSearch", + label: "Search", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + { + helpText: "Toggle visibility of the filters", + propertyName: "isVisibleFilters", + label: "Filters", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + { + helpText: "Toggle visibility of the data download", + propertyName: "isVisibleDownload", + label: "Download", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + { + helpText: "Toggle visibility of the compact mode", + propertyName: "isVisibleCompactMode", + label: "Compact Mode", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + { + helpText: "Toggle visibility of the pagination", + propertyName: "isVisiblePagination", + label: "Pagination", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + validation: VALIDATION_TYPES.BOOLEAN, + }, + ], + }, { sectionName: "Actions", children: [ diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx index baa72efb0e..9996506cdb 100644 --- a/app/client/src/widgets/TableWidget/index.tsx +++ b/app/client/src/widgets/TableWidget/index.tsx @@ -632,6 +632,11 @@ class TableWidget extends BaseWidget { handleResizeColumn={this.handleResizeColumn} height={componentHeight} isLoading={this.props.isLoading} + isVisibleCompactMode={this.props.isVisibleCompactMode} + isVisibleDownload={this.props.isVisibleDownload} + isVisibleFilters={this.props.isVisibleFilters} + isVisiblePagination={this.props.isVisiblePagination} + isVisibleSearch={this.props.isVisibleSearch} multiRowSelection={this.props.multiRowSelection} nextPageClick={this.handleNextPageClick} onCommandClick={this.onCommandClick} From f943d0b330044e6236d1a6f9458b53715e12224e Mon Sep 17 00:00:00 2001 From: Paul Li Date: Tue, 11 May 2021 04:08:31 -0400 Subject: [PATCH 015/119] FEATURE-2111 : Show/Hide Table Controls -- Remove JS convertible -- Set isBindProperty and isTriggerProperty to false --- .../TableWidget/TablePropertyPaneConfig.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts index 7a2cae150c..6230b561f9 100644 --- a/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts +++ b/app/client/src/widgets/TableWidget/TablePropertyPaneConfig.ts @@ -687,45 +687,40 @@ export default [ propertyName: "isVisibleSearch", label: "Search", controlType: "SWITCH", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, + isBindProperty: false, + isTriggerProperty: false, }, { helpText: "Toggle visibility of the filters", propertyName: "isVisibleFilters", label: "Filters", controlType: "SWITCH", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, + isBindProperty: false, + isTriggerProperty: false, }, { helpText: "Toggle visibility of the data download", propertyName: "isVisibleDownload", label: "Download", controlType: "SWITCH", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, + isBindProperty: false, + isTriggerProperty: false, }, { helpText: "Toggle visibility of the compact mode", propertyName: "isVisibleCompactMode", label: "Compact Mode", controlType: "SWITCH", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, + isBindProperty: false, + isTriggerProperty: false, }, { helpText: "Toggle visibility of the pagination", propertyName: "isVisiblePagination", label: "Pagination", controlType: "SWITCH", - isJSConvertible: true, - isBindProperty: true, - isTriggerProperty: true, + isBindProperty: false, + isTriggerProperty: false, validation: VALIDATION_TYPES.BOOLEAN, }, ], From 35e28dcb084ff6488a59a64dbdb33fe4861ada67 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Wed, 12 May 2021 06:35:40 -0400 Subject: [PATCH 016/119] FEATURE-2111 : Show/Hide Table Controls -- PositionedContainer : Add padding-bottom: 42px in case of Table widget --- .../designSystems/appsmith/PositionedContainer.tsx | 9 ++++++++- .../appsmith/TableComponent/Table.tsx | 14 +++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx index 141b14ce28..5430285507 100644 --- a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx +++ b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx @@ -1,6 +1,6 @@ import React, { CSSProperties, ReactNode, useCallback, useMemo } from "react"; import { BaseStyle } from "widgets/BaseWidget"; -import { WIDGET_PADDING } from "constants/WidgetConstants"; +import { WidgetTypes, WIDGET_PADDING } from "constants/WidgetConstants"; import { generateClassName } from "utils/generators"; import styled from "styled-components"; import { useClickOpenPropPane } from "utils/hooks/useClickOpenPropPane"; @@ -43,9 +43,16 @@ export function PositionedContainer(props: PositionedContainerProps) { top: y, height: props.style.componentHeight + (props.style.heightUnit || "px"), width: props.style.componentWidth + (props.style.widthUnit || "px"), +<<<<<<< HEAD padding: padding + "px", zIndex: Layers.positionedWidget, backgroundColor: "inherit", +======= + padding: + props.widgetType === WidgetTypes.TABLE_WIDGET + ? `${padding}px ${padding}px 42px` + : padding + "px", +>>>>>>> FEATURE-2111 : Show/Hide Table Controls }; }, [props.style]); diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx index 9e4ae470c9..962f66c736 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx @@ -166,6 +166,14 @@ export function Table(props: TableProps) { const tableWrapperRef = useRef(null); const tableBodyRef = useRef(null); const tableHeaderWrapperRef = React.createRef(); + + const isHeaderVisible = + props.isVisibleSearch || + props.isVisibleFilters || + props.isVisibleDownload || + props.isVisibleCompactMode || + props.isVisiblePagination; + return ( - {(props.isVisibleSearch || - props.isVisibleFilters || - props.isVisibleDownload || - props.isVisibleCompactMode || - props.isVisiblePagination) && ( + {isHeaderVisible && ( Date: Fri, 14 May 2021 05:39:57 -0400 Subject: [PATCH 017/119] FEATURE-2111 : Show/Hide Table Controls -- Remove extra space when the header options is all hidden --- .../appsmith/PositionedContainer.tsx | 9 +------- .../appsmith/TableComponent/Table.tsx | 10 ++++++-- .../TableComponent/TableStyledWrappers.tsx | 4 +++- app/client/src/widgets/TableWidget/index.tsx | 23 +++++++++++++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx index 5430285507..141b14ce28 100644 --- a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx +++ b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx @@ -1,6 +1,6 @@ import React, { CSSProperties, ReactNode, useCallback, useMemo } from "react"; import { BaseStyle } from "widgets/BaseWidget"; -import { WidgetTypes, WIDGET_PADDING } from "constants/WidgetConstants"; +import { WIDGET_PADDING } from "constants/WidgetConstants"; import { generateClassName } from "utils/generators"; import styled from "styled-components"; import { useClickOpenPropPane } from "utils/hooks/useClickOpenPropPane"; @@ -43,16 +43,9 @@ export function PositionedContainer(props: PositionedContainerProps) { top: y, height: props.style.componentHeight + (props.style.heightUnit || "px"), width: props.style.componentWidth + (props.style.widthUnit || "px"), -<<<<<<< HEAD padding: padding + "px", zIndex: Layers.positionedWidget, backgroundColor: "inherit", -======= - padding: - props.widgetType === WidgetTypes.TABLE_WIDGET - ? `${padding}px ${padding}px 42px` - : padding + "px", ->>>>>>> FEATURE-2111 : Show/Hide Table Controls }; }, [props.style]); diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx index 962f66c736..5707e3a445 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx @@ -179,6 +179,7 @@ export function Table(props: TableProps) { backgroundColor={Colors.ATHENS_GRAY_DARKER} height={props.height} id={`table${props.widgetId}`} + isHeaderVisible={isHeaderVisible} tableSizes={tableSizes} triggerRowSelection={props.triggerRowSelection} width={props.width} @@ -239,7 +240,10 @@ export function Table(props: TableProps) { >
subPage.length && renderEmptyRows( - props.pageSize - subPage.length, + isHeaderVisible + ? props.pageSize - subPage.length + : props.pageSize - subPage.length + 1, props.columns, props.width, subPage, diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx index 25cc63fcd1..d7fa99e09a 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx @@ -14,6 +14,7 @@ export const TableWrapper = styled.div<{ tableSizes: TableSizes; backgroundColor?: Color; triggerRowSelection: boolean; + isHeaderVisible?: boolean; }>` width: 100%; height: 100%; @@ -56,7 +57,8 @@ export const TableWrapper = styled.div<{ overflow: hidden; } .tbody { - height: ${(props) => props.height - 80}px; + height: ${(props) => + props.isHeaderVisible ? props.height - 80 : props.height - 40}px; width: 100%; overflow-y: auto; ${hideScrollbar}; diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx index 9996506cdb..dc6cc2ec33 100644 --- a/app/client/src/widgets/TableWidget/index.tsx +++ b/app/client/src/widgets/TableWidget/index.tsx @@ -570,6 +570,29 @@ class TableWidget extends BaseWidget { }); } } + + // If header options are all invisible + const isPrevHiddenHeaderOptions = + !prevProps.isVisibleSearch && + !prevProps.isVisibleFilters && + !prevProps.isvisibleDownload && + !prevProps.isVisibleCompactMode && + !prevProps.isVisiblePagination; + + const isHiddenHeaderOptions = + !this.props.isVisibleSearch && + !this.props.isVisibleFilters && + !this.props.isvisibleDownload && + !this.props.isVisibleCompactMode && + !this.props.isVisiblePagination; + + if (isHiddenHeaderOptions !== isPrevHiddenHeaderOptions) { + if (isHiddenHeaderOptions === true) { + super.updateWidgetProperty("bottomRow", this.props.bottomRow - 1); + } else { + super.updateWidgetProperty("bottomRow", this.props.bottomRow + 1); + } + } } updateSelectedRowIndex = () => { From 9e0ec2304abe86cdb112cd4323266ec6ef71c8e4 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Mon, 17 May 2021 17:44:32 -0400 Subject: [PATCH 018/119] Revert "FEATURE-2111 : Show/Hide Table Controls" This reverts commit 1d3c2da455d0a58beb4189898464baf5096c1aee. --- .../appsmith/TableComponent/Table.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx index 5707e3a445..200d9c82b1 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx @@ -166,14 +166,6 @@ export function Table(props: TableProps) { const tableWrapperRef = useRef(null); const tableBodyRef = useRef(null); const tableHeaderWrapperRef = React.createRef(); - - const isHeaderVisible = - props.isVisibleSearch || - props.isVisibleFilters || - props.isVisibleDownload || - props.isVisibleCompactMode || - props.isVisiblePagination; - return ( - {isHeaderVisible && ( + {(props.isVisibleSearch || + props.isVisibleFilters || + props.isVisibleDownload || + props.isVisibleCompactMode || + props.isVisiblePagination) && ( Date: Wed, 19 May 2021 05:25:19 -0400 Subject: [PATCH 019/119] FEATURE-2111 : Show/Hide table controls -- Apply some cleanup --- .../appsmith/TableComponent/Table.tsx | 7 ++++++ .../TableWidget/TablePropertyPaneConfig.ts | 1 - app/client/src/widgets/TableWidget/index.tsx | 23 ------------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx index 200d9c82b1..bbc106b27c 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx @@ -166,6 +166,13 @@ export function Table(props: TableProps) { const tableWrapperRef = useRef(null); const tableBodyRef = useRef(null); const tableHeaderWrapperRef = React.createRef(); + const isHeaderVisible = + props.isVisibleSearch || + props.isVisibleFilters || + props.isVisibleDownload || + props.isVisibleCompactMode || + props.isVisiblePagination; + return ( { }); } } - - // If header options are all invisible - const isPrevHiddenHeaderOptions = - !prevProps.isVisibleSearch && - !prevProps.isVisibleFilters && - !prevProps.isvisibleDownload && - !prevProps.isVisibleCompactMode && - !prevProps.isVisiblePagination; - - const isHiddenHeaderOptions = - !this.props.isVisibleSearch && - !this.props.isVisibleFilters && - !this.props.isvisibleDownload && - !this.props.isVisibleCompactMode && - !this.props.isVisiblePagination; - - if (isHiddenHeaderOptions !== isPrevHiddenHeaderOptions) { - if (isHiddenHeaderOptions === true) { - super.updateWidgetProperty("bottomRow", this.props.bottomRow - 1); - } else { - super.updateWidgetProperty("bottomRow", this.props.bottomRow + 1); - } - } } updateSelectedRowIndex = () => { From c1557c187a9f9428594675d5484178e681d2830b Mon Sep 17 00:00:00 2001 From: Paul Li Date: Wed, 19 May 2021 07:02:07 -0400 Subject: [PATCH 020/119] FEATURE-2111 : Show/Hide Table Controls -- Adjust pageSize -- Change column header height -- Restore the logic for renderEmptyRow --- .../appsmith/TableComponent/Table.tsx | 4 +-- .../TableComponent/TableStyledWrappers.tsx | 6 ++-- app/client/src/widgets/TableWidget/index.tsx | 30 ++++++++++++++----- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx index bbc106b27c..c2c8dfe4fd 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx @@ -333,9 +333,7 @@ export function Table(props: TableProps) { })} {props.pageSize > subPage.length && renderEmptyRows( - isHeaderVisible - ? props.pageSize - subPage.length - : props.pageSize - subPage.length + 1, + props.pageSize - subPage.length, props.columns, props.width, subPage, diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx index d7fa99e09a..ba9abc108d 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/TableStyledWrappers.tsx @@ -113,8 +113,10 @@ export const TableWrapper = styled.div<{ } .th { padding: 0 10px 0 0; - height: ${(props) => props.tableSizes.COLUMN_HEADER_HEIGHT}px; - line-height: ${(props) => props.tableSizes.COLUMN_HEADER_HEIGHT}px; + height: ${(props) => + props.isHeaderVisible ? props.tableSizes.COLUMN_HEADER_HEIGHT : 40}px; + line-height: ${(props) => + props.isHeaderVisible ? props.tableSizes.COLUMN_HEADER_HEIGHT : 40}px; background: ${Colors.ATHENS_GRAY_DARKER}; } .td { diff --git a/app/client/src/widgets/TableWidget/index.tsx b/app/client/src/widgets/TableWidget/index.tsx index 9996506cdb..c6a2e76f0d 100644 --- a/app/client/src/widgets/TableWidget/index.tsx +++ b/app/client/src/widgets/TableWidget/index.tsx @@ -613,10 +613,24 @@ class TableWidget extends BaseWidget { }; getPageView() { - const { pageSize, filteredTableData = [] } = this.props; + const { + pageSize, + filteredTableData = [], + isVisibleCompactMode, + isVisibleDownload, + isVisibleFilters, + isVisiblePagination, + isVisibleSearch, + } = this.props; const tableColumns = this.getTableColumns() || []; const transformedData = this.transformData(filteredTableData, tableColumns); const { componentHeight, componentWidth } = this.getComponentDimensions(); + const isVisibleHeaderOptions = + isVisibleCompactMode || + isVisibleDownload || + isVisibleFilters || + isVisiblePagination || + isVisibleSearch; return ( }> @@ -632,17 +646,19 @@ class TableWidget extends BaseWidget { handleResizeColumn={this.handleResizeColumn} height={componentHeight} isLoading={this.props.isLoading} - isVisibleCompactMode={this.props.isVisibleCompactMode} - isVisibleDownload={this.props.isVisibleDownload} - isVisibleFilters={this.props.isVisibleFilters} - isVisiblePagination={this.props.isVisiblePagination} - isVisibleSearch={this.props.isVisibleSearch} + isVisibleCompactMode={isVisibleCompactMode} + isVisibleDownload={isVisibleDownload} + isVisibleFilters={isVisibleFilters} + isVisiblePagination={isVisiblePagination} + isVisibleSearch={isVisibleSearch} multiRowSelection={this.props.multiRowSelection} nextPageClick={this.handleNextPageClick} onCommandClick={this.onCommandClick} onRowClick={this.handleRowClick} pageNo={this.props.pageNo} - pageSize={Math.max(1, pageSize)} + pageSize={ + isVisibleHeaderOptions ? Math.max(1, pageSize) : pageSize + 1 + } prevPageClick={this.handlePrevPageClick} searchKey={this.props.searchText} searchTableData={this.handleSearchTable} From c5133fca87f0367dc216a079a8b3bbfcfcec7dd8 Mon Sep 17 00:00:00 2001 From: Satish Gandham Date: Fri, 21 May 2021 00:55:05 +0530 Subject: [PATCH 021/119] - Patch the blueprint core module with the editabletext performance optimization --- app/client/package.json | 5 +- .../patches/@blueprintjs+core+3.36.0.patch | 22 ++++++++ app/client/yarn.lock | 55 ++++++++++++++++++- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 app/client/patches/@blueprintjs+core+3.36.0.patch diff --git a/app/client/package.json b/app/client/package.json index 02f95af730..3be5115485 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -161,7 +161,8 @@ "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" + "build-storybook": "build-storybook -s public", + "postinstall": "patch-package" }, "resolution": { "jest": "24.8.0" @@ -232,6 +233,8 @@ "mochawesome": "^5.0.0", "mochawesome-report-generator": "^4.1.0", "msw": "^0.28.0", + "patch-package": "^6.4.7", + "postinstall-postinstall": "^2.1.0", "raw-loader": "^4.0.2", "react-docgen-typescript-loader": "^3.6.0", "react-is": "^16.12.0", diff --git a/app/client/patches/@blueprintjs+core+3.36.0.patch b/app/client/patches/@blueprintjs+core+3.36.0.patch new file mode 100644 index 0000000000..3342cbfefc --- /dev/null +++ b/app/client/patches/@blueprintjs+core+3.36.0.patch @@ -0,0 +1,22 @@ +diff --git a/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js b/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js +index 84f03fa..5e5488a 100644 +--- a/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js ++++ b/node_modules/@blueprintjs/core/lib/esm/components/editable-text/editableText.js +@@ -188,7 +188,16 @@ var EditableText = /** @class */ (function (_super) { + if (this.state.isEditing && !prevState.isEditing) { + (_b = (_a = this.props).onEdit) === null || _b === void 0 ? void 0 : _b.call(_a, this.state.value); + } +- this.updateInputDimensions(); ++ // updateInputDimensions is an expensive method. Call it only when the props ++ // it depends on change ++ if (this.state.value !== prevState.value || ++ this.props.alwaysRenderInput !== prevProps.alwaysRenderInput || ++ this.props.maxLines !== prevProps.maxLines || ++ this.props.minLines !== prevProps.minLines || ++ this.props.minWidth !== prevProps.minWidth || ++ this.props.multiline !== prevProps.multiline) { ++ this.updateInputDimensions(); ++ } + }; + EditableText.prototype.renderInput = function (value) { + var _a = this.props, disabled = _a.disabled, maxLength = _a.maxLength, multiline = _a.multiline, type = _a.type, placeholder = _a.placeholder; diff --git a/app/client/yarn.lock b/app/client/yarn.lock index a6af49a55e..f980d2d04a 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -4610,6 +4610,11 @@ version "4.2.2" resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + abab@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -6674,7 +6679,7 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.1" -cross-spawn@6.0.5, cross-spawn@^6.0.0: +cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" dependencies: @@ -8604,6 +8609,13 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-yarn-workspace-root@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd" + integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ== + dependencies: + micromatch "^4.0.2" + flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -8746,7 +8758,7 @@ fs-extra@^0.30.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" -fs-extra@^7.0.0: +fs-extra@^7.0.0, fs-extra@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" dependencies: @@ -11064,6 +11076,13 @@ kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" +klaw-sync@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c" + integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ== + dependencies: + graceful-fs "^4.1.11" + klaw@^1.0.0: version "1.3.1" resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" @@ -12536,6 +12555,14 @@ open@^7.0.0, open@^7.0.2, open@^7.1.0: is-docker "^2.0.0" is-wsl "^2.1.1" +open@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" @@ -12802,6 +12829,25 @@ pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" +patch-package@^6.4.7: + version "6.4.7" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148" + integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ== + dependencies: + "@yarnpkg/lockfile" "^1.1.0" + chalk "^2.4.2" + cross-spawn "^6.0.5" + find-yarn-workspace-root "^2.0.0" + fs-extra "^7.0.1" + is-ci "^2.0.0" + klaw-sync "^6.0.0" + minimist "^1.2.0" + open "^7.4.2" + rimraf "^2.6.3" + semver "^5.6.0" + slash "^2.0.0" + tmp "^0.0.33" + path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" @@ -13619,6 +13665,11 @@ postcss@^8.1.0: nanoid "^3.1.15" source-map "^0.6.1" +postinstall-postinstall@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" + integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== + preact@8.2.9: version "8.2.9" resolved "https://registry.yarnpkg.com/preact/-/preact-8.2.9.tgz#813ba9dd45e5d97c5ea0d6c86d375b3be711cc40" From 4d9aa2bd00266185baa567166f1d7b984d72d282 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Thu, 20 May 2021 15:28:02 -0400 Subject: [PATCH 022/119] FIX-4362 : Unexpected data types in the Recaptcha field for buttons breaks the widget -- Add a guard for invalid googleRecaptcha key --- .../blueprint/ButtonComponent.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx index 44b5338312..daa1459448 100644 --- a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx @@ -180,8 +180,25 @@ function RecaptchaComponent( }); props.onClick && props.onClick(event); } + + // Check if a string is a valid JSON string + const checkValidJson = (inputString: string): boolean => { + try { + JSON.parse(inputString); + return true; + } catch (err) { + return false; + } + }; + + let validGoogleRecaptchaKey = props.googleRecaptchaKey; + + if (validGoogleRecaptchaKey && checkValidJson(validGoogleRecaptchaKey)) { + validGoogleRecaptchaKey = undefined; + } + const status = useScript( - `https://www.google.com/recaptcha/api.js?render=${props.googleRecaptchaKey}`, + `https://www.google.com/recaptcha/api.js?render=${validGoogleRecaptchaKey}`, ); return (
Date: Fri, 21 May 2021 05:32:28 -0400 Subject: [PATCH 023/119] FIX-2358 : Too much spacing between option on multi selection option in Dropdown Widget -- Adjust space between tag items along a flexbox's cross-axis --- .../src/components/designSystems/blueprint/DropdownComponent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx index 2434d3df7a..76a29fc287 100644 --- a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx @@ -188,6 +188,7 @@ const StyledMultiDropDown = styled(MultiDropDown)<{ overflow: hidden; display: flex; height: ${(props) => props.height - WIDGET_PADDING * 2 - 2}px; + align-content: flex-start; } .${Classes.TAG} { From 591f9395f603f42402beba8065eaa86128d745d5 Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 24 May 2021 12:27:33 +0530 Subject: [PATCH 024/119] [ISSUE#4524]Fix: Added a check in else block too and assigned default config --- app/client/src/sagas/WidgetOperationSagas.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index a7e16db98b..ed56207c10 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -1042,7 +1042,9 @@ function* setWidgetDynamicPropertySaga( const { isDynamic, propertyPath, widgetId } = action.payload; const stateWidget: WidgetProps = yield select(getWidget, widgetId); let widget = cloneDeep({ ...stateWidget }); - const propertyValue = _.get(widget, propertyPath); + const defaultConfig: any = WidgetConfigResponse.config[widget.type]; + + let propertyValue = _.get(widget, propertyPath); let dynamicPropertyPathList = getWidgetDynamicPropertyPathList(widget); if (isDynamic) { const keyExists = @@ -1055,6 +1057,12 @@ function* setWidgetDynamicPropertySaga( } widget = set(widget, propertyPath, convertToString(propertyValue)); } else { + const keyExists = + dynamicPropertyPathList.findIndex((path) => path.key === propertyPath) > + -1; + if (keyExists) { + propertyValue = defaultConfig[propertyPath]; + } dynamicPropertyPathList = _.reject(dynamicPropertyPathList, { key: propertyPath, }); From e34a156f18ecc7266c78c387f760121434354d1d Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 24 May 2021 18:27:50 +0530 Subject: [PATCH 025/119] FIX #4573 : added custom fusion chart config to override default message --- app/client/src/mockResponses/WidgetConfigResponse.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 521ce177cf..39a6ca5a91 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -541,6 +541,12 @@ const WidgetConfigResponse: WidgetConfigReducerState = { }, xAxisName: "Last Week", yAxisName: "Total Order Revenue $", + customFusionChartConfig: { + config: { + type: "", + dataSource: {}, + }, + }, }, FORM_BUTTON_WIDGET: { rows: 1 * GRID_DENSITY_MIGRATION_V1, From dcebd1103ebb0de4c2ff0783662e49cf1353122d Mon Sep 17 00:00:00 2001 From: "vicky.bansal@primathon.in" Date: Wed, 26 May 2021 08:47:46 +0530 Subject: [PATCH 026/119] Fix for email parse --- app/client/package.json | 6 +- .../designSystems/blueprint/EmailMatcher.tsx | 81 ---------------- .../designSystems/blueprint/TextComponent.tsx | 14 ++- app/client/yarn.lock | 95 ++----------------- 4 files changed, 17 insertions(+), 179 deletions(-) delete mode 100644 app/client/src/components/designSystems/blueprint/EmailMatcher.tsx diff --git a/app/client/package.json b/app/client/package.json index 79bd92c5e4..6a5914ba33 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -66,13 +66,14 @@ "immer": "^8.0.1", "instantsearch.css": "^7.4.2", "instantsearch.js": "^4.4.1", - "interweave": "^12.1.1", - "interweave-autolink": "^4.0.1", + "interweave": "^12.7.2", + "interweave-autolink": "^4.4.2", "js-sha256": "^0.9.0", "json-fn": "^1.1.1", "lint-staged": "^9.2.5", "localforage": "^1.7.3", "lodash": "^4.17.19", + "lodash-es": "4.17.14", "lodash-move": "^1.1.1", "loglevel": "^1.6.7", "lottie-web": "^5.7.4", @@ -118,7 +119,6 @@ "redux-form": "^8.2.6", "redux-saga": "^1.1.3", "reselect": "^4.0.0", - "lodash-es": "4.17.14", "scroll-into-view-if-needed": "^2.2.26", "shallowequal": "^1.1.0", "showdown": "^1.9.1", diff --git a/app/client/src/components/designSystems/blueprint/EmailMatcher.tsx b/app/client/src/components/designSystems/blueprint/EmailMatcher.tsx deleted file mode 100644 index aa5a84bda3..0000000000 --- a/app/client/src/components/designSystems/blueprint/EmailMatcher.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; -import { ChildrenNode, Matcher, MatchResponse, Node } from "interweave"; -import { EmailProps, Email } from "interweave-autolink"; - -type EmailMatch = Pick; - -interface CombinePatternsOptions { - capture?: boolean; - flags?: string; - join?: string; - match?: string; - nonCapture?: boolean; -} - -function combinePatterns( - patterns: RegExp[], - options: CombinePatternsOptions = {}, -) { - let regex = patterns - .map((pattern) => pattern.source) - .join(options.join || ""); - - if (options.capture) { - regex = `(${regex})`; - } else if (options.nonCapture) { - regex = `(?:${regex})`; - } - - if (options.match) { - regex += options.match; - } - - return new RegExp(regex, options.flags || ""); -} - -const URL_HOST = combinePatterns( - [ - /(?:(?:[a-z0-9](?:[-a-z0-9_]*[a-z0-9])?)\.)*/, // Subdomain - /(?:(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)\.)/, // Domain - /(?:[a-z](?:[-a-z0-9]*[a-z0-9])?)/, // TLD - ], - { - capture: true, - }, -); - -const EMAIL_USERNAME_PART = /[.a-z0-9!#$%&?*+=_{|}~-]+/; - -const VALID_ALNUM_CHARS = /[a-z0-9]/; - -const EMAIL_USERNAME = combinePatterns( - [VALID_ALNUM_CHARS, EMAIL_USERNAME_PART, VALID_ALNUM_CHARS], - { - capture: true, - }, -); - -export const EMAIL_PATTERN = combinePatterns([EMAIL_USERNAME, URL_HOST], { - flags: "i", - join: "@", -}); - -export default class EmailMatcher extends Matcher { - replaceWith(children: ChildrenNode, props: EmailProps): Node { - return React.createElement(Email, props, children); - } - - asTag(): string { - return "a"; - } - - match(string: string): MatchResponse | null { - return this.doMatch(string, EMAIL_PATTERN, (matches) => ({ - email: matches[0], - emailParts: { - host: matches[2], - username: matches[1], - }, - })); - } -} diff --git a/app/client/src/components/designSystems/blueprint/TextComponent.tsx b/app/client/src/components/designSystems/blueprint/TextComponent.tsx index de409d4edf..acc31a26b7 100644 --- a/app/client/src/components/designSystems/blueprint/TextComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/TextComponent.tsx @@ -3,9 +3,8 @@ import { Text } from "@blueprintjs/core"; import styled from "styled-components"; import { ComponentProps } from "components/designSystems/appsmith/BaseComponent"; import { TextAlign } from "widgets/TextWidget"; -import Interweave from "interweave"; -import { UrlMatcher } from "interweave-autolink"; -import EmailMatcher, { EMAIL_PATTERN } from "./EmailMatcher"; +import Interweave, { Node } from "interweave"; +import { UrlMatcher, EmailMatcher } from "interweave-autolink"; import { FontStyleTypes, TextSize, @@ -84,10 +83,6 @@ class TextComponent extends React.Component { textColor, backgroundColor, } = this.props; - let matchers = [new UrlMatcher("url"), new EmailMatcher("email")]; - if (text && EMAIL_PATTERN.test(text)) { - matchers = [new EmailMatcher("email")]; - } return ( { backgroundColor={backgroundColor} className={this.props.isLoading ? "bp3-skeleton" : "bp3-ui-text"} > - + ); diff --git a/app/client/yarn.lock b/app/client/yarn.lock index 4a3012d16b..35a6104aef 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -1627,7 +1627,6 @@ "@babel/runtime@^7.12.5": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d" - integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw== dependencies: regenerator-runtime "^0.13.4" @@ -2308,7 +2307,6 @@ "@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" @@ -2370,7 +2368,6 @@ "@mswjs/cookies@^0.1.4": version "0.1.4" resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.1.4.tgz#85ef872997eea2acd888f21af0b2067224dac244" - integrity sha512-gdtmSv21D4wHTnqF4rrZVX6ye7mQ4nRCTIHYnHBr4SkgoXaiqe3sMvUzXm43+H4PnL0EAKvUTxRVSSXz2xebeg== dependencies: "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" @@ -2378,7 +2375,6 @@ "@mswjs/interceptors@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.8.1.tgz#8ef43a8b7b25c7b9a2bac67b3702167e25e5fc07" - integrity sha512-OI9FYmtURESZG3QDNz4Yt3osy3HY4T3FjlRw+AG4QS1UDdTSZ0tuPFAkp23nGR9ojmbSSj4gSMjf5+R8Oi/qtQ== dependencies: "@open-draft/until" "^1.0.3" debug "^4.3.0" @@ -2416,7 +2412,6 @@ "@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" - integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== "@optimizely/js-sdk-datafile-manager@^0.8.0": version "0.8.0" @@ -2558,7 +2553,6 @@ "@sentry/browser@6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.2.4.tgz#b74757b1f76e7a525e6eaca39668db36db82cb21" - integrity sha512-OV1CQUxNawncpSEcrA+YccOu72rLC0tyYq/Pc4D/ihpfJmvR0o0L8vZYESay55V5lcqnJPFp8IyCJ2bF8IZTsA== dependencies: "@sentry/core" "6.2.4" "@sentry/types" "6.2.4" @@ -2578,7 +2572,6 @@ "@sentry/core@6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.2.4.tgz#613102074208958c580df4e7e06e9aa6b4d5f40c" - integrity sha512-8Z98OTM4wFS2n3T+V8a6cYWHDAk1byWuMb8JquZLdYgR5O1jkSpSFrhksQ+B/wDbVw05VOolSNFJsDTC2D5qXg== dependencies: "@sentry/hub" "6.2.4" "@sentry/minimal" "6.2.4" @@ -2589,7 +2582,6 @@ "@sentry/hub@6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.2.4.tgz#0e25b851dc04b806713c8d878b1e11696ccf47ea" - integrity sha512-dY8Vj3c4oIirNNNzWkJvoRMzjlU8Nw3PJ/IwhdWjiQhj5/oqOzJwJQSMeOKdOGIhArAifr0hSXdy1+tHGEOOdQ== dependencies: "@sentry/types" "6.2.4" "@sentry/utils" "6.2.4" @@ -2598,7 +2590,6 @@ "@sentry/minimal@6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.2.4.tgz#7d8d490b0942e14cde544c0a77550693e4702d97" - integrity sha512-KN+Abbz5CCAceSMvwymSG8GIVPaz4Y/xuY7R7dA8IlzncHaWRQ/Ss0PXjYUWL4YoTlTK6id1AW0i3JMICHMVgw== dependencies: "@sentry/hub" "6.2.4" "@sentry/types" "6.2.4" @@ -2607,7 +2598,6 @@ "@sentry/react@^6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/react/-/react-6.2.4.tgz#7d5a67a6e5f01238bf88e91433841da5180916f0" - integrity sha512-0TqM51HwnAUoDSYyK38Bq/m6xLqWHsOL98Uu4HoMMmx6VXW1xf1UDxhjmIQFfjWfYT5tlld0CoDRfTJJlc82Ow== dependencies: "@sentry/browser" "6.2.4" "@sentry/minimal" "6.2.4" @@ -2619,7 +2609,6 @@ "@sentry/tracing@^6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.2.4.tgz#4af650c180a41b72e130c7b92838fa9d1040792e" - integrity sha512-FNPTd22Q487SVyGM4BXlVeeRwPr9CG0OV8bz+GRHQtpVDhL+zdkGlIJYbxZnrOcdyYNVgLCJUPDHqyv55nhU4A== dependencies: "@sentry/hub" "6.2.4" "@sentry/minimal" "6.2.4" @@ -2630,12 +2619,10 @@ "@sentry/types@6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.2.4.tgz#5974e64f000e6084d92d752e6ca199dc2ef4438f" - integrity sha512-c+vEExoj8H67NPaskTvxJBSAtDWzfFXOmlkicEZPUWbkL+Yxxlbzp1lI8K6GOks56UYMUBUU/fwQvv/34cO96g== "@sentry/utils@6.2.4": version "6.2.4" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.2.4.tgz#ab6a0bdfa2a32428f6b1ee87082d9bd40a226b11" - integrity sha512-lavbb3yQMUleVffmDkPH7X3dlgbXlyiFNmfER+swJ6WRxa4Yq6I8yea2s6maoqnZMhZe+yztn455DPwXIItfCA== dependencies: "@sentry/types" "6.2.4" tslib "^1.9.3" @@ -3400,7 +3387,6 @@ "@testing-library/dom@^7.28.1": version "7.30.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.1.tgz#07b6f3ccd7f1f1e34ab0406932073e2971817f3d" - integrity sha512-RQUvqqq2lxTCOffhSNxpX/9fCoR+nwuQPmG5uhuuEH5KBAzNf2bK3OzBoWjm5zKM78SLjnGRAKt8hRjQA4E46A== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -3427,7 +3413,6 @@ "@testing-library/react@^11.2.5": version "11.2.5" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.5.tgz#ae1c36a66c7790ddb6662c416c27863d87818eb9" - integrity sha512-yEx7oIa/UWLe2F2dqK0FtMF9sJWNXD+2PPtp39BvE0Kh9MJ9Kl0HrZAgEuhUJR+Lx8Di6Xz+rKwSdEPY2UV8ZQ== dependencies: "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1" @@ -3435,7 +3420,6 @@ "@testing-library/user-event@^13.1.1": version "13.1.1" resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.1.1.tgz#1e011de944cf4d2a917cef6c3046c26389943e24" - integrity sha512-B4roX+0mpXKGj8ndd38YoIo3IV9pmTTWxr/2cOke5apTtrNabEUE0KMBccpcAcYlfPcr7uMu+dxeeC3HdXd9qQ== dependencies: "@babel/runtime" "^7.12.5" @@ -3504,15 +3488,9 @@ dependencies: "@types/tern" "*" -"@types/component-emitter@^1.2.10": - version "1.2.10" - resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.10.tgz#ef5b1589b9f16544642e473db5ea5639107ef3ea" - integrity sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg== - "@types/cookie@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.0.tgz#14f854c0f93d326e39da6e3b6f34f7d37513d108" - integrity sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg== "@types/deep-diff@^1.0.0": version "1.0.0" @@ -3582,7 +3560,6 @@ "@types/inquirer@^7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.1.tgz#1f231224e7df11ccfaf4cf9acbcc3b935fea292d" - integrity sha512-osD38QVIfcdgsPCT0V3lD7eH0OFurX71Jft18bZrsVQWVRt6TuxRzlr0GJLrxoHZR2V5ph7/qP8se/dcnI7o0g== dependencies: "@types/through" "*" rxjs "^6.4.0" @@ -3634,7 +3611,6 @@ "@types/js-levenshtein@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz#9541eec4ad6e3ec5633270a3a2b55d981edc44a9" - integrity sha512-14t0v1ICYRtRVcHASzes0v/O+TIeASb8aD55cWF1PidtInhFWSXcmhzhHqGjUWf9SUq1w70cvd1cWKUULubAfQ== "@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.6" @@ -3651,7 +3627,6 @@ "@types/marked@^1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/marked/-/marked-1.2.2.tgz#1f858a0e690247ecf3b2eef576f98f86e8d960d4" - integrity sha512-wLfw1hnuuDYrFz97IzJja0pdVsC0oedtS4QsKH1/inyW9qkLQbXgMUqEQT0MVtUBx3twjWeInUfjQbhBVLECXw== "@types/mdast@^3.0.0": version "3.0.3" @@ -3832,7 +3807,6 @@ "@types/react-window@^1.8.2": version "1.8.2" resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.2.tgz#a5a6b2762ce73ffaab7911ee1397cf645f2459fe" - integrity sha512-gP1xam68Wc4ZTAee++zx6pTdDAH08rAkQrWm4B4F/y6hhmlT9Mgx2q8lTCXnrPHXsr15XjRN9+K2DLKcz44qEQ== dependencies: "@types/react" "*" @@ -3871,7 +3845,6 @@ "@types/set-cookie-parser@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.0.tgz#10cc0446bad372827671a5195fbd14ebce4a9baf" - integrity sha512-w7BFUq81sy7H/0jN0K5cax8MwRN6NOSURpY4YuO4+mOgoicxCZ33BUYz+gyF/sUf7uDl2We2yGJfppxzEXoAXQ== dependencies: "@types/node" "*" @@ -3939,7 +3912,6 @@ "@types/through@*": version "0.0.30" resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" - integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== dependencies: "@types/node" "*" @@ -5414,7 +5386,6 @@ bluebird@^3.3.5, bluebird@^3.5.5, bluebird@^3.7.2: bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== bn.js@^5.1.1: version "5.1.3" @@ -5947,7 +5918,6 @@ chokidar@^3.4.1: chokidar@^3.4.2: version "3.5.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== dependencies: anymatch "~3.1.1" braces "~3.0.2" @@ -6100,7 +6070,6 @@ cliui@^6.0.0: cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" @@ -6151,7 +6120,6 @@ code-point-at@^1.0.0: codemirror@^5.59.2: version "5.59.2" resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.59.2.tgz#ee674d3a4a8d241af38d52afc482625ba7393922" - integrity sha512-/D5PcsKyzthtSy2NNKCyJi3b+htRkoKv3idswR/tR6UAvMNKA7SrmyZy6fOONJxSRs1JlUWEDAbxqfdArbK8iA== collapse-white-space@^1.0.2: version "1.0.6" @@ -6275,7 +6243,6 @@ compression@^1.7.4: compute-scroll-into-view@^1.0.16: version "1.0.16" resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz#5b7bf4f7127ea2c19b750353d7ce6776a90ee088" - integrity sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ== concat-map@0.0.1: version "0.0.1" @@ -6358,7 +6325,6 @@ cookie@0.4.0: cookie@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== copy-concurrently@^1.0.0: version "1.0.5" @@ -6407,7 +6373,6 @@ core-js@^3.0.1, core-js@^3.0.4, core-js@^3.6.5: core-js@^3.9.1: version "3.9.1" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae" - integrity sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -6696,7 +6661,6 @@ cssesc@^3.0.0: cssfontparser@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" - integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M= cssnano-preset-default@^4.0.7: version "4.0.7" @@ -6920,10 +6884,9 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -debug@^4.3.0, debug@~4.3.1: +debug@^4.3.0: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: ms "2.1.2" @@ -7186,7 +7149,6 @@ doctypes@^1.1.0: dom-accessibility-api@^0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" - integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== dom-converter@^0.2: version "0.2.0" @@ -7356,7 +7318,6 @@ element-resize-detector@^1.2.1: elliptic@^6.5.3: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: bn.js "^4.11.9" brorand "^1.1.0" @@ -7880,7 +7841,6 @@ events@^3.0.0: events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== eventsource@^1.0.7: version "1.0.7" @@ -8536,7 +8496,6 @@ fsevents@^2.1.2, fsevents@^2.1.3, fsevents@~2.1.1, fsevents@~2.1.2: fsevents@~2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== fstream@^1.0.0, fstream@^1.0.12: version "1.0.12" @@ -8846,7 +8805,6 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 graphql@^15.4.0: version "15.5.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5" - integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA== growl@1.10.5: version "1.10.5" @@ -9032,7 +8990,6 @@ he@1.2.0, he@^1.2.0: headers-utils@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/headers-utils/-/headers-utils-3.0.2.tgz#dfc65feae4b0e34357308aefbcafa99c895e59ef" - integrity sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ== hex-color-regex@^1.1.0: version "1.1.0" @@ -9496,20 +9453,15 @@ interpret@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" -interweave-autolink@^4.0.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/interweave-autolink/-/interweave-autolink-4.2.1.tgz#b875e67970512484e47666adf952b9025743bd9d" - dependencies: - "@types/react" "*" - prop-types "^15.7.2" +interweave-autolink@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/interweave-autolink/-/interweave-autolink-4.4.2.tgz#5ef9272361c78cb39180207a9926e034dfaca4e3" -interweave@^12.1.1: - version "12.5.0" - resolved "https://registry.yarnpkg.com/interweave/-/interweave-12.5.0.tgz#c1c6cbda55e3d2864c94eeed8e86f4159111f493" +interweave@^12.7.2: + version "12.7.2" + resolved "https://registry.yarnpkg.com/interweave/-/interweave-12.7.2.tgz#c42c74a512202e2cd05165d94e6590c095a7132b" dependencies: - "@types/react" "*" escape-html "^1.0.3" - prop-types "^15.7.2" invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" @@ -10042,7 +9994,6 @@ jake@^10.6.1: jest-canvas-mock@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.3.1.tgz#9535d14bc18ccf1493be36ac37dd349928387826" - integrity sha512-5FnSZPrX3Q2ZfsbYNE3wqKR3+XorN8qFzDzB5o0golWgt6EOX1+emBnpOc9IAQ+NXFj8Nzm3h7ZdE/9H0ylBcg== dependencies: cssfontparser "^1.2.1" moo-color "^1.0.2" @@ -10471,7 +10422,6 @@ jest-util@^24.9.0: jest-util@^26.1.0: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== dependencies: "@jest/types" "^26.6.2" "@types/node" "*" @@ -10563,7 +10513,6 @@ js-base64@^2.1.8: js-levenshtein@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" - integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== js-sha256@^0.9.0: version "0.9.0" @@ -11024,12 +10973,10 @@ locate-path@^5.0.0: lodash-es@4.17.14: version "4.17.14" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.14.tgz#12a95a963cc5955683cee3b74e85458954f37ecc" - integrity sha512-7zchRrGa8UZXjD/4ivUWP1867jDkhzTG2c/uj739utSd7O/pFFdxspCemIFKEEjErbcqRzn8nKnGsi7mvTgRPA== lodash-move@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/lodash-move/-/lodash-move-1.1.1.tgz#59f76e0f1ac57e6d8683f531bec07c5b6ea4e348" - integrity sha1-WfduDxrFfm2Gg/UxvsB8W26k40g= dependencies: lodash "^4.6.1" @@ -11106,20 +11053,14 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@4.x: +lodash@4.x, lodash@^4.6.1: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== "lodash@>=3.5 <5", lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.16.2, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5, lodash@~4.17.10: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" -lodash@^4.6.1: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - log-symbols@3.0.0, log-symbols@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" @@ -11285,7 +11226,6 @@ markdown-to-jsx@^6.10.3, markdown-to-jsx@^6.11.4: marked@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.0.tgz#9662bbcb77ebbded0662a7be66ff929a8611cee5" - integrity sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q== marker-clusterer-plus@^2.1.4: version "2.1.4" @@ -11738,7 +11678,6 @@ moment-timezone@*, moment-timezone@^0.5.27, moment-timezone@^0.5.31: moo-color@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.2.tgz#837c40758d2d58763825d1359a84e330531eca64" - integrity sha512-5iXz5n9LWQzx/C2WesGFfpE6RLamzdHwsn3KpfzShwbfIqs7stnoEpaNErf/7+3mbxwZ4s8Foq7I0tPxw7BWHg== dependencies: color-name "^1.1.4" @@ -11768,7 +11707,6 @@ ms@2.1.2, ms@^2.1.1: msw@^0.28.0: version "0.28.0" resolved "https://registry.yarnpkg.com/msw/-/msw-0.28.0.tgz#abed17416f59241a2100fe6c8740cc1c9a32339b" - integrity sha512-Hh+dPp613tethIFwNg90lvAzrW9T0U39D6AYzV8qIOAWskP49CErrqVWZnmPDQC87o69GzZ9Hl3RGz/65mms3A== dependencies: "@mswjs/cookies" "^0.1.4" "@mswjs/interceptors" "^0.8.0" @@ -11965,7 +11903,6 @@ node-libs-browser@^2.2.1: node-match-path@^0.6.1: version "0.6.2" resolved "https://registry.yarnpkg.com/node-match-path/-/node-match-path-0.6.2.tgz#29a05ed7eda4d325f29d7abb088c12bbf1578e87" - integrity sha512-2VYsUKiovaCZDq1t/3kEqh09743H91WE6B3RzSdjsKh+S/a5z+LQoujMI1JI/RYXqNKFvoqMfye1H0g3Dg9u+g== node-modules-regexp@^1.0.0: version "1.0.0" @@ -12567,7 +12504,6 @@ path-to-regexp@^1.7.0: path-to-regexp@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" - integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg== path-type@^1.0.0: version "1.1.0" @@ -13405,7 +13341,6 @@ pretty-format@^26.6.0, pretty-format@^26.6.1: pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== dependencies: "@jest/types" "^26.6.2" ansi-regex "^5.0.0" @@ -13419,7 +13354,6 @@ pretty-hrtime@^1.0.3: prismjs@^1.23.0, prismjs@^1.8.4: version "1.23.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33" - integrity sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA== optionalDependencies: clipboard "^2.0.0" @@ -14390,7 +14324,6 @@ react-window@^1.8.2: react-window@^1.8.6: version "1.8.6" resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.6.tgz#d011950ac643a994118632665aad0c6382e2a112" - integrity sha512-8VwEEYyjz6DCnGBsd+MgkD0KJ2/OXFULyDtorIiTz+QzwoP94tBoA7CnbtyXMm+cCeAUER5KJcPtWl9cpKbOBg== dependencies: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" @@ -15179,7 +15112,6 @@ scriptjs@^2.5.8: scroll-into-view-if-needed@^2.2.26: version "2.2.26" resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.26.tgz#e4917da0c820135ff65ad6f7e4b7d7af568c4f13" - integrity sha512-SQ6AOKfABaSchokAmmaxVnL9IArxEnLEX9j4wAZw+x4iUTb40q7irtHG3z4GtAWz5veVZcCnubXDBRyLVQaohw== dependencies: compute-scroll-into-view "^1.0.16" @@ -15223,7 +15155,6 @@ semver@7.3.2: semver@7.x: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== dependencies: lru-cache "^6.0.0" @@ -15309,7 +15240,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: set-cookie-parser@^2.4.6: version "2.4.8" resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" - integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" @@ -15720,7 +15650,6 @@ static-extend@^0.1.1: statuses@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== stdout-stream@^1.4.0: version "1.4.1" @@ -15773,12 +15702,10 @@ stream-shift@^1.0.0: strict-event-emitter@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.1.0.tgz#fd742c1fb7e3852f0b964ecdae2d7666a6fb7ef8" - integrity sha512-8hSYfU+WKLdNcHVXJ0VxRXiPESalzRe7w1l8dg9+/22Ry+iZQUoQuoJ27R30GMD1TiyYINWsIEGY05WrskhSKw== strict-event-emitter@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz#78e2f75dc6ea502e5d8a877661065a1e2deedecd" - integrity sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w== dependencies: events "^3.3.0" @@ -16456,7 +16383,6 @@ ts-dedent@^1.1.0: ts-jest@^26.5.4: version "26.5.4" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.4.tgz#207f4c114812a9c6d5746dd4d1cdf899eafc9686" - integrity sha512-I5Qsddo+VTm94SukBJ4cPimOoFZsYTeElR2xy6H2TOVs+NsvgYglW8KuQgKoApOKuaU/Ix/vrF9ebFZlb5D2Pg== dependencies: bs-logger "0.x" buffer-from "1.x" @@ -17476,7 +17402,6 @@ wrap-ansi@^6.2.0: wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" string-width "^4.1.0" @@ -17546,12 +17471,10 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" - integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== y18n@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18" - integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg== yallist@^2.1.2: version "2.1.2" @@ -17579,7 +17502,6 @@ yargs-parser@13.1.2, yargs-parser@^13.1.2: yargs-parser@20.x, yargs-parser@^20.2.2: version "20.2.7" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a" - integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw== yargs-parser@^15.0.1: version "15.0.1" @@ -17653,7 +17575,6 @@ yargs@^15.4.1: yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" escalade "^3.1.1" From 7d7dad4b87c0531d4fbaf178f873911e82a0392a Mon Sep 17 00:00:00 2001 From: "vicky.bansal@primathon.in" Date: Wed, 26 May 2021 09:04:11 +0530 Subject: [PATCH 027/119] Updated yarn --- app/client/yarn.lock | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/client/yarn.lock b/app/client/yarn.lock index cf2c98723c..f91fe22c24 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -3584,7 +3584,6 @@ "@types/emoji-mart@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/emoji-mart/-/emoji-mart-3.0.4.tgz#418868330c13b510a7b4b86b1ee9904291daeec1" - integrity sha512-Uqegqi54lXzz1qlDnLYEbFlUXY46auTVwbeOO8mj+9maGu2WBx/lGnmLKg7WS4CC5lB8Q6ch8K5VPBR9bDjWgg== dependencies: "@types/react" "*" @@ -3935,7 +3934,6 @@ "@types/resize-observer-browser@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.5.tgz#36d897708172ac2380cd486da7a3daf1161c1e23" - integrity sha512-8k/67Z95Goa6Lznuykxkfhq9YU3l1Qe6LNZmwde1u7802a3x8v44oq0j91DICclxatTr0rNnhXx7+VTIetSrSQ== "@types/resolve@0.0.8": version "0.0.8" @@ -4302,7 +4300,6 @@ "@uppy/image-editor@^0.2.4": version "0.2.4" resolved "https://registry.yarnpkg.com/@uppy/image-editor/-/image-editor-0.2.4.tgz#af49fb1f7ab94ed63dc6d8aa04e64b6477baea7b" - integrity sha512-LTkT536CuJwurIhpn7Gj1p/F+U+ULYYXMxz5XMVijSlDLkfoi7IF6E3kbt4N4raUkyBtGgtmd2Ka7oUvoEFtxA== dependencies: "@uppy/utils" "^3.5.0" cropperjs "1.5.7" @@ -4391,7 +4388,6 @@ "@uppy/utils@^3.5.0": version "3.5.0" resolved "https://registry.yarnpkg.com/@uppy/utils/-/utils-3.5.0.tgz#0822f68e8a4c7e833a7ca9ddf387adbd3ea0535c" - integrity sha512-Hhe8e/ArclSascuRjpwWtiEqAcykh9Qb/tZrA6cw+L4QjoYhxaxnOZOQoPG8LOz+zZS/DgQyF7IjWp+oiHDzag== dependencies: abortcontroller-polyfill "^1.4.0" lodash.throttle "^4.1.1" @@ -6657,7 +6653,6 @@ create-react-context@0.3.0, create-react-context@^0.3.0: cropperjs@1.5.7: version "1.5.7" resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.7.tgz#b65019725bae1c6285e881fb661b2141fa57025b" - integrity sha512-sGj+G/ofKh+f6A4BtXLJwtcKJgMUsXYVUubfTo9grERiDGXncttefmue/fyQFvn8wfdyoD1KhDRYLfjkJFl0yw== cross-fetch@^3.0.4: version "3.1.4" @@ -7100,7 +7095,7 @@ debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -debug@^4.3.0: +debug@^4.3.0, debug@~4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" dependencies: @@ -7566,7 +7561,6 @@ emittery@^0.7.1: emoji-mart@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-3.0.1.tgz#9ce86706e02aea0506345f98464814a662ca54c6" - integrity sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg== dependencies: "@babel/runtime" "^7.0.0" prop-types "^15.6.0" @@ -7947,7 +7941,6 @@ eslint-plugin-react@^7.21.5: eslint-plugin-sort-destructure-keys@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/eslint-plugin-sort-destructure-keys/-/eslint-plugin-sort-destructure-keys-1.3.5.tgz#c6f45c3e58d4435564025a6ca5f4a838010800fd" - integrity sha512-JmVpidhDsLwZsmRDV7Tf/vZgOAOEQGkLtwToSvX5mD8fuWYS/xkgMRBsalW1fGlc8CgJJwnzropt4oMQ7YCHLg== dependencies: natural-compare-lite "^1.4.0" @@ -12121,7 +12114,6 @@ native-url@^0.2.6: natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha1-F7CVgZiJef3a/gIB6TG6kzyWy7Q= natural-compare@^1.4.0: version "1.4.0" From 312ea0013854c0391efbd2a372b27c58fe877f18 Mon Sep 17 00:00:00 2001 From: "vicky.bansal@primathon.in" Date: Wed, 26 May 2021 10:22:08 +0530 Subject: [PATCH 028/119] Increase width of default table widget so that scrollbars are not required by default --- app/client/src/mockResponses/WidgetConfigResponse.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 521ce177cf..488afe4c4e 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -148,7 +148,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = { }, TABLE_WIDGET: { rows: 7 * GRID_DENSITY_MIGRATION_V1, - columns: 8 * GRID_DENSITY_MIGRATION_V1, + columns: 9 * GRID_DENSITY_MIGRATION_V1, label: "Data", widgetName: "Table", searchKey: "", From bd5ca7011ca43ca625eec758a417fa68ea8b0712 Mon Sep 17 00:00:00 2001 From: "vicky.bansal@primathon.in" Date: Wed, 26 May 2021 16:57:01 +0530 Subject: [PATCH 029/119] Removed unused import --- .../src/components/designSystems/blueprint/TextComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/components/designSystems/blueprint/TextComponent.tsx b/app/client/src/components/designSystems/blueprint/TextComponent.tsx index 6719e37b69..2fe019bd39 100644 --- a/app/client/src/components/designSystems/blueprint/TextComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/TextComponent.tsx @@ -3,7 +3,7 @@ import { Text } from "@blueprintjs/core"; import styled from "styled-components"; import { ComponentProps } from "components/designSystems/appsmith/BaseComponent"; import { TextAlign } from "widgets/TextWidget"; -import Interweave, { Node } from "interweave"; +import Interweave from "interweave"; import { UrlMatcher, EmailMatcher } from "interweave-autolink"; import { FontStyleTypes, From 218ad0455359f24fdb8c1a1caf68056b013ce069 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 26 May 2021 17:43:25 +0530 Subject: [PATCH 030/119] FIX #4524 : revert added check in else block and add handle boolean validation in validator --- app/client/src/sagas/WidgetOperationSagas.tsx | 9 +-------- app/client/src/workers/validations.ts | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index ed56207c10..7669036f94 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -1042,9 +1042,8 @@ function* setWidgetDynamicPropertySaga( const { isDynamic, propertyPath, widgetId } = action.payload; const stateWidget: WidgetProps = yield select(getWidget, widgetId); let widget = cloneDeep({ ...stateWidget }); - const defaultConfig: any = WidgetConfigResponse.config[widget.type]; + const propertyValue = _.get(widget, propertyPath); - let propertyValue = _.get(widget, propertyPath); let dynamicPropertyPathList = getWidgetDynamicPropertyPathList(widget); if (isDynamic) { const keyExists = @@ -1057,12 +1056,6 @@ function* setWidgetDynamicPropertySaga( } widget = set(widget, propertyPath, convertToString(propertyValue)); } else { - const keyExists = - dynamicPropertyPathList.findIndex((path) => path.key === propertyPath) > - -1; - if (keyExists) { - propertyValue = defaultConfig[propertyPath]; - } dynamicPropertyPathList = _.reject(dynamicPropertyPathList, { key: propertyPath, }); diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index 4a76c0a60e..d9995bbe09 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -155,7 +155,7 @@ export const VALIDATORS: Record = { if (!isValid) { return { isValid: isValid, - parsed: parsed, + parsed: !!parsed, message: `${WIDGET_TYPE_VALIDATION_ERROR} "boolean"`, }; } From c014485268e211a82ab4f07fede45fe53e783789 Mon Sep 17 00:00:00 2001 From: bhavin Date: Thu, 27 May 2021 16:27:10 +0530 Subject: [PATCH 031/119] added string check on options prop --- app/client/src/widgets/DropdownWidget.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/client/src/widgets/DropdownWidget.tsx b/app/client/src/widgets/DropdownWidget.tsx index e0e4dd8782..e242ed6e6b 100644 --- a/app/client/src/widgets/DropdownWidget.tsx +++ b/app/client/src/widgets/DropdownWidget.tsx @@ -149,7 +149,11 @@ class DropdownWidget extends BaseWidget { } getPageView() { - const options = this.props.options || []; + const options = this.props.options + ? typeof this.props.options === "string" + ? [] + : this.props.options + : []; const selectedIndex = _.findIndex(this.props.options, { value: this.props.selectedOptionValue, }); From 9de9b1eb3075202978d034e7c95af7f5a72f83f1 Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 28 May 2021 14:49:36 +0530 Subject: [PATCH 032/119] FIX #4573 : added sample custom fusion chart config and updated data shape to override default message --- .../mockResponses/WidgetConfigResponse.tsx | 46 +++++++++++++++++-- app/client/src/widgets/ChartWidget/index.tsx | 2 +- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 39a6ca5a91..231fd137da 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -542,9 +542,49 @@ const WidgetConfigResponse: WidgetConfigReducerState = { xAxisName: "Last Week", yAxisName: "Total Order Revenue $", customFusionChartConfig: { - config: { - type: "", - dataSource: {}, + type: "column2d", + dataSource: { + chart: { + caption: "Monthly revenue for last year", + subCaption: "Harry's SuperMart", + xAxisName: "Month", + yAxisName: "Revenues (In USD)", + numberPrefix: "$", + theme: "fusion", + }, + data: [ + { + label: "Jan", + value: "420000", + }, + { + label: "Feb", + value: "810000", + }, + { + label: "Mar", + value: "720000", + }, + { + label: "Apr", + value: "550000", + }, + { + label: "May", + value: "910000", + }, + ], + trendlines: [ + { + line: [ + { + startvalue: "700000", + valueOnRight: "1", + displayvalue: "Monthly Target", + }, + ], + }, + ], }, }, }, diff --git a/app/client/src/widgets/ChartWidget/index.tsx b/app/client/src/widgets/ChartWidget/index.tsx index 84432435a9..b06ec6153c 100644 --- a/app/client/src/widgets/ChartWidget/index.tsx +++ b/app/client/src/widgets/ChartWidget/index.tsx @@ -92,7 +92,7 @@ export interface ChartData { export interface ChartWidgetProps extends WidgetProps, WithMeta { chartType: ChartType; chartData: AllChartData; - customFusionChartConfig: { config: CustomFusionChartConfig }; + customFusionChartConfig: CustomFusionChartConfig; xAxisName: string; yAxisName: string; chartName: string; From ad427c86b2241b07780777ec49f69046d73fdfe3 Mon Sep 17 00:00:00 2001 From: Yash Date: Fri, 28 May 2021 21:42:47 +0530 Subject: [PATCH 033/119] FIX #3648 : updated css to display toast message properly on deleting RTE --- app/client/src/components/ads/Toast.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/client/src/components/ads/Toast.tsx b/app/client/src/components/ads/Toast.tsx index 34154c8fb8..d0a78223e3 100644 --- a/app/client/src/components/ads/Toast.tsx +++ b/app/client/src/components/ads/Toast.tsx @@ -97,6 +97,7 @@ const ToastBody = styled.div<{ color: ${props.theme.colors.toast.undo}; line-height: 18px; font-weight: 600; + white-space: nowrap } ` : null} From 2b9cc622cc35c6995204dc59e98d0ae1139c4d38 Mon Sep 17 00:00:00 2001 From: bhavin Date: Tue, 1 Jun 2021 09:36:47 +0530 Subject: [PATCH 034/119] replaced string check with array check --- app/client/src/widgets/DropdownWidget.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/client/src/widgets/DropdownWidget.tsx b/app/client/src/widgets/DropdownWidget.tsx index e242ed6e6b..933314d2a4 100644 --- a/app/client/src/widgets/DropdownWidget.tsx +++ b/app/client/src/widgets/DropdownWidget.tsx @@ -149,11 +149,7 @@ class DropdownWidget extends BaseWidget { } getPageView() { - const options = this.props.options - ? typeof this.props.options === "string" - ? [] - : this.props.options - : []; + const options = _.isArray(this.props.options) ? this.props.options : []; const selectedIndex = _.findIndex(this.props.options, { value: this.props.selectedOptionValue, }); From 53598e80277af046a1ce80bfd865fd9950207758 Mon Sep 17 00:00:00 2001 From: bhavin Date: Tue, 1 Jun 2021 14:50:13 +0530 Subject: [PATCH 035/119] added onclose property and handlers on ModalWidget --- .../blueprint/ModalComponent.tsx | 9 ++++++ .../ActionConstants.tsx | 1 + app/client/src/widgets/ModalWidget.tsx | 29 +++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/app/client/src/components/designSystems/blueprint/ModalComponent.tsx b/app/client/src/components/designSystems/blueprint/ModalComponent.tsx index acf3aa652d..e3dd12ead4 100644 --- a/app/client/src/components/designSystems/blueprint/ModalComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/ModalComponent.tsx @@ -56,6 +56,7 @@ const Content = styled.div<{ export type ModalComponentProps = { isOpen: boolean; onClose: (e: any) => void; + onModalClose?: () => void; children: ReactNode; width?: number; className?: string; @@ -76,6 +77,14 @@ export function ModalComponent(props: ModalComponentProps) { const modalContentRef: RefObject = useRef( null, ); + useEffect(() => { + return () => { + // handle modal close events when this component unmounts + // will be called in all cases :- + // escape key press, click out side, close click from other btn widget + if (props.onModalClose) props.onModalClose(); + }; + }, []); useEffect(() => { if (!props.scrollContents) { modalContentRef.current?.scrollTo({ top: 0, behavior: "smooth" }); diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx index 7e63eb2edc..6f8dc9c1dc 100644 --- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx +++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx @@ -56,6 +56,7 @@ export enum EventType { ON_HOVER = "ON_HOVER", ON_TOGGLE = "ON_TOGGLE", ON_LOAD = "ON_LOAD", + ON_MODAL_CLOSE = "ON_MODAL_CLOSE", ON_TEXT_CHANGE = "ON_TEXT_CHANGE", ON_SUBMIT = "ON_SUBMIT", ON_CHECK_CHANGE = "ON_CHECK_CHANGE", diff --git a/app/client/src/widgets/ModalWidget.tsx b/app/client/src/widgets/ModalWidget.tsx index a740c9eaec..6fae51bd08 100644 --- a/app/client/src/widgets/ModalWidget.tsx +++ b/app/client/src/widgets/ModalWidget.tsx @@ -3,6 +3,7 @@ import React, { ReactNode } from "react"; import { connect } from "react-redux"; import { ReduxActionTypes } from "constants/ReduxActionConstants"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import WidgetFactory from "utils/WidgetFactory"; import ModalComponent from "components/designSystems/blueprint/ModalComponent"; import { @@ -70,6 +71,20 @@ export class ModalWidget extends BaseWidget { }, ], }, + { + sectionName: "Actions", + children: [ + { + helpText: "Triggers an action when the modal is closed", + propertyName: "onClose", + label: "onClose", + controlType: "ACTION_SELECTOR", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + }, + ], + }, ]; } static defaultProps = { @@ -99,6 +114,18 @@ export class ModalWidget extends BaseWidget { return WidgetFactory.createWidget(childWidgetData, this.props.renderMode); }; + onModalClose = () => { + if (this.props.onClose) { + super.executeAction({ + triggerPropertyName: "onClose", + dynamicString: this.props.onClose, + event: { + type: EventType.ON_MODAL_CLOSE, + }, + }); + } + }; + closeModal = (e: any) => { this.props.showPropertyPane(undefined); // TODO(abhinav): Create a static property with is a map of widget properties @@ -124,6 +151,7 @@ export class ModalWidget extends BaseWidget { height={MODAL_SIZE[this.props.size].height} isOpen={!!this.props.isVisible} onClose={this.closeModal} + onModalClose={this.onModalClose} scrollContents={!!this.props.shouldScrollContents} width={this.getModalWidth()} > @@ -159,6 +187,7 @@ export interface ModalWidgetProps extends WidgetProps, WithMeta { canEscapeKeyClose?: boolean; shouldScrollContents?: boolean; size: string; + onClose: string; mainContainer: WidgetProps; } From a88915371485c217229a2060e1acf66e350e0bf9 Mon Sep 17 00:00:00 2001 From: bhavin Date: Tue, 1 Jun 2021 16:01:32 +0530 Subject: [PATCH 036/119] fixing docs and expectation strings on custom chart --- .../components/propertyControls/CustomFusionChartControl.tsx | 2 +- app/client/src/widgets/ChartWidget/propertyConfig.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/client/src/components/propertyControls/CustomFusionChartControl.tsx b/app/client/src/components/propertyControls/CustomFusionChartControl.tsx index 6b741a1330..bcf39b84f7 100644 --- a/app/client/src/components/propertyControls/CustomFusionChartControl.tsx +++ b/app/client/src/components/propertyControls/CustomFusionChartControl.tsx @@ -3,7 +3,7 @@ import InputTextControl, { InputText } from "./InputTextControl"; class CustomFusionChartControl extends InputTextControl { render() { - const expected = "{\n type: string,\n dataSource: Object\n}"; + const expected = '{\n "type": string,\n "dataSource": Object\n}'; const { dataTreePath, label, placeholderText, propertyValue } = this.props; return ( Date: Tue, 1 Jun 2021 17:37:16 +0530 Subject: [PATCH 037/119] - Change useEffect hook to use deep cehck for modifiers instead of refernece equality - Moemoize getThemeDetails --- app/client/src/pages/Editor/Popper.tsx | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/app/client/src/pages/Editor/Popper.tsx b/app/client/src/pages/Editor/Popper.tsx index 689d659faa..1507879d3d 100644 --- a/app/client/src/pages/Editor/Popper.tsx +++ b/app/client/src/pages/Editor/Popper.tsx @@ -1,14 +1,13 @@ -import React, { useRef, useEffect } from "react"; -import styled, { ThemeProvider } from "styled-components"; -import { createPortal } from "react-dom"; -import PopperJS, { Placement, PopperOptions } from "popper.js"; -import { noop } from "utils/AppsmithUtils"; -import { draggableElement } from "./utils"; import { ReactComponent as DragHandleIcon } from "assets/icons/ads/app-icons/draghandler.svg"; import { Colors } from "constants/Colors"; -import { getThemeDetails, ThemeMode } from "selectors/themeSelectors"; +import PopperJS, { Placement, PopperOptions } from "popper.js"; +import React, { useEffect, useMemo, useRef } from "react"; +import { createPortal } from "react-dom"; import { AppState } from "reducers"; -import { useSelector } from "react-redux"; +import { getThemeDetails, ThemeMode } from "selectors/themeSelectors"; +import styled, { ThemeProvider } from "styled-components"; +import { noop } from "utils/AppsmithUtils"; +import { draggableElement } from "./utils"; export type PopperProps = { zIndex: number; @@ -60,9 +59,13 @@ export default (props: PopperProps) => { onPositionChange = noop, themeMode = props.themeMode || ThemeMode.LIGHT, } = props; - const popperTheme = useSelector((state: AppState) => - getThemeDetails(state, themeMode), + // Meomoizing to avoid rerender of draggable icon. + // What is the cost of memoizing? + const popperTheme = useMemo( + () => getThemeDetails({} as AppState, themeMode), + [themeMode], ); + useEffect(() => { const parentElement = props.targetNode && props.targetNode.parentElement; if ( @@ -133,7 +136,7 @@ export default (props: PopperProps) => { }, [ props.targetNode, props.isOpen, - props.modifiers, + JSON.stringify(props.modifiers), props.placement, disablePopperEvents, ]); From 23bfcc706da1953e7ea79f34bb7b1ca967eb0112 Mon Sep 17 00:00:00 2001 From: Yash Date: Tue, 1 Jun 2021 20:27:58 +0530 Subject: [PATCH 038/119] FIX #3899 : added supported custom chart types list, validate chart type and fix type error by fusioncharts --- .../src/constants/CustomChartConstants.ts | 64 ++++++++++++++ app/client/src/workers/validations.test.ts | 87 ++----------------- app/client/src/workers/validations.ts | 14 +++ 3 files changed, 87 insertions(+), 78 deletions(-) create mode 100644 app/client/src/constants/CustomChartConstants.ts diff --git a/app/client/src/constants/CustomChartConstants.ts b/app/client/src/constants/CustomChartConstants.ts new file mode 100644 index 0000000000..fa11c207e9 --- /dev/null +++ b/app/client/src/constants/CustomChartConstants.ts @@ -0,0 +1,64 @@ +export const CUSTOM_CHART_TYPES = [ + "column2d", + "column3d", + "line", + "area", + "bar2d", + "bar3d", + "pie2d", + "pie3d", + "doughnut2d", + "doughnut3d", + "pareto2d", + "pareto3d", + "scrollcombidy2d", + "scrollcombi2d", + "scrollstackedcolumn2d", + "scrollmsstackedcolumn2d", + "scrollmsstackedcolumn2dlinedy", + "scrollstackedbar2d", + "scrollarea2d", + "scrollline2d", + "scrollcolumn2d", + "scrollbar2d", + "bubble", + "scatter", + "msstackedcolumn2d", + "stackedarea2d", + "stackedbar3d", + "stackedbar2d", + "stackedcolumn3d", + "stackedcolumn2d", + "msstackedcolumn2dlinedy", + "stackedcolumn3dlinedy", + "mscolumn3dlinedy", + "mscombidy2d", + "mscombidy3d", + "stackedcolumn3dline", + "stackedcolumn2dline", + "mscolumnline3d", + "mscombi3d", + "mscombi2d", + "marimekko", + "msarea", + "msbar3d", + "msbar2d", + "msline", + "mscolumn3d", + "mscolumn2d", + "spline", + "splinearea", + "msspline", + "mssplinedy", + "mssplinearea", + "stackedcolumn2dlinedy", + "stackedarea2dlinedy", +]; + +export const CUSTOM_CHART_DEFAULT_PARSED = { + type: "", + dataSource: { + chart: {}, + data: [], + }, +}; diff --git a/app/client/src/workers/validations.test.ts b/app/client/src/workers/validations.test.ts index 4956ddaed4..b1053b5bd2 100644 --- a/app/client/src/workers/validations.test.ts +++ b/app/client/src/workers/validations.test.ts @@ -126,7 +126,7 @@ describe("Chart Custom Config validator", () => { const cases = [ { input: { - type: "area2d", + type: "area", dataSource: { chart: { caption: "Countries With Most Oil Reserves [2017-18]", @@ -175,7 +175,7 @@ describe("Chart Custom Config validator", () => { output: { isValid: true, parsed: { - type: "area2d", + type: "area", dataSource: { chart: { caption: "Countries With Most Oil Reserves [2017-18]", @@ -222,7 +222,7 @@ describe("Chart Custom Config validator", () => { }, transformed: { - type: "area2d", + type: "area", dataSource: { chart: { caption: "Countries With Most Oil Reserves [2017-18]", @@ -310,83 +310,14 @@ describe("Chart Custom Config validator", () => { }, }, output: { - isValid: true, + isValid: false, + message: + 'This value does not evaluate to type "{type: string, dataSource: { chart: object, data: Array<{label: string, value: number}>}}"', parsed: { - type: "area2d", + type: "", dataSource: { - data: [ - { - label: "Venezuela", - value: "290", - }, - { - label: "Saudi", - value: "260", - }, - { - label: "Canada", - value: "180", - }, - { - label: "Iran", - value: "140", - }, - { - label: "Russia", - value: "115", - }, - { - label: "UAE", - value: "100", - }, - { - label: "US", - value: "30", - }, - { - label: "China", - value: "30", - }, - ], - }, - }, - transformed: { - type: "area2d", - dataSource: { - data: [ - { - label: "Venezuela", - value: "290", - }, - { - label: "Saudi", - value: "260", - }, - { - label: "Canada", - value: "180", - }, - { - label: "Iran", - value: "140", - }, - { - label: "Russia", - value: "115", - }, - { - label: "UAE", - value: "100", - }, - { - label: "US", - value: "30", - }, - { - label: "China", - value: "30", - }, - ], + chart: {}, + data: [], }, }, }, diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index c374c9d737..c17447170c 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -7,6 +7,7 @@ import { import { DataTree } from "../entities/DataTree/dataTreeFactory"; import _, { every, + indexOf, isBoolean, isNil, isNumber, @@ -18,6 +19,10 @@ import _, { toString, } from "lodash"; import { WidgetProps } from "../widgets/BaseWidget"; +import { + CUSTOM_CHART_TYPES, + CUSTOM_CHART_DEFAULT_PARSED, +} from "../constants/CustomChartConstants"; import moment from "moment"; export function validateDateString( @@ -416,6 +421,15 @@ export const VALIDATORS: Record = { message: `${WIDGET_TYPE_VALIDATION_ERROR} "{type: string, dataSource: { chart: object, data: Array<{label: string, value: number}>}}"`, }; } + // check custom chart exist or not + const typeExist = indexOf(CUSTOM_CHART_TYPES, parsed.type) !== -1; + if (!typeExist) { + return { + isValid: false, + parsed: { ...CUSTOM_CHART_DEFAULT_PARSED }, + message: `${WIDGET_TYPE_VALIDATION_ERROR} "{type: string, dataSource: { chart: object, data: Array<{label: string, value: number}>}}"`, + }; + } return { isValid, parsed, transformed: parsed }; }, [VALIDATION_TYPES.MARKERS]: ( From 77940c0cfaafd14a3c3196eec3c6ecfd9d969b17 Mon Sep 17 00:00:00 2001 From: Nayan <83352306+nayan-rafiq@users.noreply.github.com> Date: Wed, 2 Jun 2021 15:56:52 +0600 Subject: [PATCH 039/119] [Issue #3785][Bug] User names in organization management page are stale (#4702) * [Issue #3785][Bug] User names in organization management page are stale * -update name in userroles in background when there is a change in user --- .../CustomOrganizationRepository.java | 1 + .../CustomOrganizationRepositoryImpl.java | 12 +++ .../repositories/OrganizationRepository.java | 2 + .../services/OrganizationServiceImpl.java | 2 + .../server/solutions/UserChangedHandler.java | 15 +++ .../OrganizationRepositoryTest.java | 83 ++++++++++++++++ .../services/OrganizationServiceUnitTest.java | 97 +++++++++++++++++++ 7 files changed, 212 insertions(+) create mode 100644 app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/OrganizationRepositoryTest.java create mode 100644 app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceUnitTest.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java index 3d80001be8..59d9e1f33a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepository.java @@ -16,4 +16,5 @@ public interface CustomOrganizationRepository extends AppsmithRepository nextSlugNumber(String slugPrefix); + Mono updateUserRoleNames(String userId, String userName); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java index 9b45fa8d70..b96c0ff0b8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomOrganizationRepositoryImpl.java @@ -1,6 +1,7 @@ package com.appsmith.server.repositories; import com.appsmith.server.acl.AclPermission; +import com.appsmith.server.domains.Comment; import com.appsmith.server.domains.Organization; import com.appsmith.server.domains.QOrganization; import lombok.extern.slf4j.Slf4j; @@ -9,6 +10,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -66,4 +68,14 @@ public class CustomOrganizationRepositoryImpl extends BaseAppsmithRepositoryImpl }); } + @Override + public Mono updateUserRoleNames(String userId, String userName) { + return mongoOperations + .updateMulti( + Query.query(Criteria.where("userRoles.userId").is(userId)), + Update.update("userRoles.$.name", userName), + Organization.class + ) + .then(); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java index 6d2224f775..76e51eaf35 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/OrganizationRepository.java @@ -13,4 +13,6 @@ public interface OrganizationRepository extends BaseRepository findByName(String name); + Mono updateUserRoleNames(String userId, String userName); + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java index e45b431d1b..d8659fe97b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/OrganizationServiceImpl.java @@ -1,5 +1,6 @@ package com.appsmith.server.services; +import com.appsmith.external.models.BaseDomain; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.AppsmithRole; import com.appsmith.server.acl.RoleGraph; @@ -35,6 +36,7 @@ import javax.validation.Validator; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; +import java.util.ArrayList; import java.util.Map; import java.util.Optional; import java.util.Set; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java index 921580c22e..8fa28c55f9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/UserChangedHandler.java @@ -3,6 +3,7 @@ package com.appsmith.server.solutions; import com.appsmith.server.domains.User; import com.appsmith.server.events.UserChangedEvent; import com.appsmith.server.repositories.CommentRepository; +import com.appsmith.server.repositories.OrganizationRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; @@ -20,6 +21,7 @@ public class UserChangedHandler { private final ApplicationEventPublisher applicationEventPublisher; private final CommentRepository commentRepository; + private final OrganizationRepository organizationRepository; public User publish(User user) { applicationEventPublisher.publishEvent(new UserChangedEvent(user)); @@ -35,6 +37,10 @@ public class UserChangedHandler { updateNameInComments(user) .subscribeOn(Schedulers.elastic()) .subscribe(); + + updateNameInUserRoles(user) + .subscribeOn(Schedulers.elastic()) + .subscribe(); } private Mono updateNameInComments(User user) { @@ -47,4 +53,13 @@ public class UserChangedHandler { return commentRepository.updateAuthorNames(user.getId(), user.getName()); } + private Mono updateNameInUserRoles(User user) { + if (user.getId() == null) { + log.warn("Attempt to update name in userRoles of organization for user with null ID."); + return Mono.empty(); + } + + log.debug("Updating name in userRoles of organization for user {}", user.getId()); + return organizationRepository.updateUserRoleNames(user.getId(), user.getName()); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/OrganizationRepositoryTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/OrganizationRepositoryTest.java new file mode 100644 index 0000000000..b36cb6edbd --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/OrganizationRepositoryTest.java @@ -0,0 +1,83 @@ +package com.appsmith.server.repositories; + +import com.appsmith.server.domains.Organization; +import com.appsmith.server.domains.UserRole; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; +import reactor.util.function.Tuple2; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +@RunWith(SpringRunner.class) +@SpringBootTest +@Slf4j +@DirtiesContext +class OrganizationRepositoryTest { + + @Autowired + private OrganizationRepository organizationRepository; + + @Test + void updateUserRoleNames_WhenUserIdMatched_AllOrgsUpdated() { + String oldUserName = "Old name", + newUserName = "New name", + userId = "user1"; + UserRole userRole = new UserRole(); + userRole.setName(oldUserName); + userRole.setUserId(userId); + + List userRoles = new ArrayList<>(); + userRoles.add(userRole); + + Organization org1 = new Organization(); + org1.setId(UUID.randomUUID().toString()); + org1.setSlug(org1.getId()); + org1.setUserRoles(userRoles); + + Organization org2 = new Organization(); + org2.setId(UUID.randomUUID().toString()); + org2.setSlug(org2.getId()); + org2.setUserRoles(userRoles); + + // create two orgs + Mono> aveOrgsMonoZip = Mono.zip( + organizationRepository.save(org1), organizationRepository.save(org2) + ); + + Mono> updatedOrgTupleMono = aveOrgsMonoZip.flatMap(objects -> { + // update the user names + return organizationRepository.updateUserRoleNames(userId, newUserName).thenReturn(objects); + }).flatMap(organizationTuple2 -> { + // fetch the two orgs again + Mono updatedOrg1Mono = organizationRepository.findBySlug(org1.getId()); + Mono updatedOrg2Mono = organizationRepository.findBySlug(org2.getId()); + return Mono.zip(updatedOrg1Mono, updatedOrg2Mono); + }); + + StepVerifier.create(updatedOrgTupleMono).assertNext(orgTuple -> { + Organization o1 = orgTuple.getT1(); + Assert.assertEquals(1, o1.getUserRoles().size()); + UserRole userRole1 = o1.getUserRoles().get(0); + Assert.assertEquals(userId, userRole1.getUserId()); + Assert.assertEquals(newUserName, userRole1.getName()); + + Organization o2 = orgTuple.getT2(); + Assert.assertEquals(1, o2.getUserRoles().size()); + UserRole userRole2 = o2.getUserRoles().get(0); + Assert.assertEquals(userId, userRole2.getUserId()); + Assert.assertEquals(newUserName, userRole2.getName()); + }).verifyComplete(); + } +} \ No newline at end of file diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceUnitTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceUnitTest.java new file mode 100644 index 0000000000..452745efbf --- /dev/null +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/OrganizationServiceUnitTest.java @@ -0,0 +1,97 @@ +package com.appsmith.server.services; + +import com.appsmith.server.acl.RoleGraph; +import com.appsmith.server.constants.FieldName; +import com.appsmith.server.domains.Organization; +import com.appsmith.server.domains.User; +import com.appsmith.server.domains.UserRole; +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.repositories.AssetRepository; +import com.appsmith.server.repositories.OrganizationRepository; +import com.appsmith.server.repositories.PluginRepository; +import com.appsmith.server.repositories.UserRepository; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Scheduler; +import reactor.test.StepVerifier; + +import javax.validation.Validator; +import java.util.ArrayList; +import java.util.List; + +import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS; + +@RunWith(SpringJUnit4ClassRunner.class) +public class OrganizationServiceUnitTest { + + @MockBean PluginRepository pluginRepository; + @MockBean SessionUserService sessionUserService; + @MockBean UserOrganizationService userOrganizationService; + @MockBean UserRepository userRepository; + @MockBean RoleGraph roleGraph; + @MockBean AssetRepository assetRepository; + @MockBean AssetService assetService; + @MockBean Scheduler scheduler; + @MockBean MongoConverter mongoConverter; + @MockBean ReactiveMongoTemplate reactiveMongoTemplate; + @MockBean OrganizationRepository organizationRepository; + @MockBean Validator validator; + @MockBean AnalyticsService analyticsService; + + OrganizationService organizationService; + + @Before + public void setUp() { + organizationService = new OrganizationServiceImpl(scheduler, validator, mongoConverter, reactiveMongoTemplate, + organizationRepository, analyticsService, pluginRepository, sessionUserService, userOrganizationService, + userRepository, roleGraph, assetRepository, assetService + ); + MockitoAnnotations.initMocks(this); + } + + @Test + public void getOrganizationMembers_WhenRoleIsNull_ReturnsEmptyList() { + // create a organization object + Organization testOrg = new Organization(); + testOrg.setName("Get All Members For Organization Test"); + testOrg.setDomain("test.com"); + testOrg.setWebsite("https://test.com"); + testOrg.setId("test-org-id"); + + // mock repository methods so that they return the objects we've created + Mockito.when(organizationRepository.findById("test-org-id", ORGANIZATION_INVITE_USERS)) + .thenReturn(Mono.just(testOrg)); + + Mono> organizationMembers = organizationService.getOrganizationMembers(testOrg.getId()); + StepVerifier + .create(organizationMembers) + .assertNext(userRoles -> { + Assert.assertEquals(0, userRoles.size()); + }) + .verifyComplete(); + } + + @Test + public void getOrganizationMembers_WhenNoOrgFound_ThrowsException() { + String sampleOrgId = "test-org-id"; + // mock repository methods so that they return the objects we've created + Mockito.when(organizationRepository.findById(sampleOrgId, ORGANIZATION_INVITE_USERS)) + .thenReturn(Mono.empty()); + + Mono> organizationMembers = organizationService.getOrganizationMembers(sampleOrgId); + StepVerifier + .create(organizationMembers) + .expectErrorMessage(AppsmithError.NO_RESOURCE_FOUND.getMessage(FieldName.ORGANIZATION, sampleOrgId)) + .verify(); + } +} From 8c84dcfdecd34af076c7a55b1a5660c104383ac8 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Wed, 2 Jun 2021 18:27:27 +0530 Subject: [PATCH 040/119] Fix multiline js issues (#4816) --- app/client/src/workers/evaluate.test.ts | 5 ----- app/client/src/workers/evaluate.ts | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/app/client/src/workers/evaluate.test.ts b/app/client/src/workers/evaluate.test.ts index d658e35fc4..d708ce2952 100644 --- a/app/client/src/workers/evaluate.test.ts +++ b/app/client/src/workers/evaluate.test.ts @@ -34,11 +34,6 @@ describe("evaluate", () => { const response = evaluate(js, {}); expect(response.result).toBe("Hello!"); }); - it("unescapes string and removes linebreaks before evaluation", () => { - const js = "'Hello,\\nworld!'"; - const response = evaluate(js, {}); - expect(response.result).toBe("Hello,world!"); - }); it("throws error for undefined js", () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore diff --git a/app/client/src/workers/evaluate.ts b/app/client/src/workers/evaluate.ts index 6e182f1d88..37d9b91465 100644 --- a/app/client/src/workers/evaluate.ts +++ b/app/client/src/workers/evaluate.ts @@ -17,7 +17,7 @@ export default function evaluate( data: DataTree, callbackData?: Array, ): EvalResult { - const unescapedJS = unescapeJS(js).replace(/(\r\n|\n|\r)/gm, ""); + const unescapedJS = unescapeJS(js); const scriptToEvaluate = ` const result = ${unescapedJS}; return { result, triggers: self.triggers } From 03b2e7b04277a50624565f4a6287622ff16aa7b3 Mon Sep 17 00:00:00 2001 From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Date: Wed, 2 Jun 2021 21:23:30 +0530 Subject: [PATCH 041/119] updated locator (#4871) --- .../OrganisationTests/CreateOrgTests_spec.js | 6 +++--- app/client/cypress/support/commands.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js index 2b7c53bdf6..1effada689 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OrganisationTests/CreateOrgTests_spec.js @@ -56,7 +56,7 @@ describe("Create new org and share with a user", function() { }); it("login as Org owner and update the invited user role to developer", function() { - cy.LoginFromAPI(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); cy.visit("/applications"); cy.wait("@applications").should( "have.nested.property", @@ -95,7 +95,7 @@ describe("Create new org and share with a user", function() { }); it("login as Org owner and update the invited user role to administrator", function() { - cy.LoginFromAPI(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); cy.visit("/applications"); cy.wait("@applications").should( "have.nested.property", @@ -129,7 +129,7 @@ describe("Create new org and share with a user", function() { }); it("login as Org owner and delete App ", function() { - cy.LoginFromAPI(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); + cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD")); cy.visit("/applications"); cy.wait("@applications").should( "have.nested.property", diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 75ea622e30..eda98ece15 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -167,7 +167,9 @@ Cypress.Commands.add("deleteUserFromOrg", (orgName, email) => { "response.body.responseMeta.status", 200, ); - cy.get(homePage.DeleteBtn).click({ force: true }); + cy.get(homePage.DeleteBtn) + .last() + .click({ force: true }); cy.xpath(homePage.appHome) .first() .should("be.visible") From 8fea689b4d472cd19887900eea32b9576c403294 Mon Sep 17 00:00:00 2001 From: Abhijeet <41686026+abhvsn@users.noreply.github.com> Date: Thu, 3 Jun 2021 08:59:27 +0530 Subject: [PATCH 042/119] Actions are updated to page level resource, application name included in json file name (#4879) --- .../server/controllers/ApplicationController.java | 4 ++-- .../solutions/ImportExportApplicationService.java | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java index c8e035b655..a1d7fca78d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ApplicationController.java @@ -135,11 +135,11 @@ public class ApplicationController extends BaseController { - + String applicationName = fetchedResource.getExportedApplication().getName(); HttpHeaders responseHeaders = new HttpHeaders(); ContentDisposition contentDisposition = ContentDisposition .builder("attachment") - .filename("application-file.json", StandardCharsets.UTF_8) + .filename(applicationName + ".json", StandardCharsets.UTF_8) .build(); responseHeaders.setContentDisposition(contentDisposition); responseHeaders.setContentType(MediaType.APPLICATION_JSON); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java index 4a6b7e9dac..9e76603ce7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationService.java @@ -362,7 +362,6 @@ public class ImportExportApplicationService { ) .flatMap(savedApp -> { importedApplication.setId(savedApp.getId()); - importedNewPageList.forEach(newPage -> newPage.setApplicationId(savedApp.getId())); Map> applicationPages = Map.of( PublishType.UNPUBLISH, new ArrayList<>(), PublishType.PUBLISH, new ArrayList<>() @@ -412,14 +411,14 @@ public class ImportExportApplicationService { NewPage parentPage = new NewPage(); if (newAction.getUnpublishedAction() != null && newAction.getUnpublishedAction().getName() != null) { parentPage = pageNameMap.get(newAction.getUnpublishedAction().getPageId()); - actionIdMap.put(newAction.getUnpublishedAction().getName(), newAction.getId()); + actionIdMap.put(newAction.getUnpublishedAction().getName() + parentPage.getId(), newAction.getId()); newAction.getUnpublishedAction().setPageId(parentPage.getId()); mapDatasourceIdToNewAction(newAction.getUnpublishedAction(), datasourceMap); } if (newAction.getPublishedAction() != null && newAction.getPublishedAction().getName() != null) { parentPage = pageNameMap.get(newAction.getPublishedAction().getPageId()); - actionIdMap.put(newAction.getPublishedAction().getName(), newAction.getId()); + actionIdMap.put(newAction.getPublishedAction().getName() + parentPage.getId(), newAction.getId()); newAction.getPublishedAction().setPageId(parentPage.getId()); mapDatasourceIdToNewAction(newAction.getPublishedAction(), datasourceMap); } @@ -434,14 +433,18 @@ public class ImportExportApplicationService { .map(newAction -> { if (newAction.getUnpublishedAction() != null) { + ActionDTO unpublishedAction = newAction.getUnpublishedAction(); actionIdMap.put( - actionIdMap.get(newAction.getUnpublishedAction().getName()), newAction.getId() + actionIdMap.get(unpublishedAction.getName() + unpublishedAction.getPageId()), + newAction.getId() ); } if (newAction.getPublishedAction() != null) { + ActionDTO publishedAction = newAction.getPublishedAction(); actionIdMap.put( - actionIdMap.get(newAction.getPublishedAction().getName()), newAction.getId() + actionIdMap.get(publishedAction.getName() + publishedAction.getPageId()), + newAction.getId() ); } From 226c0720bd1633db1f52a9ad73f58b3cd558c9e0 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Thu, 3 Jun 2021 09:20:47 +0530 Subject: [PATCH 043/119] Default state for mongo actions that were already configured (#4873) * Default state for mongo actions that were already configured * Clean up --- .../server/migrations/DatabaseChangelog.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java index 030aa70595..88bb57c7d3 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java @@ -50,6 +50,7 @@ import com.appsmith.server.services.OrganizationService; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.cloudyrock.mongock.ChangeLog; import com.github.cloudyrock.mongock.ChangeSet; +import com.github.cloudyrock.mongock.decorator.impl.MongockTemplate; import com.google.gson.Gson; import com.mongodb.MongoClient; import com.mongodb.MongoException; @@ -2257,4 +2258,34 @@ public class DatabaseChangelog { // Delete all the existing mongo datasource structures by setting the key to null. mongoOperations.updateMulti(query, update, Datasource.class); } + + @ChangeSet(order = "069", id = "set-mongo-actions-type-to-raw", author = "") + public void setMongoActionInputToRaw(MongockTemplate mongockTemplate) { + + // All the existing mongo actions at this point will only have ever been in the raw format + // For these actions to be readily available to users, we need to set their input type to raw manually + // This is required because since the mongo form, the default input type on the UI has been set to FORM + Plugin mongoPlugin = mongockTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); + + // Fetch all the actions built on top of a mongo database, not having any value set for input type + assert mongoPlugin != null; + List rawMongoActions = mongockTemplate.find( + query(new Criteria().andOperator( + where(fieldName(QNewAction.newAction.pluginId)).is(mongoPlugin.getId()))), + NewAction.class + ) + .stream() + .filter(mongoAction -> { + final List pluginSpecifiedTemplates = mongoAction.getUnpublishedAction().getActionConfiguration().getPluginSpecifiedTemplates(); + return pluginSpecifiedTemplates != null && pluginSpecifiedTemplates.size() == 1; + }) + .collect(Collectors.toList()); + + for (NewAction action : rawMongoActions) { + List pluginSpecifiedTemplates = action.getUnpublishedAction().getActionConfiguration().getPluginSpecifiedTemplates(); + pluginSpecifiedTemplates.add(new Property(null, "RAW")); + + mongockTemplate.save(action); + } + } } From a31adce20d6496e5bdaea798cfc987533f72cb5f Mon Sep 17 00:00:00 2001 From: arunvjn <32433245+arunvjn@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:28:18 +0530 Subject: [PATCH 044/119] Bug/api editor datasources (#4780) * Fixed datasource urls' disappearance when the saved datasource has a trailing slash * Refactored query params parser to a seperate function. Added unit tests for the new function --- app/client/src/sagas/ApiPaneSagas.ts | 71 +++++++---------------- app/client/src/utils/ApiPaneUtils.test.ts | 31 +++++++++- app/client/src/utils/ApiPaneUtils.tsx | 23 ++++++++ 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/app/client/src/sagas/ApiPaneSagas.ts b/app/client/src/sagas/ApiPaneSagas.ts index 29b8e690be..e968ce79c3 100644 --- a/app/client/src/sagas/ApiPaneSagas.ts +++ b/app/client/src/sagas/ApiPaneSagas.ts @@ -68,7 +68,11 @@ import { Toaster } from "components/ads/Toast"; import { createMessage, ERROR_ACTION_RENAME_FAIL } from "constants/messages"; import { checkCurrentStep } from "./OnboardingSagas"; import { OnboardingStep } from "constants/OnboardingConstants"; -import { getIndextoUpdate } from "utils/ApiPaneUtils"; +import { + getIndextoUpdate, + parseUrlForQueryParams, + queryParamsRegEx, +} from "utils/ApiPaneUtils"; function* syncApiParamsSaga( actionPayload: ReduxActionWithMeta, @@ -76,58 +80,25 @@ function* syncApiParamsSaga( ) { const field = actionPayload.meta.field; //Payload here contains the path and query params of a typical url like https://{domain}/{path}?{query_params} - let value = actionPayload.payload; + const value = actionPayload.payload; // Regular expression to find the query params group - const padQueryParams = { key: "", value: "" }; - const queryParamsRegEx = /(\/[\s\S]*?)(\?(?![^{]*})[\s\S]*)?$/; PerformanceTracker.startTracking(PerformanceTransactionName.SYNC_PARAMS_SAGA); if (field === "actionConfiguration.path") { - value = (value.match(queryParamsRegEx) || [])[2] || ""; - if (value.indexOf("?") > -1) { - const paramsString = value.substr(value.indexOf("?") + 1); - const params = paramsString.split("&").map((p) => { - const firstEqualPos = p.indexOf("="); - const keyValue = - firstEqualPos > -1 - ? [p.substring(0, firstEqualPos), p.substring(firstEqualPos + 1)] - : []; - return { key: keyValue[0] || "", value: keyValue[1] || "" }; - }); - if (params.length < 2) { - while (params.length < 2) { - params.push(padQueryParams); - } - } - yield put( - autofill( - API_EDITOR_FORM_NAME, - "actionConfiguration.queryParameters", - params, - ), - ); - yield put( - setActionProperty({ - actionId: actionId, - propertyName: "actionConfiguration.queryParameters", - value: params, - }), - ); - } else { - yield put( - autofill( - API_EDITOR_FORM_NAME, - "actionConfiguration.queryParameters", - Array(2).fill(padQueryParams), - ), - ); - yield put( - setActionProperty({ - actionId: actionId, - propertyName: "actionConfiguration.queryParameters", - value: Array(2).fill(padQueryParams), - }), - ); - } + const params = parseUrlForQueryParams(value); + yield put( + autofill( + API_EDITOR_FORM_NAME, + "actionConfiguration.queryParameters", + params, + ), + ); + yield put( + setActionProperty({ + actionId: actionId, + propertyName: "actionConfiguration.queryParameters", + value: params, + }), + ); } else if (field.includes("actionConfiguration.queryParameters")) { const { values } = yield select(getFormData, API_EDITOR_FORM_NAME); const path = values.actionConfiguration.path || ""; diff --git a/app/client/src/utils/ApiPaneUtils.test.ts b/app/client/src/utils/ApiPaneUtils.test.ts index f1640e7bc9..a924bf7e19 100644 --- a/app/client/src/utils/ApiPaneUtils.test.ts +++ b/app/client/src/utils/ApiPaneUtils.test.ts @@ -1,7 +1,7 @@ -import { getIndextoUpdate } from "utils/ApiPaneUtils"; +import { getIndextoUpdate, parseUrlForQueryParams } from "utils/ApiPaneUtils"; describe("api pane header insertion or removal", () => { - describe("index for header needs to be retuened", () => { + describe("index for header needs to be returned", () => { test("it gives correct index", () => { const headers = [ { key: "content-type", value: "application/json" }, @@ -29,3 +29,30 @@ describe("api pane header insertion or removal", () => { }); }); }); + +describe("Api pane query parameters parsing", () => { + test("It gives correct query parameters", () => { + const url1 = "user?q=2&b='Auth=xyz'"; + const params1 = [ + { key: "q", value: "2" }, + { key: "b", value: "'Auth=xyz'" }, + ]; + expect(parseUrlForQueryParams(url1)).toEqual(params1); + const url2 = "/user?q=2&b='Auth=xyz'"; + expect(parseUrlForQueryParams(url2)).toEqual(params1); + const url3 = "user?q=2&b={{Api1.data.isLatest ? 'v1' : 'v2'}}"; + const params2 = [ + { key: "q", value: "2" }, + { key: "b", value: "{{Api1.data.isLatest ? 'v1' : 'v2'}}" }, + ]; + expect(parseUrlForQueryParams(url3)).toEqual(params2); + const url4 = ""; + const params3 = [ + { key: "", value: "" }, + { key: "", value: "" }, + ]; + expect(parseUrlForQueryParams(url4)).toEqual(params3); + const url5 = "/"; + expect(parseUrlForQueryParams(url5)).toEqual(params3); + }); +}); diff --git a/app/client/src/utils/ApiPaneUtils.tsx b/app/client/src/utils/ApiPaneUtils.tsx index b60d8f292b..13d2e158df 100644 --- a/app/client/src/utils/ApiPaneUtils.tsx +++ b/app/client/src/utils/ApiPaneUtils.tsx @@ -12,3 +12,26 @@ export const getIndextoUpdate = ( contentTypeHeaderIndex > -1 ? contentTypeHeaderIndex : newHeaderIndex; return indexToUpdate; }; + +export const queryParamsRegEx = /([\s\S]*?)(\?(?![^{]*})[\s\S]*)?$/; + +export function parseUrlForQueryParams(url: string) { + const padQueryParams = { key: "", value: "" }; + let params = Array(2).fill(padQueryParams); + const matchGroup = url.match(queryParamsRegEx) || []; + const parsedUrlWithQueryParams = matchGroup[2] || ""; + if (parsedUrlWithQueryParams.indexOf("?") > -1) { + const paramsString = parsedUrlWithQueryParams.substr( + parsedUrlWithQueryParams.indexOf("?") + 1, + ); + params = paramsString.split("&").map((p) => { + const firstEqualPos = p.indexOf("="); + const keyValue = + firstEqualPos > -1 + ? [p.substring(0, firstEqualPos), p.substring(firstEqualPos + 1)] + : []; + return { key: keyValue[0] || "", value: keyValue[1] || "" }; + }); + } + return params; +} From 4952207fc2461947674ebe4473e2d09cb08daf42 Mon Sep 17 00:00:00 2001 From: arunvjn <32433245+arunvjn@users.noreply.github.com> Date: Thu, 3 Jun 2021 10:29:41 +0530 Subject: [PATCH 045/119] UI improvements to API Editor (#4845) * Change CTA to apply datasource on datasource card click * Fix for Request method button's alignment issue. * Hide save/edit datasource button when url field is empty * Added edit icons to navigate to datasource form --- .../CodeEditor/styledComponents.ts | 2 +- .../editorComponents/StoreAsDatasource.tsx | 6 +- .../fields/EmbeddedDatasourcePathField.tsx | 3 +- .../pages/Editor/APIEditor/DatasourceList.tsx | 108 +++++++++++++----- .../src/pages/Editor/APIEditor/Form.tsx | 53 +++++++-- 5 files changed, 124 insertions(+), 48 deletions(-) diff --git a/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts b/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts index cdd2e42f20..4b50cc9fdd 100644 --- a/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts +++ b/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts @@ -59,7 +59,7 @@ export const HintStyles = createGlobalStyle<{ } .datasource-hint { - padding: 10px; + padding: 10px !important; display: block; width: 500px; height: 32px; diff --git a/app/client/src/components/editorComponents/StoreAsDatasource.tsx b/app/client/src/components/editorComponents/StoreAsDatasource.tsx index 35abcea5ff..989d82323c 100644 --- a/app/client/src/components/editorComponents/StoreAsDatasource.tsx +++ b/app/client/src/components/editorComponents/StoreAsDatasource.tsx @@ -7,12 +7,12 @@ import Icon, { IconSize } from "components/ads/Icon"; import { Classes } from "components/ads/common"; export const DatasourceIcon = styled.div<{ enable?: boolean }>` - position: absolute; - right: -155px; - top: 7px; display: flex; align-items: center; cursor: pointer; + margin-left: 10px; + height: 100%; + min-height: 37px; .${Classes.TEXT} { color: ${(props) => props.theme.colors.text.heading}; } diff --git a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx index 24c333c964..e1a1677f37 100644 --- a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx +++ b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx @@ -63,7 +63,6 @@ type Props = EditorProps & const DatasourceContainer = styled.div` display: flex; position: relative; - width: calc(100% - 155px); `; const hintContainerStyles: React.CSSProperties = { @@ -299,7 +298,7 @@ class EmbeddedDatasourcePathComponent extends React.Component { return ( - {datasource && !("id" in datasource) ? ( + {displayValue && datasource && !("id" in datasource) ? ( ) : datasource && "id" in datasource ? ( props.theme.colors.apiPane.dividerBg}; cursor: pointer; transition: 0.3s all ease; + .cs-icon { + opacity: 0; + transition: 0.3s all ease; + } &:hover { box-shadow: 0 0 5px #c7c7c7; + .cs-icon { + opacity: 1; + } } `; const DatasourceURL = styled.span` margin: 8px 0; - padding: 5px; font-size: 12px; - border: 1px solid #69b5ff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - background: #e7f3ff; + color: #457ae6; width: fit-content; max-width: 100%; + font-weight: 500; `; const PadTop = styled.div` @@ -77,6 +83,28 @@ const PadTop = styled.div` border: none; `; +const DataSourceNameContainer = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + .cs-text { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .cs-icon { + flex-shrink: 0; + svg { + path { + fill: #4b4848; + } + } + &: hover { + background-color: ${(props) => props.theme.colors.apiPane.iconHoverBg}; + } + } +`; + export const getDatasourceInfo = (datasource: any): string => { const info = []; const headers = get(datasource, "datasourceConfiguration.headers", []); @@ -110,33 +138,51 @@ export default function DataSourceList(props: any) { - {(props.datasources || []).map((d: any, idx: number) => ( - - history.push( - DATA_SOURCES_EDITOR_ID_URL( - props.applicationId, - props.currentPageId, - d.id, - ), - ) - } - > - - {d.name} - - - {d.datasourceConfiguration.url} - - - - - {getDatasourceInfo(d)} - - - - ))} + {(props.datasources || []).map((d: any, idx: number) => { + const dataSourceInfo: string = getDatasourceInfo(d); + return ( + props.onClick(d)} + > + + + {d.name} + + { + e.stopPropagation(); + history.push( + DATA_SOURCES_EDITOR_ID_URL( + props.applicationId, + props.currentPageId, + d.id, + ), + ); + }} + size={IconSize.LARGE} + /> + + + {d.datasourceConfiguration.url} + + {dataSourceInfo && ( + <> + + + + {dataSourceInfo} + + + + )} + + ); + })} ) : ( diff --git a/app/client/src/pages/Editor/APIEditor/Form.tsx b/app/client/src/pages/Editor/APIEditor/Form.tsx index 7565e40ce4..fbabdfa4ec 100644 --- a/app/client/src/pages/Editor/APIEditor/Form.tsx +++ b/app/client/src/pages/Editor/APIEditor/Form.tsx @@ -1,6 +1,11 @@ import React, { useState } from "react"; import { connect, useSelector, useDispatch } from "react-redux"; -import { formValueSelector, InjectedFormProps, reduxForm } from "redux-form"; +import { + formValueSelector, + InjectedFormProps, + reduxForm, + change, +} from "redux-form"; import { HTTP_METHOD_OPTIONS, HTTP_METHODS, @@ -41,6 +46,7 @@ import { Icon as ButtonIcon } from "@blueprintjs/core"; import { IconSize } from "components/ads/Icon"; import get from "lodash/get"; import DataSourceList from "./DatasourceList"; +import { Datasource } from "entities/Datasource"; const Form = styled.form` display: flex; flex-direction: column; @@ -197,6 +203,7 @@ interface APIFormProps { datasources?: any; currentPageId?: string; applicationId?: string; + updateDatasource: (datasource: Datasource) => void; } type Props = APIFormProps & InjectedFormProps; @@ -319,6 +326,11 @@ const DatasourceListTrigger = styled.div` } `; +const BoundaryContainer = styled.div` + border: 1px solid transparent; + border-right: none; +`; + function renderImportedHeadersButton( headersCount: number, onClick: any, @@ -351,6 +363,11 @@ const CloseIconContainer = styled.div` position: absolute; top: 12px; right: 10px; + svg { + path { + fill: #a9a7a7; + } + } `; function renderHelpSection( @@ -433,6 +450,7 @@ function ApiEditorForm(props: Props) { paramsCount, pluginId, settingsConfig, + updateDatasource, } = props; const dispatch = useDispatch(); const allowPostBody = @@ -484,15 +502,17 @@ function ApiEditorForm(props: Props) { - + + + void; +}; + +const mapDispatchToProps = (dispatch: any): ReduxDispatchProps => ({ + updateDatasource: (datasource) => { + dispatch(change(API_EDITOR_FORM_NAME, "datasource", datasource)); + }, +}); + export default connect((state: AppState, props: { pluginId: string }) => { const httpMethodFromForm = selector(state, "actionConfiguration.httpMethod"); const actionConfigurationHeaders = @@ -690,7 +721,7 @@ export default connect((state: AppState, props: { pluginId: string }) => { currentPageId: state.entities.pageList.currentPageId, applicationId: state.entities.pageList.applicationId, }; -})( +}, mapDispatchToProps)( reduxForm({ form: API_EDITOR_FORM_NAME, })(ApiEditorForm), From 80b6d6578c1b7a68047638f0d332529ccf6cd529 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Thu, 3 Jun 2021 10:58:26 +0530 Subject: [PATCH 046/119] Fix migration NPE --- .../java/com/appsmith/server/migrations/DatabaseChangelog.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java index 88bb57c7d3..1d2053317d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog.java @@ -2276,6 +2276,9 @@ public class DatabaseChangelog { ) .stream() .filter(mongoAction -> { + if (mongoAction.getUnpublishedAction() == null || mongoAction.getUnpublishedAction().getActionConfiguration() == null) { + return false; + } final List pluginSpecifiedTemplates = mongoAction.getUnpublishedAction().getActionConfiguration().getPluginSpecifiedTemplates(); return pluginSpecifiedTemplates != null && pluginSpecifiedTemplates.size() == 1; }) From 57874b412635d25bb41cd2c6ab1a9843907246cc Mon Sep 17 00:00:00 2001 From: abhishek nayak Date: Thu, 3 Jun 2021 11:18:03 +0530 Subject: [PATCH 047/119] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a7d411b00..42ecaed51c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - Appsmith - The Frontend Tool for Backend DevsAppsmith - The Frontend Tool for Backend Devs + Appsmith - The Frontend Tool for Backend DevsAppsmith - The Frontend Tool for Backend Devs

Start Building From 3a6da2d797b576b6a7424d88ba8eff9ef2ced296 Mon Sep 17 00:00:00 2001 From: Kaushik Varanasi Date: Thu, 3 Jun 2021 11:48:08 +0530 Subject: [PATCH 048/119] Feature/import applications (#4483) * Added export option to app menu. TODO: call api to download app file * Added checkbox component and removed unused code * Added import app without filepicker. Opens modal * added ability to fetch the exported app * can download exported application as a json file * Updated the file picker component to accept other file formats * WIP import app * Added functionality to import application json file * minor fixes * Made the file type prop mandatory for file picker * added a test suite for export app * Test added to check if on import application click, it open a modal * added a dummy application file for cypress testing * Added end to end integration test suite to verify import app feature * added test to verify the export api status and download file. * added a linked btn to carry exporting. - according to latest BE changes * Removed old redux and saga mechanism for app export * updated cypress test to validate new flow * fixed minor linting errors * updated test case title * updated the test cases for import/export app feat * review changes * added prop to facilitate delayed upload * added new application file to fixtures. Minor fix to take care of loading state. * Removed export app modal. Added one click action, to download the file. * Updated File picker to work with all other files acc to the design. * Updated the import modal * updated the import application test * Added remove upload tooltip * updated the icons for import/export actions * removed unused logs * added hard coded feature flag to hide/show import export feature Co-authored-by: Pranav Kanade --- .../cypress/fixtures/application-file.json | 174 ++++++++++++++ .../Applications/ExportApplication_spec.js | 26 +++ .../OrgImportApplication_spec.js | 39 ++++ app/client/cypress/locators/HomePage.json | 4 + app/client/cypress/support/commands.js | 2 + app/client/src/actions/applicationActions.ts | 12 +- app/client/src/api/ApplicationApi.tsx | 22 ++ app/client/src/assets/icons/ads/download.svg | 3 + .../src/assets/icons/ads/upload_success.svg | 3 + app/client/src/components/ads/FilePicker.tsx | 219 ++++++++++++++---- app/client/src/components/ads/Icon.tsx | 12 + .../components/stories/FilePicker.stories.tsx | 11 +- .../src/constants/ReduxActionConstants.tsx | 3 + app/client/src/constants/messages.ts | 5 +- .../pages/Applications/ApplicationCard.tsx | 30 +++ .../src/pages/Applications/ForkModalStyles.ts | 3 +- .../Applications/ImportApplicationModal.tsx | 117 ++++++++++ app/client/src/pages/Applications/index.tsx | 29 ++- app/client/src/pages/organization/General.tsx | 2 + .../uiReducers/applicationsReducer.tsx | 26 +++ app/client/src/sagas/ApplicationSagas.tsx | 52 +++++ app/client/src/selectors/{ui.ts => ui.tsx} | 0 22 files changed, 744 insertions(+), 50 deletions(-) create mode 100644 app/client/cypress/fixtures/application-file.json create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js create mode 100644 app/client/src/assets/icons/ads/download.svg create mode 100644 app/client/src/assets/icons/ads/upload_success.svg create mode 100644 app/client/src/pages/Applications/ImportApplicationModal.tsx rename app/client/src/selectors/{ui.ts => ui.tsx} (100%) diff --git a/app/client/cypress/fixtures/application-file.json b/app/client/cypress/fixtures/application-file.json new file mode 100644 index 0000000000..5a1193ac1d --- /dev/null +++ b/app/client/cypress/fixtures/application-file.json @@ -0,0 +1,174 @@ +{ + "exportedApplication": { + "userPermissions": [ + "canComment:applications", + "manage:applications", + "read:applications", + "publish:applications", + "makePublic:applications" + ], + "name": "testing app - pk", + "isPublic": false, + "appIsExample": false, + "color": "#FE9F44", + "icon": "heart", + "new": true + }, + "datasourceList": [], + "pageList": [ + { + "userPermissions": [ + "read:pages", + "manage:pages" + ], + "unpublishedPage": { + "name": "Page1", + "layouts": [ + { + "id": "60a77186cdbfc9440388285c", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1118, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1280, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 18, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicTriggerPathList": [], + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [ + { + "widgetName": "Button1", + "rightColumn": 4, + "isDefaultClickDisabled": true, + "widgetId": "g7jf8v3wkq", + "buttonStyle": "PRIMARY_BUTTON", + "topRow": 0, + "bottomRow": 1, + "parentRowSpace": 40, + "isVisible": true, + "type": "BUTTON_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "parentColumnSpace": 67.375, + "leftColumn": 2, + "text": "Submit", + "isDisabled": false + }, + { + "widgetName": "Chart1", + "rightColumn": 8, + "allowHorizontalScroll": false, + "widgetId": "ow55pc4z0z", + "topRow": 5, + "bottomRow": 13, + "parentRowSpace": 40, + "isVisible": true, + "type": "CHART_WIDGET", + "version": 1, + "parentId": "0", + "isLoading": false, + "chartData": { + "pftw37090s": { + "seriesName": "Sales", + "data": [ + { + "x": "Mon", + "y": 10000 + }, + { + "x": "Tue", + "y": 12000 + }, + { + "x": "Wed", + "y": 32000 + }, + { + "x": "Thu", + "y": 28000 + }, + { + "x": "Fri", + "y": 14000 + }, + { + "x": "Sat", + "y": 19000 + }, + { + "x": "Sun", + "y": 36000 + } + ] + } + }, + "yAxisName": "Total Order Revenue $", + "parentColumnSpace": 67.375, + "chartName": "Last week's revenue", + "leftColumn": 2, + "xAxisName": "Last Week", + "chartType": "LINE_CHART" + } + ] + }, + "layoutOnLoadActions": [], + "new": false + } + ], + "userPermissions": [] + }, + "publishedPage": { + "name": "Page1", + "layouts": [ + { + "id": "60a77186cdbfc9440388285c", + "userPermissions": [], + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 1224, + "snapColumns": 16, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0, + "bottomRow": 1254, + "containerStyle": "none", + "snapRows": 33, + "parentRowSpace": 1, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 4, + "minHeight": 1292, + "parentColumnSpace": 1, + "dynamicBindingPathList": [], + "leftColumn": 0, + "children": [] + }, + "new": false + } + ], + "userPermissions": [] + }, + "new": true + } + ], + "publishedDefaultPageName": "Page1", + "unpublishedDefaultPageName": "Page1", + "actionList": [], + "decryptedFields": {}, + "publishedLayoutmongoEscapedWidgets": {}, + "unpublishedLayoutmongoEscapedWidgets": {} +} \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js new file mode 100644 index 0000000000..1c8bbc29a0 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Applications/ExportApplication_spec.js @@ -0,0 +1,26 @@ +const dsl = require("../../../../fixtures/displayWidgetDsl.json"); +const homePage = require("../../../../locators/HomePage.json"); +const commonlocators = require("../../../../locators/commonlocators.json"); + +describe("Export application as a JSON file", function() { + before(() => { + cy.addDsl(dsl); + }); + + it("Check if exporting app flow works as expected", function() { + cy.get(commonlocators.homeIcon).click({ force: true }); + const appname = localStorage.getItem("AppName"); + cy.get(homePage.searchInput).type(appname); + // eslint-disable-next-line cypress/no-unnecessary-waiting + cy.wait(2000); + + cy.get(homePage.applicationCard) + .first() + .trigger("mouseover"); + cy.get(homePage.appMoreIcon) + .first() + .click({ force: true }); + cy.get(homePage.exportAppFromMenu).click({ force: true }); + cy.get(homePage.toastMessage).should("contain", "Successfully exported"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js new file mode 100644 index 0000000000..620b05e0aa --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/OrganisationTests/OrgImportApplication_spec.js @@ -0,0 +1,39 @@ +const homePage = require("../../../../locators/HomePage.json"); + +describe("Organization Import Application", function() { + let orgid; + let newOrganizationName; + const fixtureDummyAppPath = "application-file.json"; + it("Can Import Application", function() { + cy.NavigateToHome(); + cy.generateUUID().then((uid) => { + orgid = uid; + localStorage.setItem("OrgName", orgid); + cy.createOrg(); + cy.wait("@createOrg").then((createOrgInterception) => { + newOrganizationName = createOrgInterception.response.body.data.name; + cy.renameOrg(newOrganizationName, orgid); + cy.get(homePage.orgImportAppOption).click({ force: true }); + + cy.get(homePage.orgImportAppModal).should("be.visible"); + cy.xpath(homePage.uploadLogo).attachFile(fixtureDummyAppPath); + + cy.get(homePage.orgImportAppButton).click({ force: true }); + cy.wait("@importNewApplication").then((interception) => { + let appId = interception.response.body.data.id; + let defaultPage = interception.response.body.data.pages.find( + (eachPage) => !!eachPage.isDefault, + ); + cy.get(homePage.toastMessage).should( + "contain", + "Application imported successfully", + ); + cy.url().should( + "include", + `/applications/${appId}/pages/${defaultPage.id}/edit`, + ); + }); + }); + }); + }); +}); diff --git a/app/client/cypress/locators/HomePage.json b/app/client/cypress/locators/HomePage.json index fbc854261f..39bfc865de 100644 --- a/app/client/cypress/locators/HomePage.json +++ b/app/client/cypress/locators/HomePage.json @@ -9,6 +9,7 @@ "appMoreIcon": ".bp3-popover-wrapper.more .bp3-popover-target", "duplicateApp": "[data-cy=t--duplicate]", "forkAppFromMenu": "[data-cy=t--fork-app]", + "exportAppFromMenu": "[data-cy=t--export-app]", "forkAppOrgList": ".radio-group", "forkAppOrgButton": "[data-cy=t--fork-app-to-org-button]", "selectAction": "#Base", @@ -59,6 +60,9 @@ "applicationColorSelector": ".t--color-not-selected", "applicationBackgroundColor": ".t--application-card-background", "orgSettingOption": "[data-cy=t--org-setting]", + "orgImportAppOption": "[data-cy=t--org-import-app]", + "orgImportAppModal": ".t--import-application-modal", + "orgImportAppButton": "[data-cy=t--org-import-app-button]", "orgNameInput": "[data-cy=t--org-name-input]", "renameOrgInput": "[data-cy=t--org-rename-input]", "orgEmailInput": "[data-cy=t--org-email-input]", diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index eda98ece15..1e49791eaa 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -2212,6 +2212,8 @@ Cypress.Commands.add("startServerAndRoutes", () => { cy.route("PUT", "/api/v1/actions/move").as("moveAction"); cy.route("POST", "/api/v1/organizations").as("createOrg"); + cy.route("POST", "api/v1/applications/import/*").as("importNewApplication"); + cy.route("GET", "api/v1/applications/export/*").as("exportApplication"); cy.route("GET", "/api/v1/organizations/roles?organizationId=*").as( "getRoles", ); diff --git a/app/client/src/actions/applicationActions.ts b/app/client/src/actions/applicationActions.ts index 1dda8b55d8..e4cf360f65 100644 --- a/app/client/src/actions/applicationActions.ts +++ b/app/client/src/actions/applicationActions.ts @@ -1,6 +1,9 @@ import { ReduxAction, ReduxActionTypes } from "constants/ReduxActionConstants"; import { APP_MODE } from "../reducers/entityReducers/appReducer"; -import { UpdateApplicationPayload } from "api/ApplicationApi"; +import { + UpdateApplicationPayload, + ImportApplicationRequest, +} from "api/ApplicationApi"; export const setDefaultApplicationPageSuccess = ( pageId: string, @@ -77,6 +80,13 @@ export const duplicateApplication = (applicationId: string) => { }; }; +export const importApplication = (appDetails: ImportApplicationRequest) => { + return { + type: ReduxActionTypes.IMPORT_APPLICATION_INIT, + payload: appDetails, + }; +}; + export const getAllApplications = () => { return { type: ReduxActionTypes.GET_ALL_APPLICATION_INIT, diff --git a/app/client/src/api/ApplicationApi.tsx b/app/client/src/api/ApplicationApi.tsx index 0347802ff0..5ad7f57fd7 100644 --- a/app/client/src/api/ApplicationApi.tsx +++ b/app/client/src/api/ApplicationApi.tsx @@ -119,6 +119,13 @@ export interface FetchUsersApplicationsOrgsResponse extends ApiResponse { }; } +export interface ImportApplicationRequest { + orgId: string; + applicationFile?: File; + progress?: (progressEvent: ProgressEvent) => void; + onSuccessCallback?: () => void; +} + class ApplicationApi extends Api { static baseURL = "v1/applications/"; static publishURLPath = (applicationId: string) => `publish/${applicationId}`; @@ -212,6 +219,21 @@ class ApplicationApi extends Api { request.organizationId, ); } + + static importApplicationToOrg( + request: ImportApplicationRequest, + ): AxiosPromise { + const formData = new FormData(); + if (request.applicationFile) { + formData.append("file", request.applicationFile); + } + return Api.post("v1/applications/import/" + request.orgId, formData, null, { + headers: { + "Content-Type": "multipart/form-data", + }, + onUploadProgress: request.progress, + }); + } } export default ApplicationApi; diff --git a/app/client/src/assets/icons/ads/download.svg b/app/client/src/assets/icons/ads/download.svg new file mode 100644 index 0000000000..0cbe360e6c --- /dev/null +++ b/app/client/src/assets/icons/ads/download.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/ads/upload_success.svg b/app/client/src/assets/icons/ads/upload_success.svg new file mode 100644 index 0000000000..578e9a3171 --- /dev/null +++ b/app/client/src/assets/icons/ads/upload_success.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/components/ads/FilePicker.tsx b/app/client/src/components/ads/FilePicker.tsx index 1d5d6936b4..4ccffaa125 100644 --- a/app/client/src/components/ads/FilePicker.tsx +++ b/app/client/src/components/ads/FilePicker.tsx @@ -3,28 +3,52 @@ import styled from "styled-components"; import Button, { Category, Size } from "./Button"; import axios from "axios"; import { ReactComponent as UploadIcon } from "../../assets/icons/ads/upload.svg"; +import { ReactComponent as UploadSuccessIcon } from "../../assets/icons/ads/upload_success.svg"; import { DndProvider, useDrop, DropTargetMonitor } from "react-dnd"; import HTML5Backend, { NativeTypes } from "react-dnd-html5-backend"; import Text, { TextType } from "./Text"; import { Classes, Variant } from "./common"; import { Toaster } from "./Toast"; -import { createMessage, ERROR_FILE_TOO_LARGE } from "constants/messages"; - +import { + createMessage, + ERROR_FILE_TOO_LARGE, + REMOVE_FILE_TOOL_TIP, +} from "constants/messages"; +import TooltipComponent from "components/ads/Tooltip"; +import { Position } from "@blueprintjs/core/lib/esm/common/position"; +import Icon, { IconSize } from "./Icon"; const CLOUDINARY_PRESETS_NAME = ""; const CLOUDINARY_CLOUD_NAME = ""; +const FileEndings = { + IMAGE: ".jpeg,.png,.svg", + JSON: ".json", + TEXT: ".txt", + ANY: "*", +}; + +export enum FileType { + IMAGE = "IMAGE", + JSON = "JSON", + TEXT = "TEXT", + ANY = "ANY", +} + type FilePickerProps = { onFileUploaded?: (fileUrl: string) => void; onFileRemoved?: () => void; fileUploader?: FileUploader; url?: string; logoUploadError?: string; + fileType: FileType; + delayedUpload?: boolean; }; const ContainerDiv = styled.div<{ isUploaded: boolean; isActive: boolean; canDrop: boolean; + fileType: FileType; }>` width: 320px; height: 190px; @@ -41,7 +65,7 @@ const ContainerDiv = styled.div<{ color: ${(props) => props.theme.colors.filePicker.color}; } - .bg-image { + .upload-form-container { width: 100%; height: 100%; display: grid; @@ -51,15 +75,36 @@ const ContainerDiv = styled.div<{ background-size: contain; } + .centered { + justify-content: center; + flex-direction: column; + align-items: center; + + .success-container { + display: flex; + align-items: center; + .success-icon { + margin-right: ${(props) => props.theme.spaces[4]}px; + } + + .success-text { + color: #03b365; + margin-right: ${(props) => props.theme.spaces[4]}px; + } + } + } + .file-description { width: 95%; - margin-top: auto; + margin: 0 auto; + margin-top: ${(props) => + props.fileType === FileType.IMAGE ? "auto" : "0px"}; margin-bottom: ${(props) => props.theme.spaces[6] + 1}px; display: none; } .file-spec { - margin-bottom: ${(props) => props.theme.spaces[2]}px; + margin-bottom: ${(props) => props.theme.spaces[3]}px; span { margin-right: ${(props) => props.theme.spaces[4]}px; } @@ -116,6 +161,11 @@ const ContainerDiv = styled.div<{ } `; +const IconWrapper = styled.div` + width: ${(props) => props.theme.spaces[9]}px; + padding-left: ${(props) => props.theme.spaces[2]}px; +`; + export type SetProgress = (percentage: number) => void; export type UploadCallback = (url: string) => void; export type FileUploader = ( @@ -159,7 +209,7 @@ export function CloudinaryUploader( } function FilePickerComponent(props: FilePickerProps) { - const { logoUploadError } = props; + const { fileType, logoUploadError } = props; const [fileInfo, setFileInfo] = useState<{ name: string; size: number }>({ name: "", size: 0, @@ -207,7 +257,7 @@ function FilePickerComponent(props: FilePickerProps) { } if (uploadPercentage === 100) { setIsUploaded(true); - if (fileDescRef.current && bgRef.current) { + if (fileDescRef.current && bgRef.current && fileType === FileType.IMAGE) { fileDescRef.current.style.display = "none"; bgRef.current.style.opacity = "1"; } @@ -219,6 +269,35 @@ function FilePickerComponent(props: FilePickerProps) { } function handleFileUpload(files: FileList | null) { + if (fileType === FileType.IMAGE) { + handleImageFileUpload(files); + } else { + handleOtherFileUpload(files); + } + } + + function handleOtherFileUpload(files: FileList | null) { + const file = files && files[0]; + let fileSize = 0; + if (!file) { + return; + } + fileSize = Math.floor(file.size / 1024); + setFileInfo({ name: file.name, size: fileSize }); + if (props.delayedUpload) { + setIsUploaded(true); + setProgress(100); + } + if (fileDescRef.current) { + fileDescRef.current.style.display = "flex"; + } + if (fileContainerRef.current) { + fileContainerRef.current.style.display = "none"; + } + props.fileUploader && props.fileUploader(file, setProgress, onUpload); + } + + function handleImageFileUpload(files: FileList | null) { const file = files && files[0]; let fileSize = 0; @@ -253,10 +332,15 @@ function FilePickerComponent(props: FilePickerProps) { } function removeFile() { - if (fileContainerRef.current && bgRef.current) { + if (fileContainerRef.current) { setFileUrl(""); + if (fileDescRef.current) { + fileDescRef.current.style.display = "none"; + } fileContainerRef.current.style.display = "flex"; - bgRef.current.style.backgroundImage = "url('')"; + if (bgRef.current) { + bgRef.current.style.backgroundImage = "url('')"; + } setIsUploaded(false); props.onFileRemoved && props.onFileRemoved(); } @@ -275,8 +359,9 @@ function FilePickerComponent(props: FilePickerProps) { } }, [props.url]); + // Following hook should be used only if file type is image. useEffect(() => { - if (fileUrl && !isUploaded) { + if (fileUrl && !isUploaded && fileType === FileType.IMAGE) { setIsUploaded(true); if (bgRef.current) { bgRef.current.style.backgroundImage = `url(${fileUrl})`; @@ -291,42 +376,47 @@ function FilePickerComponent(props: FilePickerProps) { } }, [fileUrl, logoUploadError]); - return ( - -

-
- - - Drag & Drop files to upload or - -
- handleFileUpload(el.target.files)} - ref={inputRef} - type="file" - value={""} - /> -
+ // + + const uploadFileForm = ( +
+ + + Drag & Drop files to upload or + +
+ handleFileUpload(el.target.files)} + ref={inputRef} + type="file" + value={""} + /> +
+ ); + + const uploadStatus = ( +
+ {fileInfo.name} + {fileInfo.size}KB +
+ ); + + const imageUploadComponent = ( + <> +
+ {uploadFileForm}
-
- {fileInfo.name} - {fileInfo.size}KB -
+ {uploadStatus}
@@ -341,6 +431,45 @@ function FilePickerComponent(props: FilePickerProps) { text="remove" />
+ + ); + + const uploadComponent = ( +
+ {uploadFileForm} +
+ {uploadStatus} +
+ + + Successfully Uploaded! + + + removeFile()}> + + + +
+
+
+ ); + + return ( + + {fileType === FileType.IMAGE ? imageUploadComponent : uploadComponent} ); } diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx index e02af7fe68..ecca134ab8 100644 --- a/app/client/src/components/ads/Icon.tsx +++ b/app/client/src/components/ads/Icon.tsx @@ -64,6 +64,8 @@ import { ReactComponent as Pin3 } from "assets/icons/comments/pin_3.svg"; import { ReactComponent as Unpin } from "assets/icons/comments/unpin.svg"; import { ReactComponent as Reaction } from "assets/icons/comments/reaction.svg"; import { ReactComponent as Reaction2 } from "assets/icons/comments/reaction-2.svg"; +import { ReactComponent as Upload } from "assets/icons/ads/upload.svg"; +import { ReactComponent as Download } from "assets/icons/ads/download.svg"; import styled from "styled-components"; import { CommonComponentProps, Classes } from "./common"; import { noop } from "lodash"; @@ -117,6 +119,8 @@ export const sizeHandler = (size?: IconSize) => { }; export const IconCollection = [ + "upload", + "download", "book", "bug", "cancel", @@ -477,6 +481,14 @@ const Icon = forwardRef( returnIcon = ; break; + case "upload": + returnIcon = ; + break; + + case "download": + returnIcon = ; + break; + default: returnIcon = null; break; diff --git a/app/client/src/components/stories/FilePicker.stories.tsx b/app/client/src/components/stories/FilePicker.stories.tsx index 8fd569b06a..f9edcec2bc 100644 --- a/app/client/src/components/stories/FilePicker.stories.tsx +++ b/app/client/src/components/stories/FilePicker.stories.tsx @@ -1,5 +1,5 @@ import React from "react"; -import FilePicker, { CloudinaryUploader } from "../ads/FilePicker"; +import FilePicker, { CloudinaryUploader, FileType } from "../ads/FilePicker"; export default { title: "FilePicker", @@ -12,6 +12,15 @@ function ShowUploadedFile(data: any) { export const withDynamicProps = () => ( ShowUploadedFile(data)} + /> +); + +export const withJsonInputType = () => ( + ShowUploadedFile(data)} /> diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index b8b4711c9a..3f24e7b315 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -418,6 +418,8 @@ export const ReduxActionTypes: { [key: string]: string } = { CURRENT_APPLICATION_LAYOUT_UPDATE: "CURRENT_APPLICATION_LAYOUT_UPDATE", FORK_APPLICATION_INIT: "FORK_APPLICATION_INIT", FORK_APPLICATION_SUCCESS: "FORK_APPLICATION_SUCCESS", + IMPORT_APPLICATION_INIT: "IMPORT_APPLICATION_INIT", + IMPORT_APPLICATION_SUCCESS: "IMPORT_APPLICATION_SUCCESS", SET_WIDGET_LOADING: "SET_WIDGET_LOADING", SET_GLOBAL_SEARCH_QUERY: "SET_GLOBAL_SEARCH_QUERY", TOGGLE_SHOW_GLOBAL_SEARCH_MODAL: "TOGGLE_SHOW_GLOBAL_SEARCH_MODAL", @@ -525,6 +527,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = { SAVE_ACTION_NAME_ERROR: "SAVE_ACTION_NAME_ERROR", FETCH_USER_APPLICATIONS_ORGS_ERROR: "FETCH_USER_APPLICATIONS_ORGS_ERROR", FORK_APPLICATION_ERROR: "FORK_APPLICATION_ERROR", + IMPORT_APPLICATION_ERROR: "IMPORT_APPLICATION_ERROR", FETCH_ALL_USERS_ERROR: "FETCH_ALL_USERS_ERROR", FETCH_ALL_ROLES_ERROR: "FETCH_ALL_ROLES_ERROR", UPDATE_USER_DETAILS_ERROR: "UPDATE_USER_DETAILS_ERROR", diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index 9084eb5eaf..b40f6f66db 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -202,7 +202,7 @@ export const GOOGLE_RECAPTCHA_DOMAIN_ERROR = () => export const SERVER_API_TIMEOUT_ERROR = () => `Appsmith server is taking too long to respond. Please try again after some time`; export const DEFAULT_ERROR_MESSAGE = () => `There was an unexpected error`; - +export const REMOVE_FILE_TOOL_TIP = () => "Remove Upload"; export const ERROR_FILE_TOO_LARGE = (fileSize: string) => `File size should be less than ${fileSize}!`; export const ERROR_DATEPICKER_MIN_DATE = () => @@ -329,3 +329,6 @@ export const OPEN_THE_DEBUGGER = () => " to open the debugger"; export const NO_LOGS = () => "No logs to show"; export const TROUBLESHOOT_ISSUE = () => "Troubleshoot issue"; + +// Import/Export Application features +export const IMPORT_APPLICATION_MODAL_TITLE = () => "Import Application"; diff --git a/app/client/src/pages/Applications/ApplicationCard.tsx b/app/client/src/pages/Applications/ApplicationCard.tsx index 6f6fae0bc4..e65dc5f501 100644 --- a/app/client/src/pages/Applications/ApplicationCard.tsx +++ b/app/client/src/pages/Applications/ApplicationCard.tsx @@ -46,6 +46,8 @@ import { Classes as CsClasses } from "components/ads/common"; import TooltipComponent from "components/ads/Tooltip"; import { isEllipsisActive } from "utils/helpers"; import ForkApplicationModal from "./ForkApplicationModal"; +import { Toaster } from "components/ads/Toast"; +import { Variant } from "components/ads/common"; type NameWrapperProps = { hasReadPermission: boolean; @@ -220,6 +222,7 @@ type ApplicationCardProps = { share?: (applicationId: string) => void; delete?: (applicationId: string) => void; update?: (id: string, data: UpdateApplicationPayload) => void; + enableImportExport?: boolean; }; const EditButton = styled(Button)` @@ -297,6 +300,14 @@ export function ApplicationCard(props: ApplicationCardProps) { cypressSelector: "t--fork-app", }); } + if (!!props.enableImportExport) { + moreActionItems.push({ + onSelect: exportApplicationAsJSONFile, + text: "Export", + icon: "download", + cypressSelector: "t--export-app", + }); + } setMoreActionItems(moreActionItems); addDeleteOption(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -331,6 +342,25 @@ export function ApplicationCard(props: ApplicationCardProps) { const shareApp = () => { props.share && props.share(props.application.id); }; + const exportApplicationAsJSONFile = () => { + // export api response comes with content-disposition header. + // there is no straightforward way to handle it with axios/fetch + const id = `t--export-app-link`; + const existingLink = document.getElementById(id); + existingLink && existingLink.remove(); + const link = document.createElement("a"); + link.href = `/api/v1/applications/export/${props.application.id}`; + link.target = "_blank"; + link.id = id; + document.body.appendChild(link); + link.click(); + setIsMenuOpen(false); + Toaster.show({ + text: `Successfully exported ${props.application.name}`, + variant: Variant.success, + }); + link.remove(); + }; const forkApplicationInitiate = () => { // open fork application modal // on click on an organisation, create app and take to app diff --git a/app/client/src/pages/Applications/ForkModalStyles.ts b/app/client/src/pages/Applications/ForkModalStyles.ts index a79e04f042..2e731b7b57 100644 --- a/app/client/src/pages/Applications/ForkModalStyles.ts +++ b/app/client/src/pages/Applications/ForkModalStyles.ts @@ -26,9 +26,10 @@ const StyledRadioComponent = styled(RadioComponent)` } `; -const ForkButton = styled(Button)` +const ForkButton = styled(Button)<{ disabled?: boolean }>` height: 38px; width: 203px; + pointer-events: ${(props) => (!!props.disabled ? "none" : "auto")}; `; const OrganizationList = styled.div` diff --git a/app/client/src/pages/Applications/ImportApplicationModal.tsx b/app/client/src/pages/Applications/ImportApplicationModal.tsx new file mode 100644 index 0000000000..4f37426224 --- /dev/null +++ b/app/client/src/pages/Applications/ImportApplicationModal.tsx @@ -0,0 +1,117 @@ +import React, { useCallback, useState } from "react"; +import styled from "styled-components"; +import Button, { Size } from "components/ads/Button"; +import { StyledDialog } from "./ForkModalStyles"; +import { useSelector } from "store"; +import { AppState } from "reducers"; +import FilePicker, { SetProgress, FileType } from "components/ads/FilePicker"; +import { useDispatch } from "react-redux"; +import { importApplication } from "actions/applicationActions"; +import { Toaster } from "components/ads/Toast"; +import { Variant } from "components/ads/common"; +import { IMPORT_APPLICATION_MODAL_TITLE } from "constants/messages"; + +const ImportButton = styled(Button)<{ disabled?: boolean }>` + height: 30px; + width: 81px; + pointer-events: ${(props) => (!!props.disabled ? "none" : "auto")}; +`; + +const ButtonWrapper = styled.div` + display: flex; + justify-content: center; +`; + +const FilePickerWrapper = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: center; +`; + +type ImportApplicationModalProps = { + // import?: (file: any) => void; + organizationId?: string; + isModalOpen?: boolean; + onClose?: () => void; +}; + +function ImportApplicationModal(props: ImportApplicationModalProps) { + const { isModalOpen, onClose, organizationId } = props; + const [appFileToBeUploaded, setAppFileToBeUploaded] = useState<{ + file: File; + setProgress: SetProgress; + } | null>(null); + const dispatch = useDispatch(); + + const importingApplication = useSelector( + (state: AppState) => state.ui.applications.importingApplication, + ); + + const FileUploader = useCallback( + async (file: File, setProgress: SetProgress) => { + if (!!file) { + setAppFileToBeUploaded({ + file, + setProgress, + }); + } else { + setAppFileToBeUploaded(null); + } + }, + [], + ); + + const onImportApplication = useCallback(() => { + if (!appFileToBeUploaded) { + Toaster.show({ + text: "Please choose a valid application file!", + variant: Variant.danger, + }); + return; + } + const { file } = appFileToBeUploaded || {}; + + dispatch( + importApplication({ + orgId: organizationId as string, + applicationFile: file, + }), + ); + }, [appFileToBeUploaded, organizationId]); + + const onRemoveFile = useCallback(() => setAppFileToBeUploaded(null), []); + + return ( + + + + + + + + + ); +} + +export default ImportApplicationModal; diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index 3b3282c734..d75a4525f3 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -78,6 +78,7 @@ import WelcomeHelper from "components/editorComponents/Onboarding/WelcomeHelper" import { useIntiateOnboarding } from "components/editorComponents/Onboarding/utils"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { createOrganizationSubmitHandler } from "../organization/helpers"; +import ImportApplicationModal from "./ImportApplicationModal"; const OrgDropDown = styled.div` display: flex; @@ -505,6 +506,7 @@ const NoSearchResultImg = styled.img` `; function ApplicationsSection(props: any) { + const enableImportExport = true; const dispatch = useDispatch(); const theme = useContext(ThemeContext); const isSavingOrgInfo = useSelector(getIsSavingOrgInfo); @@ -534,6 +536,10 @@ function ApplicationsSection(props: any) { }; const [selectedOrgId, setSelectedOrgId] = useState(); + const [ + selectedOrgIdForImportApplication, + setSelectedOrgIdForImportApplication, + ] = useState(); const Form: any = OrgInviteUsersForm; const leaveOrg = (orgId: string) => { @@ -668,6 +674,18 @@ function ApplicationsSection(props: any) { } text="Organization Settings" /> + {enableImportExport && ( + + setSelectedOrgIdForImportApplication( + organization.id, + ) + } + text="Import Application" + /> + )} setSelectedOrgId(organization.id)} @@ -691,7 +709,15 @@ function ApplicationsSection(props: any) { /> )} - + {selectedOrgIdForImportApplication && ( + setSelectedOrgIdForImportApplication("")} + organizationId={selectedOrgIdForImportApplication} + /> + )} {hasManageOrgPermissions && ( diff --git a/app/client/src/pages/organization/General.tsx b/app/client/src/pages/organization/General.tsx index f9bd6e2010..b426b6230d 100644 --- a/app/client/src/pages/organization/General.tsx +++ b/app/client/src/pages/organization/General.tsx @@ -20,6 +20,7 @@ import { getOrgLoadingStates } from "selectors/organizationSelectors"; import FilePicker, { SetProgress, UploadCallback, + FileType, } from "components/ads/FilePicker"; import { getIsFetchingApplications } from "selectors/applicationSelectors"; @@ -171,6 +172,7 @@ export function GeneralSettings() { {isFetchingOrg && } {!isFetchingOrg && ( ({ ...state, importingApplication: true }), + [ReduxActionTypes.IMPORT_APPLICATION_SUCCESS]: ( + state: ApplicationsReduxState, + action: ReduxAction<{ importedApplication: any }>, + ) => { + const { importedApplication } = action.payload; + return { + ...state, + importingApplication: false, + importedApplication, + }; + }, + [ReduxActionErrorTypes.IMPORT_APPLICATION_ERROR]: ( + state: ApplicationsReduxState, + ) => { + return { + ...state, + importingApplication: false, + }; + }, [ReduxActionTypes.SAVING_ORG_INFO]: (state: ApplicationsReduxState) => { return { ...state, @@ -349,6 +373,8 @@ export interface ApplicationsReduxState { currentApplication?: ApplicationPayload; userOrgs: Organization[]; isSavingOrgInfo: boolean; + importingApplication: boolean; + importedApplication: any; showAppInviteUsersDialog: boolean; } diff --git a/app/client/src/sagas/ApplicationSagas.tsx b/app/client/src/sagas/ApplicationSagas.tsx index 5346152181..e5a08dd70c 100644 --- a/app/client/src/sagas/ApplicationSagas.tsx +++ b/app/client/src/sagas/ApplicationSagas.tsx @@ -20,6 +20,7 @@ import ApplicationApi, { PublishApplicationResponse, SetDefaultPageRequest, UpdateApplicationRequest, + ImportApplicationRequest, } from "api/ApplicationApi"; import { all, call, put, select, takeLatest } from "redux-saga/effects"; @@ -55,8 +56,11 @@ import { getCurrentPageId, } from "selectors/editorSelectors"; import { showCompletionDialog } from "./OnboardingSagas"; + import { deleteRecentAppEntities } from "utils/storage"; import { reconnectWebsocket as reconnectWebsocketAction } from "actions/websocketActions"; +import { getCurrentOrg } from "selectors/organizationSelectors"; +import { Org } from "constants/orgConstants"; const getDefaultPageId = ( pages?: ApplicationPagePayload[], @@ -502,6 +506,53 @@ export function* forkApplicationSaga( } } +export function* importApplicationSaga( + action: ReduxAction, +) { + try { + const response: ApiResponse = yield call( + ApplicationApi.importApplicationToOrg, + action.payload, + ); + const isValidResponse = yield validateResponse(response); + if (isValidResponse) { + const allOrgs = yield select(getCurrentOrg); + const currentOrg = allOrgs.filter( + (el: Org) => el.id === action.payload.orgId, + ); + if (currentOrg.length > 0) { + const { + id: appId, + pages, + }: { + id: string; + pages: { default?: boolean; id: string; isDefault?: boolean }[]; + } = response.data; + yield put({ + type: ReduxActionTypes.IMPORT_APPLICATION_SUCCESS, + payload: { + importedApplication: appId, + }, + }); + const defaultPage = pages.filter((eachPage) => !!eachPage.isDefault); + const pageURL = BUILDER_PAGE_URL(appId, defaultPage[0].id); + history.push(pageURL); + Toaster.show({ + text: "Application imported successfully", + variant: Variant.success, + }); + } + } + } catch (error) { + yield put({ + type: ReduxActionErrorTypes.IMPORT_APPLICATION_ERROR, + payload: { + error, + }, + }); + } +} + export default function* applicationSagas() { yield all([ takeLatest( @@ -530,5 +581,6 @@ export default function* applicationSagas() { ReduxActionTypes.DUPLICATE_APPLICATION_INIT, duplicateApplicationSaga, ), + takeLatest(ReduxActionTypes.IMPORT_APPLICATION_INIT, importApplicationSaga), ]); } diff --git a/app/client/src/selectors/ui.ts b/app/client/src/selectors/ui.tsx similarity index 100% rename from app/client/src/selectors/ui.ts rename to app/client/src/selectors/ui.tsx From 53fe96b5cf2ba08e879b3e7e6362dd5fd2af2208 Mon Sep 17 00:00:00 2001 From: Paul Li Date: Thu, 3 Jun 2021 03:43:08 -0400 Subject: [PATCH 049/119] FEATURE-2111 : Show/Hide Table Controls -- Use isHeaderVisible variable for conditional rendering --- .../designSystems/appsmith/TableComponent/Table.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx index c2c8dfe4fd..b9a8b56731 100644 --- a/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx +++ b/app/client/src/components/designSystems/appsmith/TableComponent/Table.tsx @@ -183,11 +183,7 @@ export function Table(props: TableProps) { triggerRowSelection={props.triggerRowSelection} width={props.width} > - {(props.isVisibleSearch || - props.isVisibleFilters || - props.isVisibleDownload || - props.isVisibleCompactMode || - props.isVisiblePagination) && ( + {isHeaderVisible && ( Date: Thu, 3 Jun 2021 18:07:18 +0530 Subject: [PATCH 050/119] Fix to show datasource auth info clearly (#4900) --- .../components/editorComponents/CodeEditor/styledComponents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts b/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts index 4b50cc9fdd..e2d0202f57 100644 --- a/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts +++ b/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts @@ -59,7 +59,7 @@ export const HintStyles = createGlobalStyle<{ } .datasource-hint { - padding: 10px !important; + padding: 10px 20px 10px 10px !important; display: block; width: 500px; height: 32px; From 8008aefcc7f9b7924b956423b88682bc378086cb Mon Sep 17 00:00:00 2001 From: Yash Date: Thu, 3 Jun 2021 20:50:23 +0530 Subject: [PATCH 051/119] FIX #4573 : custom fusion chart sample data make consistent as chartData property --- .../mockResponses/WidgetConfigResponse.tsx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 231fd137da..2ad873bb0c 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -545,42 +545,48 @@ const WidgetConfigResponse: WidgetConfigReducerState = { type: "column2d", dataSource: { chart: { - caption: "Monthly revenue for last year", - subCaption: "Harry's SuperMart", - xAxisName: "Month", - yAxisName: "Revenues (In USD)", - numberPrefix: "$", + caption: "Last week's revenue", + xAxisName: "Last Week", + yAxisName: "Total Order Revenue $", theme: "fusion", }, data: [ { - label: "Jan", - value: "420000", + label: "Mon", + value: 10000, }, { - label: "Feb", - value: "810000", + label: "Tue", + value: 12000, }, { - label: "Mar", - value: "720000", + label: "Wed", + value: 32000, }, { - label: "Apr", - value: "550000", + label: "Thu", + value: 28000, }, { - label: "May", - value: "910000", + label: "Fri", + value: 14000, + }, + { + label: "Sat", + value: 19000, + }, + { + label: "Sun", + value: 36000, }, ], trendlines: [ { line: [ { - startvalue: "700000", + startvalue: "38000", valueOnRight: "1", - displayvalue: "Monthly Target", + displayvalue: "Weekly Target", }, ], }, From 2757f0fe158a4dc3832d09ad382e832280a1608e Mon Sep 17 00:00:00 2001 From: arunvjn <32433245+arunvjn@users.noreply.github.com> Date: Thu, 3 Jun 2021 22:02:59 +0530 Subject: [PATCH 052/119] Fixed mouse click bug in datasource suggestions(#4668) --- .../src/components/editorComponents/CodeEditor/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx index 0f28126d93..00c432efad 100644 --- a/app/client/src/components/editorComponents/CodeEditor/index.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx @@ -255,6 +255,7 @@ class CodeEditor extends Component { }; handleEditorFocus = () => { + if (this.state.isFocused) return; this.setState({ isFocused: true }); this.editor.refresh(); if (this.props.size === EditorSize.COMPACT) { @@ -265,9 +266,9 @@ class CodeEditor extends Component { } }; - handleEditorBlur = () => { + handleEditorBlur = (cm: CodeMirror.Editor) => { this.handleChange(); - this.setState({ isFocused: false }); + if (!cm.state.completionActive) this.setState({ isFocused: false }); if (this.props.size === EditorSize.COMPACT) { this.editor.setOption("lineWrapping", false); } From 8199db669aab350cff10eed233fafab9b13268c7 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Thu, 3 Jun 2021 23:31:37 +0530 Subject: [PATCH 053/119] - remove DB Auth type check. (#4896) * remove DB Auth type check, since the type is not getting set and the check does not seem to be required in any way. --- .../src/main/java/com/external/plugins/RedisPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java index f5dca31ecb..394366dc28 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java @@ -150,7 +150,7 @@ public class RedisPlugin extends BasePlugin { Jedis jedis = new Jedis(endpoint.getHost(), port); DBAuth auth = (DBAuth) datasourceConfiguration.getAuthentication(); - if (auth != null && DBAuth.Type.USERNAME_PASSWORD.equals(auth.getAuthType())) { + if (auth != null) { jedis.auth(auth.getUsername(), auth.getPassword()); } From 5cf5160c995f77cf6aa54905b1df91ad6432e8e9 Mon Sep 17 00:00:00 2001 From: Nayan <83352306+nayan-rafiq@users.noreply.github.com> Date: Fri, 4 Jun 2021 00:36:34 +0600 Subject: [PATCH 054/119] =?UTF-8?q?bugfix:=20redirect=20user=20to=20homepa?= =?UTF-8?q?ge=20when=20user=20was=20inside=20an=20org=20and=20lef=E2=80=A6?= =?UTF-8?q?=20(#4905)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * bugfix: redirect user to homepage when user was inside an org and left that organization * -removed a console log --- app/client/src/sagas/OrgSagas.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/client/src/sagas/OrgSagas.ts b/app/client/src/sagas/OrgSagas.ts index 201263b504..a7158c54ca 100644 --- a/app/client/src/sagas/OrgSagas.ts +++ b/app/client/src/sagas/OrgSagas.ts @@ -28,8 +28,10 @@ import { ApiResponse } from "api/ApiResponses"; import { Toaster } from "components/ads/Toast"; import { Variant } from "components/ads/common"; import { getCurrentOrg } from "selectors/organizationSelectors"; +import { getCurrentUser } from "selectors/usersSelectors"; import { Org } from "constants/orgConstants"; import history from "utils/history"; +import { APPLICATIONS_URL } from "constants/routes"; import { getAllApplications } from "actions/applicationActions"; import log from "loglevel"; @@ -133,12 +135,17 @@ export function* deleteOrgUserSaga(action: ReduxAction) { const response: ApiResponse = yield call(OrgApi.deleteOrgUser, request); const isValidResponse = yield validateResponse(response); if (isValidResponse) { - yield put({ - type: ReduxActionTypes.DELETE_ORG_USER_SUCCESS, - payload: { - username: action.payload.username, - }, - }); + const currentUser = yield select(getCurrentUser); + if (currentUser?.username == action.payload.username) { + history.replace(APPLICATIONS_URL); + } else { + yield put({ + type: ReduxActionTypes.DELETE_ORG_USER_SUCCESS, + payload: { + username: action.payload.username, + }, + }); + } Toaster.show({ text: `${response.data.username} has been removed successfully`, variant: Variant.success, From 98d5a0ff50c9fd937fef6dd723ef91948d4381af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Jun 2021 10:19:06 +0530 Subject: [PATCH 055/119] Bump httpclient from 4.5.10 to 4.5.13 in /app/server/appsmith-server (#4917) Bumps httpclient from 4.5.10 to 4.5.13. --- updated-dependencies: - dependency-name: org.apache.httpcomponents:httpclient dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/server/appsmith-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml index 46f27e8d23..aab5928284 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -207,7 +207,7 @@ org.apache.httpcomponents httpclient - 4.5.10 + 4.5.13 From a7a7390d081fc8c444a5a7c1cc48204b8e786390 Mon Sep 17 00:00:00 2001 From: arunvjn <32433245+arunvjn@users.noreply.github.com> Date: Fri, 4 Jun 2021 11:09:31 +0530 Subject: [PATCH 056/119] Fixed overlapping dropdown in MYSQL forms (#4914) --- .../src/components/designSystems/appsmith/Dropdown.tsx | 3 +++ .../form/fields/EmbeddedDatasourcePathField.tsx | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/app/client/src/components/designSystems/appsmith/Dropdown.tsx b/app/client/src/components/designSystems/appsmith/Dropdown.tsx index 92e1a76645..2053b0310f 100644 --- a/app/client/src/components/designSystems/appsmith/Dropdown.tsx +++ b/app/client/src/components/designSystems/appsmith/Dropdown.tsx @@ -50,12 +50,15 @@ const selectStyles = { padding: "5px", }), indicatorSeparator: () => ({}), + menu: (provided: any) => ({ ...provided, zIndex: 2 }), + menuPortal: (base: any) => ({ ...base, zIndex: 2 }), }; export function BaseDropdown(props: DropdownProps) { const { customSelectStyles, input } = props; return ( { global label="Search entities" onKeyDown={(e: any) => { - const entitySearchInput = document.getElementById( - ENTITY_EXPLORER_SEARCH_ID, - ); const widgetSearchInput = document.getElementById( WIDGETS_SEARCH_ID, ); - if (entitySearchInput) entitySearchInput.focus(); - if (widgetSearchInput) widgetSearchInput.focus(); - e.preventDefault(); - e.stopPropagation(); + if (widgetSearchInput) { + widgetSearchInput.focus(); + e.preventDefault(); + e.stopPropagation(); + } }} /> Date: Wed, 9 Jun 2021 18:15:41 +0530 Subject: [PATCH 092/119] Feature: Show entity dependencies in debugger (#4356) --- .../fixtures/debuggerDependencyDsl.json | 58 ++++++ .../Debugger/Inspect_Element_spec.js | 18 ++ .../src/assets/images/InspectElement.svg | 32 ++++ .../editorComponents/ApiResponseView.tsx | 18 +- .../Debugger/DebuggerLogs.tsx | 3 +- .../Debugger/DebuggerTabs.tsx | 16 +- .../Debugger/EntityDependecies.tsx | 176 ++++++++++++++++++ .../editorComponents/Debugger/helpers.test.ts | 26 +++ .../editorComponents/Debugger/helpers.tsx | 111 +++++++---- .../editorComponents/Debugger/hooks.ts | 144 ++++++++++++++ app/client/src/constants/DefaultTheme.tsx | 11 +- app/client/src/constants/messages.ts | 4 + .../Editor/QueryEditor/EditorJSONtoForm.tsx | 16 +- 13 files changed, 584 insertions(+), 49 deletions(-) create mode 100644 app/client/cypress/fixtures/debuggerDependencyDsl.json create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js create mode 100644 app/client/src/assets/images/InspectElement.svg create mode 100644 app/client/src/components/editorComponents/Debugger/EntityDependecies.tsx create mode 100644 app/client/src/components/editorComponents/Debugger/helpers.test.ts create mode 100644 app/client/src/components/editorComponents/Debugger/hooks.ts diff --git a/app/client/cypress/fixtures/debuggerDependencyDsl.json b/app/client/cypress/fixtures/debuggerDependencyDsl.json new file mode 100644 index 0000000000..d023180e24 --- /dev/null +++ b/app/client/cypress/fixtures/debuggerDependencyDsl.json @@ -0,0 +1,58 @@ +{ + "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, + "text": "Submit", + "buttonStyle": "PRIMARY_BUTTON", + "widgetName": "Button1", + "isDisabled": false, + "isDefaultClickDisabled": true, + "type": "BUTTON_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, + "leftColumn": 5, + "rightColumn": 7, + "topRow": 2, + "bottomRow": 3, + "parentId": "0", + "widgetId": "3qg87le9t4" + }, + { + "isVisible": true, + "inputType": "TEXT", + "label": "", + "widgetName": "Input1", + "type": "INPUT_WIDGET", + "isLoading": false, + "parentColumnSpace": 74, + "parentRowSpace": 40, + "leftColumn": 2, + "rightColumn": 7, + "topRow": 0, + "bottomRow": 1, + "parentId": "0", + "widgetId": "2lhsjdd5sg" + } + ] + } + } \ No newline at end of file diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js new file mode 100644 index 0000000000..c492494e88 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Inspect_Element_spec.js @@ -0,0 +1,18 @@ +const dsl = require("../../../../fixtures/debuggerDependencyDsl.json"); + +describe("Inspect Entity", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Check whether depedencies and references are shown correctly", function() { + cy.openPropertyPane("inputwidget"); + cy.testJsontext("defaulttext", "{{Button1.text}}"); + + cy.get(".t--debugger").click(); + cy.contains(".react-tabs__tab", "Inspect Entity").click(); + + cy.openPropertyPane("inputwidget"); + cy.contains(".t--dependencies-item", "Button1").click(); + cy.contains(".t--references-item", "Input1"); + }); +}); diff --git a/app/client/src/assets/images/InspectElement.svg b/app/client/src/assets/images/InspectElement.svg new file mode 100644 index 0000000000..75a86413ce --- /dev/null +++ b/app/client/src/assets/images/InspectElement.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index 145807ae65..bb56ee5406 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -13,7 +13,13 @@ import { getActionResponses } from "selectors/entitiesSelector"; import { Colors } from "constants/Colors"; import _ from "lodash"; import { useLocalStorage } from "utils/hooks/localstorage"; -import { CHECK_REQUEST_BODY, createMessage } from "constants/messages"; +import { + CHECK_REQUEST_BODY, + createMessage, + DEBUGGER_ERRORS, + DEBUGGER_LOGS, + INSPECT_ENTITY, +} from "constants/messages"; import { TabComponent } from "components/ads/Tabs"; import Text, { TextType } from "components/ads/Text"; import Icon from "components/ads/Icon"; @@ -25,6 +31,7 @@ import ErrorLogs from "./Debugger/Errors"; import Resizer, { ResizerCSS } from "./Debugger/Resizer"; import AnalyticsUtil from "utils/AnalyticsUtil"; import { DebugButton } from "./Debugger/DebugCTA"; +import EntityDeps from "./Debugger/EntityDependecies"; const ResponseContainer = styled.div` ${ResizerCSS} @@ -228,14 +235,19 @@ function ApiResponseView(props: Props) { }, { key: "ERROR", - title: "Errors", + title: createMessage(DEBUGGER_ERRORS), panelComponent: , }, { key: "LOGS", - title: "Logs", + title: createMessage(DEBUGGER_LOGS), panelComponent: , }, + { + key: "ENTITY_DEPENDENCIES", + title: createMessage(INSPECT_ENTITY), + panelComponent: , + }, ]; const onTabSelect = (index: number) => { diff --git a/app/client/src/components/editorComponents/Debugger/DebuggerLogs.tsx b/app/client/src/components/editorComponents/Debugger/DebuggerLogs.tsx index 3ff07385a2..ece100822f 100644 --- a/app/client/src/components/editorComponents/Debugger/DebuggerLogs.tsx +++ b/app/client/src/components/editorComponents/Debugger/DebuggerLogs.tsx @@ -3,8 +3,9 @@ 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 { BlankState } from "./helpers"; import LogItem, { getLogItemProps } from "./LogItem"; +import { usePagination, useFilteredLogs } from "./hooks"; const LIST_HEADER_HEIGHT = "38px"; diff --git a/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx b/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx index a7641165a9..03a260a9c5 100644 --- a/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx +++ b/app/client/src/components/editorComponents/Debugger/DebuggerTabs.tsx @@ -8,6 +8,13 @@ import { showDebugger } from "actions/debuggerActions"; import Errors from "./Errors"; import Resizer, { ResizerCSS } from "./Resizer"; import AnalyticsUtil from "utils/AnalyticsUtil"; +import EntityDeps from "./EntityDependecies"; +import { + createMessage, + DEBUGGER_ERRORS, + DEBUGGER_LOGS, + INSPECT_ENTITY, +} from "constants/messages"; const TABS_HEADER_HEIGHT = 36; @@ -41,14 +48,19 @@ type DebuggerTabsProps = { const DEBUGGER_TABS = [ { key: "ERROR", - title: "Errors", + title: createMessage(DEBUGGER_ERRORS), panelComponent: , }, { key: "LOGS", - title: "Logs", + title: createMessage(DEBUGGER_LOGS), panelComponent: , }, + { + key: "INSPECT_ELEMENTS", + title: createMessage(INSPECT_ENTITY), + panelComponent: , + }, ]; function DebuggerTabs(props: DebuggerTabsProps) { diff --git a/app/client/src/components/editorComponents/Debugger/EntityDependecies.tsx b/app/client/src/components/editorComponents/Debugger/EntityDependecies.tsx new file mode 100644 index 0000000000..76672346ed --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/EntityDependecies.tsx @@ -0,0 +1,176 @@ +/* eslint-disable prefer-const */ +import { Collapse } from "@blueprintjs/core"; +import React, { memo, ReactNode, useMemo, useState } from "react"; +import { useSelector } from "react-redux"; +import { AppState } from "reducers"; +import styled from "styled-components"; +import Icon, { IconSize } from "components/ads/Icon"; +import { Classes } from "components/ads/common"; +import InspectElement from "assets/images/InspectElement.svg"; +import { SourceEntity } from "entities/AppsmithConsole"; +import { createMessage, INSPECT_ENTITY_BLANK_STATE } from "constants/messages"; +import { getDependenciesFromInverseDependencies } from "./helpers"; +import { useEntityLink, useSelectedEntity } from "./hooks"; + +const CollapsibleWrapper = styled.div<{ step: number; isOpen: boolean }>` + margin-left: ${(props) => props.step * 10}px; + padding-top: ${(props) => props.theme.spaces[3]}px; + + .label-wrapper { + display: flex; + flex-direction: row; + font-weight: ${(props) => props.theme.fontWeights[2]}; + + span { + margin-left: ${(props) => props.theme.spaces[3] - 1}px; + } + } + + .${Classes.ICON} { + ${(props) => !props.isOpen && `transform: rotate(-90deg);`} + } +`; + +const DependenciesWrapper = styled.div` + padding: ${(props) => props.theme.spaces[7]}px + ${(props) => props.theme.spaces[13] + 1}px; + color: ${(props) => props.theme.colors.debugger.inspectElement.color}; + + .no-dependencies { + margin-left: ${(props) => props.theme.spaces[4]}px; + } +`; + +const StyledSpan = styled.div<{ step: number }>` + padding-top: ${(props) => props.theme.spaces[3]}px; + padding-left: ${(props) => props.theme.spaces[6] + 1}px; + margin-left: ${(props) => props.theme.spaces[4]}px; + border-left: solid 1px rgba(147, 144, 144, 0.7); + text-decoration-line: underline; + cursor: pointer; +`; + +const BlankStateContainer = styled.div` + height: 100%; + display: flex; + align-items: center; + justify-content: center; + flex: 1; + flex-direction: column; + color: ${(props) => props.theme.colors.debugger.blankState.color}; + + span { + margin-top: ${(props) => props.theme.spaces[9] + 1}px; + } +`; + +function EntityDeps() { + const deps = useSelector((state: AppState) => state.evaluations.dependencies); + const selectedEntity = useSelectedEntity(); + + const entityDependencies: { + directDependencies: string[]; + inverseDependencies: string[]; + } | null = useMemo( + () => + getDependenciesFromInverseDependencies( + deps.inverseDependencyMap, + selectedEntity ? selectedEntity.name : null, + ), + [selectedEntity, deps.inverseDependencyMap], + ); + + if (!selectedEntity || !entityDependencies) return ; + + return ( +
+ + +
+ ); +} + +function BlankState() { + return ( + + + {createMessage(INSPECT_ENTITY_BLANK_STATE)} + + ); +} + +function DependencyHierarchy(props: { + dependencies: string[]; + entityName: string; + selectedEntity: SourceEntity; + type: string; +}) { + const { navigateToEntity } = useEntityLink(); + const label = props.dependencies.length + ? props.entityName + : `No ${props.type} exist for ${props.selectedEntity.name}`; + + return ( + + {props.dependencies.length ? ( + + {props.dependencies.map((item) => { + return ( + { + e.stopPropagation(); + navigateToEntity(item); + }} + step={2} + > + {item} + + ); + })} + + ) : ( + {label} + )} + + ); +} +const MemoizedDependencyHierarchy = memo(DependencyHierarchy); + +function Collapsible(props: { + label: string; + step: number; + children: ReactNode; +}) { + const [isOpen, setIsOpen] = useState(true); + + return ( + { + e.stopPropagation(); + setIsOpen(!isOpen); + }} + step={props.step} + > +
+ + {props.label} +
+ {props.children} +
+ ); +} + +export default EntityDeps; diff --git a/app/client/src/components/editorComponents/Debugger/helpers.test.ts b/app/client/src/components/editorComponents/Debugger/helpers.test.ts new file mode 100644 index 0000000000..4d4bd71ea0 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/helpers.test.ts @@ -0,0 +1,26 @@ +import { getDependenciesFromInverseDependencies } from "./helpers"; + +describe("getDependencies", () => { + it("Check if getDependencies returns in a correct format", () => { + const input = { + "Button1.text": ["Input1.defaultText", "Button1"], + "Input1.defaultText": ["Input1.text", "Input1"], + "Input1.inputType": ["Input1.isValid", "Input1"], + "Input1.text": ["Input1.isValid", "Input1.value", "Input1"], + "Input1.isRequired": ["Input1.isValid", "Input1"], + "Input1.isValid": ["Button1.isVisible", "Input1"], + "Button1.isVisible": ["Button1"], + Button1: ["Chart1.chartName"], + "Chart1.chartName": ["Chart1"], + "Input1.value": ["Input1"], + }; + const output = { + directDependencies: ["Input1"], + inverseDependencies: ["Input1", "Chart1"], + }; + + expect( + getDependenciesFromInverseDependencies(input, "Button1"), + ).toStrictEqual(output); + }); +}); diff --git a/app/client/src/components/editorComponents/Debugger/helpers.tsx b/app/client/src/components/editorComponents/Debugger/helpers.tsx index 29e019b9cf..4f1c1a5584 100644 --- a/app/client/src/components/editorComponents/Debugger/helpers.tsx +++ b/app/client/src/components/editorComponents/Debugger/helpers.tsx @@ -1,7 +1,5 @@ -import { Message, Severity } from "entities/AppsmithConsole"; -import React, { useCallback, useEffect, useState } from "react"; -import { useSelector } from "react-redux"; -import { AppState } from "reducers"; +import { Severity } from "entities/AppsmithConsole"; +import React from "react"; import styled from "styled-components"; import { getTypographyByKey } from "constants/DefaultTheme"; import { @@ -10,6 +8,12 @@ import { OPEN_THE_DEBUGGER, PRESS, } from "constants/messages"; +import { DependencyMap } from "utils/DynamicBindingUtils"; +import { + API_EDITOR_URL, + QUERIES_EDITOR_URL, + BUILDER_PAGE_URL, +} from "constants/routes"; const BlankStateWrapper = styled.div` overflow: auto; @@ -54,46 +58,73 @@ export const SeverityIconColor: Record = { [Severity.WARNING]: "rgb(224, 179, 14)", }; -export const useFilteredLogs = (query: string, filter?: any) => { - let logs = useSelector((state: AppState) => state.ui.debugger.logs); +export function getDependenciesFromInverseDependencies( + deps: DependencyMap, + entityName: string | null, +) { + if (!entityName) return null; - if (filter) { - logs = logs.filter((log: Message) => log.severity === filter); - } + const directDependencies = new Set(); + const inverseDependencies = new Set(); - if (query) { - logs = logs.filter((log: Message) => { - if (log.source?.name) - return ( - log.source?.name.toUpperCase().indexOf(query.toUpperCase()) !== -1 - ); + Object.entries(deps).forEach(([dependant, dependencies]) => { + (dependencies as any).map((dependency: any) => { + if (!dependant.includes(entityName) && dependency.includes(entityName)) { + const entity = dependant + .split(".") + .slice(0, 1) + .join(""); + + directDependencies.add(entity); + } else if ( + dependant.includes(entityName) && + !dependency.includes(entityName) + ) { + const entity = dependency + .split(".") + .slice(0, 1) + .join(""); + + inverseDependencies.add(entity); + } }); - } + }); - return logs; + return { + inverseDependencies: Array.from(inverseDependencies), + directDependencies: Array.from(directDependencies), + }; +} + +export const onApiEditor = ( + applicationId: string | undefined, + currentPageId: string | undefined, +) => { + return ( + window.location.pathname.indexOf( + API_EDITOR_URL(applicationId, currentPageId), + ) > -1 + ); }; -export const usePagination = (data: Message[], itemsPerPage = 50) => { - const [currentPage, setCurrentPage] = useState(1); - const [paginatedData, setPaginatedData] = useState([]); - 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 }; +export const onQueryEditor = ( + applicationId: string | undefined, + currentPageId: string | undefined, +) => { + return ( + window.location.pathname.indexOf( + QUERIES_EDITOR_URL(applicationId, currentPageId), + ) > -1 + ); +}; + +export const onCanvas = ( + applicationId: string | undefined, + currentPageId: string | undefined, +) => { + return ( + window.location.pathname.indexOf( + BUILDER_PAGE_URL(applicationId, currentPageId), + ) > -1 + ); }; diff --git a/app/client/src/components/editorComponents/Debugger/hooks.ts b/app/client/src/components/editorComponents/Debugger/hooks.ts new file mode 100644 index 0000000000..aca5c9f578 --- /dev/null +++ b/app/client/src/components/editorComponents/Debugger/hooks.ts @@ -0,0 +1,144 @@ +import { useCallback, useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import { useParams } from "react-router"; +import { ENTITY_TYPE, Message } from "entities/AppsmithConsole"; +import { AppState } from "reducers"; +import { getActionConfig } from "pages/Editor/Explorer/Actions/helpers"; +import { useNavigateToWidget } from "pages/Editor/Explorer/Widgets/WidgetEntity"; +import { getWidget } from "sagas/selectors"; +import { getDataTree } from "selectors/dataTreeSelectors"; +import { + getCurrentApplicationId, + getCurrentPageId, +} from "selectors/editorSelectors"; +import { getAction } from "selectors/entitiesSelector"; +import { + getCurrentWidgetId, + getIsPropertyPaneVisible, +} from "selectors/propertyPaneSelectors"; +import { isWidget, isAction } from "workers/evaluationUtils"; +import { onApiEditor, onQueryEditor, onCanvas } from "./helpers"; +import history from "utils/history"; + +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([]); + 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 }; +}; + +export const useSelectedEntity = () => { + const applicationId = useSelector(getCurrentApplicationId); + const currentPageId = useSelector(getCurrentPageId); + + const params: any = useParams(); + const action = useSelector((state: AppState) => { + if ( + onApiEditor(applicationId, currentPageId) || + onQueryEditor(applicationId, currentPageId) + ) { + const id = params.apiId || params.queryId; + + return getAction(state, id); + } + + return null; + }); + + const isPropertyPaneVisible = useSelector(getIsPropertyPaneVisible); + const selectedWidget = useSelector(getCurrentWidgetId); + const widget = useSelector((state: AppState) => { + if (onCanvas(applicationId, currentPageId) && isPropertyPaneVisible) { + return selectedWidget ? getWidget(state, selectedWidget) : null; + } + + return null; + }); + + if ( + onApiEditor(applicationId, currentPageId) || + onQueryEditor(applicationId, currentPageId) + ) { + return { + name: action?.name ?? "", + type: ENTITY_TYPE.ACTION, + id: action?.id ?? "", + }; + } else if (onCanvas(applicationId, currentPageId)) { + return { + name: widget?.widgetName ?? "", + type: ENTITY_TYPE.WIDGET, + id: widget?.widgetId ?? "", + }; + } + + return null; +}; + +export const useEntityLink = () => { + const dataTree = useSelector(getDataTree); + const applicationId = useSelector(getCurrentApplicationId); + const pageId = useSelector(getCurrentPageId); + + const { navigateToWidget } = useNavigateToWidget(); + + const navigateToEntity = useCallback( + (name) => { + const entity = dataTree[name]; + if (isWidget(entity)) { + navigateToWidget(entity.widgetId, entity.type, pageId || ""); + } else if (isAction(entity)) { + const actionConfig = getActionConfig(entity.pluginType); + const url = + applicationId && + actionConfig?.getURL(applicationId, pageId || "", entity.actionId); + + if (url) { + history.push(url); + } + } + }, + [dataTree], + ); + + return { + navigateToEntity, + }; +}; diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 4571dd04f7..b24ad0574e 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -990,6 +990,9 @@ type ColorType = { label: string; entity: string; entityLink: string; + inspectElement: { + color: string; + }; floatingButton: { background: string; color: string; @@ -1595,6 +1598,9 @@ export const dark: ColorType = { errorCount: "#F22B2B", noErrorCount: "#03B365", }, + inspectElement: { + color: "#D4D4D4", + }, blankState: { color: "#D4D4D4", shortcut: "#D4D4D4", @@ -2045,8 +2051,11 @@ export const light: ColorType = { errorCount: "#F22B2B", noErrorCount: "#03B365", }, + inspectElement: { + color: "#090707", + }, blankState: { - color: "#716e6e", + color: "#090707", shortcut: "black", }, info: { diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index 2ee2e27172..e9b31cfe34 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -328,6 +328,10 @@ export const CLICK_ON = () => "🙌 Click on "; export const PRESS = () => "🎉 Press "; export const OPEN_THE_DEBUGGER = () => " to open the debugger"; export const NO_LOGS = () => "No logs to show"; +export const DEBUGGER_ERRORS = () => "Errors"; +export const DEBUGGER_LOGS = () => "Logs"; +export const INSPECT_ENTITY = () => "Inspect Entity"; +export const INSPECT_ENTITY_BLANK_STATE = () => "Select an entity to inspect"; export const TROUBLESHOOT_ISSUE = () => "Troubleshoot issue"; diff --git a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx index d6fbe5e8be..004f287434 100644 --- a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx +++ b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx @@ -47,7 +47,14 @@ import CloseEditor from "components/editorComponents/CloseEditor"; import { setGlobalSearchQuery } from "actions/globalSearchActions"; import { toggleShowGlobalSearchModal } from "actions/globalSearchActions"; import { omnibarDocumentationHelper } from "constants/OmnibarDocumentationConstants"; +import EntityDeps from "components/editorComponents/Debugger/EntityDependecies"; import { isHidden } from "components/formControls/utils"; +import { + createMessage, + DEBUGGER_ERRORS, + DEBUGGER_LOGS, + INSPECT_ENTITY, +} from "constants/messages"; const QueryFormContainer = styled.form` display: flex; @@ -537,14 +544,19 @@ export function EditorJSONtoForm(props: Props) { }, { key: "ERROR", - title: "Errors", + title: createMessage(DEBUGGER_ERRORS), panelComponent: , }, { key: "LOGS", - title: "Logs", + title: createMessage(DEBUGGER_LOGS), panelComponent: , }, + { + key: "ENTITY_DEPENDENCIES", + title: createMessage(INSPECT_ENTITY), + panelComponent: , + }, ]; const onTabSelect = (index: number) => { From 9c75596957323aa05c91c5762cabf9887dc456dc Mon Sep 17 00:00:00 2001 From: Rishabh Saxena Date: Wed, 9 Jun 2021 20:02:17 +0530 Subject: [PATCH 093/119] Init notifications (#4839) --- app/client/src/actions/commentActions.ts | 11 -- app/client/src/actions/notificationActions.ts | 31 ++++ app/client/src/api/NotificationsAPI.tsx | 18 +++ app/client/src/assets/icons/ads/bell.svg | 3 + .../src/comments/CommentCard/CommentCard.tsx | 38 ++--- app/client/src/comments/init.ts | 31 ---- app/client/src/comments/utils.ts | 32 ++++ app/client/src/constants/DefaultTheme.tsx | 12 ++ .../src/constants/ReduxActionConstants.tsx | 8 + app/client/src/constants/messages.ts | 1 + app/client/src/entities/Notification.ts | 12 ++ app/client/src/notifications/Bell.tsx | 107 +++++++++++++ .../notifications/NotificationListItem.tsx | 141 ++++++++++++++++++ .../src/notifications/NotificationsList.tsx | 104 +++++++++++++ app/client/src/pages/common/PageHeader.tsx | 38 +++-- app/client/src/reducers/index.tsx | 2 + .../commentsReducer/commentsReducer.ts | 8 - app/client/src/reducers/uiReducers/index.tsx | 2 + .../uiReducers/notificationsReducer.ts | 54 +++++++ .../sagas/CommentSagas/handleCommentEvents.ts | 37 ----- app/client/src/sagas/CommentSagas/index.ts | 80 ++-------- app/client/src/sagas/InitSagas.ts | 7 - app/client/src/sagas/NotificationsSagas.ts | 47 ++++++ .../{ => WebsocketSagas}/WebsocketSagas.ts | 8 +- .../src/sagas/WebsocketSagas/constants.ts | 11 ++ .../sagas/WebsocketSagas/handleSocketEvent.ts | 38 +++++ app/client/src/sagas/index.tsx | 4 +- .../src/selectors/notificationSelectors.ts | 10 ++ app/client/test/sagas.ts | 2 +- 29 files changed, 696 insertions(+), 201 deletions(-) create mode 100644 app/client/src/actions/notificationActions.ts create mode 100644 app/client/src/api/NotificationsAPI.tsx create mode 100644 app/client/src/assets/icons/ads/bell.svg delete mode 100644 app/client/src/comments/init.ts create mode 100644 app/client/src/entities/Notification.ts create mode 100644 app/client/src/notifications/Bell.tsx create mode 100644 app/client/src/notifications/NotificationListItem.tsx create mode 100644 app/client/src/notifications/NotificationsList.tsx create mode 100644 app/client/src/reducers/uiReducers/notificationsReducer.ts delete mode 100644 app/client/src/sagas/CommentSagas/handleCommentEvents.ts create mode 100644 app/client/src/sagas/NotificationsSagas.ts rename app/client/src/sagas/{ => WebsocketSagas}/WebsocketSagas.ts (96%) create mode 100644 app/client/src/sagas/WebsocketSagas/constants.ts create mode 100644 app/client/src/sagas/WebsocketSagas/handleSocketEvent.ts create mode 100644 app/client/src/selectors/notificationSelectors.ts diff --git a/app/client/src/actions/commentActions.ts b/app/client/src/actions/commentActions.ts index 114e1277b0..39d0d071aa 100644 --- a/app/client/src/actions/commentActions.ts +++ b/app/client/src/actions/commentActions.ts @@ -19,17 +19,6 @@ export const setCommentThreadsRequest = () => ({ type: ReduxActionTypes.SET_COMMENT_THREADS_REQUEST, }); -// todo remove (for dev) -export const setCommentThreadsSuccess = (payload: any) => ({ - type: ReduxActionTypes.SET_COMMENT_THREADS_SUCCESS, - payload, -}); - -// todo remove (for dev) -export const initCommentThreads = () => ({ - type: ReduxActionTypes.INIT_COMMENT_THREADS, -}); - export const commentEvent = (payload: CommentEventPayload) => ({ type: COMMENT_EVENTS_CHANNEL, payload, diff --git a/app/client/src/actions/notificationActions.ts b/app/client/src/actions/notificationActions.ts new file mode 100644 index 0000000000..a44b88b47b --- /dev/null +++ b/app/client/src/actions/notificationActions.ts @@ -0,0 +1,31 @@ +import { ReduxActionTypes } from "constants/ReduxActionConstants"; +import { AppsmithNotification } from "entities/Notification"; + +export const fetchNotificationsRequest = () => ({ + type: ReduxActionTypes.FETCH_NOTIFICATIONS_REQUEST, +}); + +export const fetchNotificationsSuccess = (payload: { + notifications: Array; +}) => ({ + type: ReduxActionTypes.FETCH_NOTIFICATIONS_SUCCESS, + payload, +}); + +export const newNotificationEvent = (payload: Notification) => ({ + type: ReduxActionTypes.NEW_NOTIFICATION_EVENT, + payload, +}); + +export const setIsNotificationsListVisible = (payload: boolean) => ({ + type: ReduxActionTypes.SET_IS_NOTIFICATIONS_LIST_VISIBLE, + payload, +}); + +export const markAllNotificationsAsReadRequest = () => ({ + type: ReduxActionTypes.MARK_ALL_NOTIFICATIONS_AS_READ_REQUEST, +}); + +export const markAllNotificationsAsReadSuccess = () => ({ + type: ReduxActionTypes.MARK_ALL_NOTIFICATIONS_AS_READ_SUCCESS, +}); diff --git a/app/client/src/api/NotificationsAPI.tsx b/app/client/src/api/NotificationsAPI.tsx new file mode 100644 index 0000000000..c1f981c4e5 --- /dev/null +++ b/app/client/src/api/NotificationsAPI.tsx @@ -0,0 +1,18 @@ +import { AxiosPromise } from "axios"; +import Api from "./Api"; +import { ApiResponse } from "./ApiResponses"; + +class NotificaitonsApi extends Api { + static baseURL = "v1/notifications"; + + static fetchNotifications(): AxiosPromise { + return Api.get(NotificaitonsApi.baseURL); + } + + // TODO update mark all as read notifications api + static markAllNotificationsAsRead(): AxiosPromise { + return Api.get(NotificaitonsApi.baseURL); + } +} + +export default NotificaitonsApi; diff --git a/app/client/src/assets/icons/ads/bell.svg b/app/client/src/assets/icons/ads/bell.svg new file mode 100644 index 0000000000..397af1d327 --- /dev/null +++ b/app/client/src/assets/icons/ads/bell.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/comments/CommentCard/CommentCard.tsx b/app/client/src/comments/CommentCard/CommentCard.tsx index 398fedc206..c73f417f6d 100644 --- a/app/client/src/comments/CommentCard/CommentCard.tsx +++ b/app/client/src/comments/CommentCard/CommentCard.tsx @@ -31,6 +31,8 @@ import history from "utils/history"; import UserApi from "api/UserApi"; +import { getCommentThreadURL } from "../utils"; + import { deleteCommentRequest, markThreadAsReadRequest, @@ -47,6 +49,10 @@ import { createMessage, LINK_COPIED_SUCCESSFULLY } from "constants/messages"; import { Variant } from "components/ads/common"; import TourTooltipWrapper from "components/ads/tour/TourTooltipWrapper"; import { TourType } from "entities/Tour"; +import { + getCurrentApplicationId, + getCurrentPageId, +} from "selectors/editorSelectors"; const StyledContainer = styled.div` width: 100%; @@ -268,26 +274,23 @@ function CommentCard({ pinnedBy = "You"; } - const getCommentURL = () => { - const url = new URL(window.location.href); - // we only link the comment thread currently - // url.searchParams.set("commentId", commentId); - url.searchParams.set("commentThreadId", commentThreadId); - url.searchParams.set("isCommentMode", "true"); - if (commentThread.resolvedState?.active) { - url.searchParams.set("isResolved", "true"); - } - return url; - }; + const pageId = useSelector(getCurrentPageId); + const applicationId = useSelector(getCurrentApplicationId); - const copyCommentLink = useCallback(() => { - const url = getCommentURL(); - copy(url.toString()); + const commentThreadURL = getCommentThreadURL({ + applicationId, + commentThreadId, + isResolved: !!commentThread?.resolvedState?.active, + pageId, + }); + + const copyCommentLink = () => { + copy(commentThreadURL.toString()); Toaster.show({ text: createMessage(LINK_COPIED_SUCCESSFULLY), variant: Variant.success, }); - }, []); + }; const pin = useCallback(() => { dispatch( @@ -330,8 +333,9 @@ function CommentCard({ // Dont make inline cards clickable const handleCardClick = () => { if (inline) return; - const url = getCommentURL(); - history.push(`${url.pathname}${url.search}${url.hash}`); + history.push( + `${commentThreadURL.pathname}${commentThreadURL.search}${commentThreadURL.hash}`, + ); if (!commentThread.isViewed) { dispatch(markThreadAsReadRequest(commentThreadId)); } diff --git a/app/client/src/comments/init.ts b/app/client/src/comments/init.ts deleted file mode 100644 index f3db7d4a4b..0000000000 --- a/app/client/src/comments/init.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { updateAndSaveLayout } from "actions/pageActions"; -import { uniqueId } from "lodash"; - -const dsl = require("./dsl.json"); - -export const updateLayout = () => updateAndSaveLayout(dsl.widgets as any); - -export const getTestComments = () => { - const commentThreads = Object.entries(dsl.widgets).map(([widgetId]) => { - return { - refId: widgetId, - position: { top: 10, left: 15 }, - id: uniqueId(), - comments: [{ body: widgetId, authorName: uniqueId() }], - }; - }); - - // const [widgetId] = Object.entries(dsl.widgets)[0]; - // const commentThreads = [ - // { - // refId: widgetId, - // meta: { - // position: { top: 10, left: 15 }, - // }, - // id: `${1}`, - // comments: [{ body: widgetId }], - // }, - // ]; - - return commentThreads; -}; diff --git a/app/client/src/comments/utils.ts b/app/client/src/comments/utils.ts index 3c8ab11dcd..2a863d77ea 100644 --- a/app/client/src/comments/utils.ts +++ b/app/client/src/comments/utils.ts @@ -1,4 +1,5 @@ import { CommentThread } from "entities/Comments/CommentsInterfaces"; +import { BUILDER_PAGE_URL } from "constants/routes"; // used for dev export const reduceCommentsByRef = (comments: any[]) => { @@ -78,3 +79,34 @@ export const getOffsetPos = ( top: offsetTopPercent, }; }; + +export const getCommentThreadURL = ({ + applicationId, + commentThreadId, + isResolved, + pageId, +}: { + applicationId?: string; + commentThreadId: string; + isResolved?: boolean; + pageId?: string; +}) => { + const queryParams: Record = { + commentThreadId, + isCommentMode: true, + }; + + if (isResolved) { + queryParams.isResolved = true; + } + + const url = new URL( + `${window.location.origin}${BUILDER_PAGE_URL( + applicationId, + pageId, + queryParams, + )}`, + ); + + return url; +}; diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index b24ad0574e..32a1e5a3a4 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -1025,6 +1025,16 @@ type ColorType = { mentionsInput: Record; showcaseCarousel: Record; displayImageUpload: Record; + notifications: Record; +}; + +const notifications = { + time: "#858282", + listHeaderTitle: "#090707", + markAllAsReadButtonBackground: "#f0f0f0", + markAllAsReadButtonText: "#716E6E", + unreadIndicator: "#F86A2B", + bellIndicator: "#E22C2C", }; const displayImageUpload = { @@ -1168,6 +1178,7 @@ const mentionsInput = { }; export const dark: ColorType = { + notifications, displayImageUpload, showcaseCarousel, mentionSuggestion, @@ -1620,6 +1631,7 @@ export const dark: ColorType = { }; export const light: ColorType = { + notifications, displayImageUpload, showcaseCarousel, mentionSuggestion, diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx index 3f24e7b315..06e2f9f9ae 100644 --- a/app/client/src/constants/ReduxActionConstants.tsx +++ b/app/client/src/constants/ReduxActionConstants.tsx @@ -9,6 +9,14 @@ export const ReduxSagaChannels: { [key: string]: string } = { }; export const ReduxActionTypes: { [key: string]: string } = { + MARK_ALL_NOTIFICATIONS_AS_READ_REQUEST: + "MARK_ALL_NOTIFICATIONS_AS_READ_REQUEST", + MARK_ALL_NOTIFICATIONS_AS_READ_SUCCESS: + "MARK_ALL_NOTIFICATIONS_AS_READ_SUCCESS", + SET_IS_NOTIFICATIONS_LIST_VISIBLE: "SET_IS_NOTIFICATIONS_LIST_VISIBLE", + NEW_NOTIFICATION_EVENT: "NEW_NOTIFICATION_EVENT", + FETCH_NOTIFICATIONS_REQUEST: "FETCH_NOTIFICATIONS_REQUEST", + FETCH_NOTIFICATIONS_SUCCESS: "FETCH_NOTIFICATIONS_SUCCESS", SET_SHOW_APP_INVITE_USERS_MODAL: "SET_SHOW_APP_INVITE_USERS_MODAL", UPDATE_COMMENT_EVENT: "UPDATE_COMMENT_EVENT", ADD_COMMENT_REACTION: "ADD_COMMENT_REACTION", diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index e9b31cfe34..717f38b2e9 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -317,6 +317,7 @@ export const FULL_NAME = () => "Full Name"; export const DISPLAY_NAME = () => "Display Name"; export const EMAIL_ADDRESS = () => "Email Address"; export const FIRST_AND_LAST_NAME = () => "First and last name"; +export const MARK_ALL_AS_READ = () => "Mark all as read"; export const INVITE_A_NEW_USER = () => "Invite a new user"; // Showcase Carousel diff --git a/app/client/src/entities/Notification.ts b/app/client/src/entities/Notification.ts new file mode 100644 index 0000000000..8e021b66c4 --- /dev/null +++ b/app/client/src/entities/Notification.ts @@ -0,0 +1,12 @@ +export type AppsmithNotification = { + id: string; + _id?: string; + type: string; + new: boolean; + [key: string]: any; +}; + +export enum NotificationTypes { + CommentNotification = "CommentNotification", + CommentThreadNotification = "CommentThreadNotification", +} diff --git a/app/client/src/notifications/Bell.tsx b/app/client/src/notifications/Bell.tsx new file mode 100644 index 0000000000..ba06a9711e --- /dev/null +++ b/app/client/src/notifications/Bell.tsx @@ -0,0 +1,107 @@ +import React, { useEffect } from "react"; +import { useSelector } from "react-redux"; + +import NotificationsList from "./NotificationsList"; +import { ReactComponent as BellIcon } from "assets/icons/ads/bell.svg"; +import { Popover2 } from "@blueprintjs/popover2"; + +import "@blueprintjs/popover2/lib/css/blueprint-popover2.css"; +import { useDispatch } from "react-redux"; + +import { + fetchNotificationsRequest, + setIsNotificationsListVisible, +} from "actions/notificationActions"; +import styled from "styled-components"; + +import { + unreadCountSelector, + isNotificationsListVisibleSelector, +} from "selectors/notificationSelectors"; + +const Container = styled.div` + position: relative; + padding: ${(props) => props.theme.spaces[1]}px; + margin-right: ${(props) => props.theme.spaces[9]}px; + top: 3px; + cursor: pointer; +`; + +const BellIndicatorContainer = styled.div` + position: absolute; + top: -7px; + right: -5px; +`; + +const Count = styled.div` + position: absolute; + top: 43%; + left: 50%; + transform: translate(-50%, -50%); +`; + +const BellIndicatorIcon = styled.div` + width: 20px; + height: 20px; + border-radius: 50%; + background-color: ${(props) => + props.theme.colors.notifications.bellIndicator}; +`; + +const StyledBellIcon = styled(BellIcon)` + width: 22px; + height: 22px; +`; + +function Bell() { + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchNotificationsRequest()); + }, []); + + const unreadCount = useSelector(unreadCountSelector); + const showIndicator = unreadCount > 0; + + const isOpen = useSelector(isNotificationsListVisibleSelector); + + return ( + } + isOpen={isOpen} + minimal + modifiers={{ + offset: { + enabled: true, + options: { + offset: [-15, 10], + }, + }, + preventOverflow: { + enabled: true, + }, + }} + onInteraction={(nextOpenState) => + dispatch(setIsNotificationsListVisible(nextOpenState)) + } + placement={"bottom-end"} + > + + + {showIndicator && ( + + {/** Not using overflow ellipsis here for UI specs */} + + + {unreadCount > 100 + ? `${unreadCount}`.slice(0, 2) + ".." + : unreadCount} + + + )} + + + ); +} + +export default Bell; diff --git a/app/client/src/notifications/NotificationListItem.tsx b/app/client/src/notifications/NotificationListItem.tsx new file mode 100644 index 0000000000..0c9018f388 --- /dev/null +++ b/app/client/src/notifications/NotificationListItem.tsx @@ -0,0 +1,141 @@ +import React from "react"; +import ProfileImage, { Profile } from "pages/common/ProfileImage"; + +import styled from "styled-components"; + +import UserApi from "api/UserApi"; + +import { AppsmithNotification, NotificationTypes } from "entities/Notification"; + +import { getTypographyByKey } from "constants/DefaultTheme"; +// import moment from "moment"; + +import { getCommentThreadURL } from "comments/utils"; + +import history from "utils/history"; +import { useDispatch } from "react-redux"; +import { markThreadAsReadRequest } from "actions/commentActions"; + +const Container = styled.div` + display: flex; + width: 100%; + padding: ${(props) => props.theme.spaces[6]}px; + + ${Profile} { + margin-right: ${(props) => props.theme.spaces[4]}px; + } + + cursor: pointer; +`; + +const NotificationBodyContainer = styled.div` + ${(props) => getTypographyByKey(props, "p1")}; + & b { + font-weight: 500; + } +`; + +const FlexContainer = styled.div` + display: flex; +`; + +const Time = styled.div` + color: ${(props) => props.theme.colors.notifications.time}; + ${(props) => getTypographyByKey(props, "p3")}; +`; + +const ProfileImageContainer = styled.div` + position: relative; +`; + +const UnreadIndicator = styled.div` + position: absolute; + right: 9px; + top: 0; + width: 9px; + height: 9px; + border-radius: 50%; + background-color: ${(props) => + props.theme.colors.notifications.unreadIndicator}; +`; + +// eslint-disable-next-line +function CommentNotification(props: { notification?: AppsmithNotification }) { + // TODO handle click + return ( + + + +
+ Comment notification body +
+ {/* */} + +
+
+ ); +} + +function CommentThreadNotification(props: { + notification: AppsmithNotification; +}) { + const dispatch = useDispatch(); + const { commentThread } = props.notification; + // TODO add isResolved, applicationId, pageId + const commentThreadUrl = getCommentThreadURL({ + commentThreadId: commentThread?.id, + }); + + const handleClick = () => { + history.push( + `${commentThreadUrl.pathname}${commentThreadUrl.search}${commentThreadUrl.hash}`, + ); + + dispatch(markThreadAsReadRequest(commentThread?.id)); + }; + + // TODO use notification isRead state + const isRead = true; + + return ( + + + + {!isRead && } + + +
+ Comment Thread notification body +
+ {/* */} + +
+
+ ); +} + +const notificationByType = { + [NotificationTypes.CommentNotification]: CommentNotification, + [NotificationTypes.CommentThreadNotification]: CommentThreadNotification, +}; + +function NotificationListItem(props: { notification: AppsmithNotification }) { + const Component = + notificationByType[NotificationTypes.CommentThreadNotification]; + + return ( + + + + ); +} + +export default NotificationListItem; diff --git a/app/client/src/notifications/NotificationsList.tsx b/app/client/src/notifications/NotificationsList.tsx new file mode 100644 index 0000000000..cb1b64d694 --- /dev/null +++ b/app/client/src/notifications/NotificationsList.tsx @@ -0,0 +1,104 @@ +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; +import styled from "styled-components"; + +import { notificationsSelector } from "selectors/notificationSelectors"; +import NotificationListItem from "./NotificationListItem"; +import { AppsmithNotification } from "entities/Notification"; + +import { createMessage, COMMENTS, MARK_ALL_AS_READ } from "constants/messages"; + +import Button, { Category } from "components/ads/Button"; +import { getTypographyByKey } from "constants/DefaultTheme"; + +import { Virtuoso } from "react-virtuoso"; + +import { markAllNotificationsAsReadRequest } from "actions/notificationActions"; + +const Container = styled.div` + width: 326px; + max-height: 376px; +`; + +const StyledHeader = styled.div` + display: flex; + justify-content: space-between; + padding: ${(props) => props.theme.spaces[4] + 1}px; + + & .title { + ${(props) => getTypographyByKey(props, "p1")}; + color: ${(props) => props.theme.colors.notifications.listHeaderTitle}; + } + + & .mark-all-as-read { + border-color: ${(props) => + props.theme.colors.notifications.markAllAsReadButtonBackground}; + background-color: ${(props) => + props.theme.colors.notifications.markAllAsReadButtonBackground}; + color: ${(props) => + props.theme.colors.notifications.markAllAsReadButtonText}; + } +`; + +function NotificationsListHeader() { + const dispatch = useDispatch(); + + return ( + +
{createMessage(COMMENTS)}
+
+ ); + }, + }} + data={notifications} + endReached={() => { + return Promise.resolve([]); + }} + itemContent={(index: number, notification: AppsmithNotification) => ( + + )} + overscan={1} + style={{ height }} + /> + + ); +} + +export default NotificationsList; diff --git a/app/client/src/pages/common/PageHeader.tsx b/app/client/src/pages/common/PageHeader.tsx index 92f358de2a..08ff194994 100644 --- a/app/client/src/pages/common/PageHeader.tsx +++ b/app/client/src/pages/common/PageHeader.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Link, useLocation } from "react-router-dom"; -import { connect } from "react-redux"; +import { connect, useSelector } from "react-redux"; import { getCurrentUser } from "selectors/usersSelectors"; import styled from "styled-components"; import StyledHeader from "components/designSystems/appsmith/StyledHeader"; @@ -12,6 +12,9 @@ import Button from "components/editorComponents/Button"; import history from "utils/history"; import { Colors } from "constants/Colors"; import ProfileDropdown from "./ProfileDropdown"; +import Bell from "notifications/Bell"; + +import { areCommentsEnabledForUserAndApp as areCommentsEnabledForUserAndAppSelector } from "selectors/commentsSelectors"; const StyledPageHeader = styled(StyledHeader)` background: ${Colors.BALTIC_SEA}; @@ -50,6 +53,10 @@ export function PageHeader(props: PageHeaderProps) { =${queryParams.get("redirectUrl")}`; } + const areCommentsEnabledForUserAndApp = useSelector( + areCommentsEnabledForUserAndAppSelector, + ); + return ( @@ -58,19 +65,22 @@ export function PageHeader(props: PageHeaderProps) { {user && ( - - {user.username === ANONYMOUS_USERNAME ? ( -