From 5f9497a6274e906ffda51abc5000a2c5cde41e39 Mon Sep 17 00:00:00 2001 From: bhavin Date: Tue, 4 May 2021 16:39:05 +0530 Subject: [PATCH 01/22] date format 'YYYY-MM-DD hh:mm:ss A' to 'YYYY-MM-DD hh:mm:ss' --- app/client/src/widgets/DatePickerWidget2.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/client/src/widgets/DatePickerWidget2.tsx b/app/client/src/widgets/DatePickerWidget2.tsx index dabd725dd9..fa5cf0a163 100644 --- a/app/client/src/widgets/DatePickerWidget2.tsx +++ b/app/client/src/widgets/DatePickerWidget2.tsx @@ -34,7 +34,7 @@ class DatePickerWidget extends BaseWidget { label: "Date Format", controlType: "DROP_DOWN", isJSConvertible: true, - optionWidth: "320px", + optionWidth: "340px", options: [ { label: moment().format("YYYY-MM-DDTHH:mm:ss.sssZ"), @@ -62,9 +62,9 @@ class DatePickerWidget extends BaseWidget { value: "YYYY-MM-DDTHH:mm:ss", }, { - label: moment().format("YYYY-MM-DD hh:mm:ss"), - subText: "YYYY-MM-DD hh:mm:ss", - value: "YYYY-MM-DD hh:mm:ss", + label: moment().format("YYYY-MM-DD hh:mm:ss A"), + subText: "YYYY-MM-DD hh:mm:ss A", + value: "YYYY-MM-DD hh:mm:ss A", }, { label: moment().format("DD/MM/YYYY HH:mm"), From 47a7128a0620f34b13b46b77f68f7fe26335440b Mon Sep 17 00:00:00 2001 From: bhavin Date: Fri, 7 May 2021 10:56:36 +0530 Subject: [PATCH 02/22] Refactor: rename Dropdown to Select --- app/client/src/constants/HelpConstants.ts | 2 +- app/client/src/mockResponses/WidgetConfigResponse.tsx | 2 +- app/client/src/mockResponses/WidgetSidebarResponse.tsx | 2 +- app/client/src/utils/autocomplete/EntityDefinitions.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts index ecd9087c09..b6004431c8 100644 --- a/app/client/src/constants/HelpConstants.ts +++ b/app/client/src/constants/HelpConstants.ts @@ -41,7 +41,7 @@ export const HelpMap = { }, DROP_DOWN_WIDGET: { path: "/widget-reference/dropdown", - searchKey: "Dropdown", + searchKey: "Select", }, RADIO_GROUP_WIDGET: { path: "/widget-reference/radio", diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 432145bec6..1282219042 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -269,7 +269,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = { { label: "Green", value: "GREEN" }, { label: "Red", value: "RED" }, ], - widgetName: "Dropdown", + widgetName: "Select", defaultOptionValue: "GREEN", version: 1, isRequired: false, diff --git a/app/client/src/mockResponses/WidgetSidebarResponse.tsx b/app/client/src/mockResponses/WidgetSidebarResponse.tsx index cec889206f..8a58f1ae9e 100644 --- a/app/client/src/mockResponses/WidgetSidebarResponse.tsx +++ b/app/client/src/mockResponses/WidgetSidebarResponse.tsx @@ -36,7 +36,7 @@ const WidgetSidebarResponse: WidgetCardProps[] = [ }, { type: "DROP_DOWN_WIDGET", - widgetCardName: "Dropdown", + widgetCardName: "Select", key: generateReactKey(), }, { diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts index 0f0467d9ed..865496c010 100644 --- a/app/client/src/utils/autocomplete/EntityDefinitions.ts +++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts @@ -76,7 +76,7 @@ export const entityDefinitions = { }, DROP_DOWN_WIDGET: { "!doc": - "Dropdown is used to capture user input/s from a specified list of permitted inputs. A Dropdown can capture a single choice as well as multiple choices", + "Select is used to capture user input/s from a specified list of permitted inputs. A Select can capture a single choice as well as multiple choices", "!url": "https://docs.appsmith.com/widget-reference/dropdown", isVisible: isVisible, selectedOptionValue: { From 45aa3c57f1d6c9f01f958b3230a5e1465e013f57 Mon Sep 17 00:00:00 2001 From: arunvjn <32433245+arunvjn@users.noreply.github.com> Date: Fri, 7 May 2021 13:24:58 +0530 Subject: [PATCH 03/22] Added null check against datasource url (#4352) --- .../form/fields/EmbeddedDatasourcePathField.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx index 94ab53e795..1355fcb2e8 100644 --- a/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx +++ b/app/client/src/components/editorComponents/form/fields/EmbeddedDatasourcePathField.tsx @@ -173,7 +173,7 @@ class EmbeddedDatasourcePathComponent extends React.Component { hint: () => { const list = datasourceList .filter((datasource) => - datasource.datasourceConfiguration.url.includes( + (datasource.datasourceConfiguration?.url || "").includes( parsed.datasourceUrl, ), ) From 8c7e844d80bce500db080ccbd02c32fbf4d5e919 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Fri, 7 May 2021 14:02:41 +0530 Subject: [PATCH 04/22] Accept PUT requests for thread updates (#4357) We were using PATCH, because that was semantically more correct for what we are doing here. But since PUT is used everywhere else in the project, changing this to PUT for consistency. --- .../com/appsmith/server/controllers/CommentController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java index 591bb634da..8ae8ad3dce 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java @@ -10,9 +10,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -60,7 +60,7 @@ public class CommentController extends BaseController new ResponseDTO<>(HttpStatus.OK.value(), threads, null)); } - @PatchMapping("/threads/{threadId}") + @PutMapping("/threads/{threadId}") public Mono> updateThread( @Valid @RequestBody CommentThread resource, @PathVariable String threadId From 3b3e24f8cf1f580bf35e72018ea61ea252a6fadf Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Fri, 7 May 2021 15:58:02 +0530 Subject: [PATCH 05/22] Add delete thread API (#4363) --- .../server/controllers/CommentController.java | 7 +++++++ .../com/appsmith/server/services/CommentService.java | 2 ++ .../appsmith/server/services/CommentServiceImpl.java | 11 +++++++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java index 8ae8ad3dce..d11e22af62 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/CommentController.java @@ -78,4 +78,11 @@ public class CommentController extends BaseController new ResponseDTO<>(HttpStatus.OK.value(), deletedResource, null)); } + @DeleteMapping("/threads/{threadId}") + public Mono> deleteThread(@PathVariable String threadId) { + log.debug("Going to delete thread with id: {}", threadId); + return service.deleteThread(threadId) + .map(deletedResource -> new ResponseDTO<>(HttpStatus.OK.value(), deletedResource, null)); + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentService.java index 757a7f9794..452b7ac4e8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentService.java @@ -18,4 +18,6 @@ public interface CommentService extends CrudService { Mono deleteComment(String id); + Mono deleteThread(String threadId); + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java index cea8dc2952..c3a3b2d82f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java @@ -258,10 +258,17 @@ public class CommentServiceImpl extends BaseService deleteComment(String id) { - return repository.findById(id, AclPermission.MANAGE_COMMENT) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.COMMENT, id))) - .flatMap(comment -> repository.archive(comment)); + .flatMap(repository::archive) + .flatMap(analyticsService::sendDeleteEvent); + } + + @Override + public Mono deleteThread(String threadId) { + return threadRepository.findById(threadId, AclPermission.MANAGE_THREAD) + .flatMap(threadRepository::archive) + .flatMap(analyticsService::sendDeleteEvent); } } From 212678a5400309a3d635c730bb1612bb6ec6dfd5 Mon Sep 17 00:00:00 2001 From: Nayan <83352306+nayan-appsmith@users.noreply.github.com> Date: Fri, 7 May 2021 16:55:48 +0600 Subject: [PATCH 06/22] =?UTF-8?q?[Docs]=20Imrove=20readme=20file=20in=20se?= =?UTF-8?q?rver,=20improve=20client=20getting=20started=20g=E2=80=A6=20(#4?= =?UTF-8?q?327)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Docs] Imrove readme file in server, improve client getting started guide * -updated the ClientSetup readme as per PR comment --- app/server/README.md | 1 + contributions/ClientSetup.md | 1 + 2 files changed, 2 insertions(+) diff --git a/app/server/README.md b/app/server/README.md index d59286da54..ec647f1efb 100644 --- a/app/server/README.md +++ b/app/server/README.md @@ -1,6 +1,7 @@ # Appsmith Server This is the server-side repository for the Appsmith framework. +For details on setting up your development machine, please refer to the [Setup Guide](https://github.com/appsmithorg/appsmith/blob/release/contributions/ServerSetup.md) ### How to build ```bash diff --git a/contributions/ClientSetup.md b/contributions/ClientSetup.md index 08b13f654a..44b39ee148 100644 --- a/contributions/ClientSetup.md +++ b/contributions/ClientSetup.md @@ -10,6 +10,7 @@ On your development machine, please ensure that: 1. You have `mkcert` installed. Please visit: [https://github.com/FiloSottile/mkcert#installation](https://github.com/FiloSottile/mkcert#installation) for details. For `mkcert` to work with Firefox you may require the `nss` utility to be installed. Details are in the link above. 1. You have `envsubst` installed. use `brew install gettext` on MacOS. Linux machines usually have this installed. 1. You have cloned the repo in your local machine. +1. You have yarn installed as a global npm package i.e. `npm install -g yarn` ### Create local HTTPS certificates: From 4a288439f0482b6110b1d4c4a97d494ae95c87b7 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Fri, 7 May 2021 17:24:05 +0530 Subject: [PATCH 07/22] Added basic authentication type to REST APIs (#4040) * Added basic authentication type to REST APIs * Spacing for prefix * Tests and json ignore for password * Client side form changes * Review comments --- .../src/entities/Datasource/RestAPIForm.ts | 9 +++- .../RestAPIDatasourceForm.tsx | 39 ++++++++++++++-- .../RestAPIDatasourceFormTransformer.ts | 17 +++++++ .../external/constants/Authentication.java | 1 + .../external/models/AuthenticationDTO.java | 3 +- .../appsmith/external/models/BasicAuth.java | 26 +++++++++++ .../connections/APIConnectionFactory.java | 3 ++ .../connections/BasicAuthentication.java | 45 +++++++++++++++++++ .../connections/OAuth2AuthorizationCode.java | 6 +-- .../connections/OAuth2ClientCredentials.java | 6 +-- .../src/main/resources/form.json | 4 ++ .../connections/BasicAuthenticationTest.java | 26 +++++++++++ 12 files changed, 173 insertions(+), 12 deletions(-) create mode 100644 app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/BasicAuth.java create mode 100644 app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/BasicAuthentication.java create mode 100644 app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/connections/BasicAuthenticationTest.java diff --git a/app/client/src/entities/Datasource/RestAPIForm.ts b/app/client/src/entities/Datasource/RestAPIForm.ts index ea4f0239e9..9abf8719a6 100644 --- a/app/client/src/entities/Datasource/RestAPIForm.ts +++ b/app/client/src/entities/Datasource/RestAPIForm.ts @@ -3,6 +3,7 @@ import { Property } from "entities/Action"; export enum AuthType { NONE = "NONE", OAuth2 = "oAuth2", + basic = "basic", } export enum GrantType { @@ -10,7 +11,7 @@ export enum GrantType { AuthorizationCode = "authorization_code", } -export type Authentication = ClientCredentials | AuthorizationCode; +export type Authentication = ClientCredentials | AuthorizationCode | Basic; export interface ApiDatasourceForm { datasourceId: string; pluginId: string; @@ -45,3 +46,9 @@ export interface AuthorizationCode extends Oauth2Common { isAuthorizationHeader: boolean; isAuthorized: boolean; } + +export interface Basic { + authenticationType: AuthType.basic; + username: string; + password: string; +} diff --git a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx index af1e66bbcb..7edfb2b9e4 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx @@ -206,7 +206,7 @@ class DatasourceRestAPIEditor extends React.Component { if (!this.props.formData) return; const { authentication } = this.props.formData; - if (!authentication || !authentication.grantType) { + if (!authentication || !_.get(authentication, "grantType")) { this.props.change( "authentication.grantType", GrantType.ClientCredentials, @@ -225,7 +225,7 @@ class DatasourceRestAPIEditor extends React.Component { return false; } - if (authentication.grantType === GrantType.AuthorizationCode) { + if (_.get(authentication, "grantType") === GrantType.AuthorizationCode) { if (_.get(authentication, "isAuthorizationHeader") === undefined) { this.props.change("authentication.isAuthorizationHeader", true); return false; @@ -435,6 +435,10 @@ class DatasourceRestAPIEditor extends React.Component { label: "None", value: AuthType.NONE, }, + { + label: "Basic", + value: AuthType.basic, + }, { label: "OAuth 2.0", value: AuthType.OAuth2, @@ -455,6 +459,8 @@ class DatasourceRestAPIEditor extends React.Component { let content; if (authType === AuthType.OAuth2) { content = this.renderOauth2(); + } else if (authType === AuthType.basic) { + content = this.renderBasic(); } if (content) { return ( @@ -465,11 +471,36 @@ class DatasourceRestAPIEditor extends React.Component { } }; + renderBasic = () => { + return ( + <> + + + + + + + + ); + }; + renderOauth2 = () => { const { authentication } = this.props.formData; if (!authentication) return; let content; - switch (authentication?.grantType) { + switch (_.get(authentication, "grantType")) { case GrantType.AuthorizationCode: content = this.renderOauth2AuthorizationCode(); break; @@ -525,7 +556,7 @@ class DatasourceRestAPIEditor extends React.Component { ]} /> - {formData.authentication?.isTokenHeader && ( + {_.get(formData.authentication, "isTokenHeader") && ( create(BasicAuth basicAuth) { + final BasicAuthentication basicAuthentication = new BasicAuthentication(); + final String decodedAuthorizationHeader = basicAuth.getUsername() + ":" + basicAuth.getPassword(); + + basicAuthentication.setEncodedAuthorizationHeader( + Base64.getEncoder().encodeToString(decodedAuthorizationHeader.getBytes(StandardCharsets.UTF_8))); + + return Mono.just(basicAuthentication); + } + + + @Override + public Mono filter(ClientRequest request, ExchangeFunction next) { + return Mono.justOrEmpty(ClientRequest.from(request) + .headers(headers -> headers.set("Authorization", HEADER_PREFIX + this.getEncodedAuthorizationHeader())) + .build()) + // Carry on to next exchange function + .flatMap(next::exchange) + // Default to next exchange function if something went wrong + .switchIfEmpty(next.exchange(request)); + } +} diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2AuthorizationCode.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2AuthorizationCode.java index aa28d35856..d9f19f9419 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2AuthorizationCode.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2AuthorizationCode.java @@ -6,7 +6,9 @@ import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.OAuth2; import com.appsmith.external.models.UpdatableConnection; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -31,6 +33,7 @@ import java.util.Map; @Setter @Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class OAuth2AuthorizationCode extends APIConnection implements UpdatableConnection { private final Clock clock = Clock.systemUTC(); @@ -42,9 +45,6 @@ public class OAuth2AuthorizationCode extends APIConnection implements UpdatableC private Object tokenResponse; private static final int MAX_IN_MEMORY_SIZE = 10 * 1024 * 1024; // 10 MB - private OAuth2AuthorizationCode() { - } - public static Mono create(OAuth2 oAuth2) { if (oAuth2 == null) { return Mono.empty(); diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2ClientCredentials.java b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2ClientCredentials.java index 33e54ebad3..ab132a4f53 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2ClientCredentials.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/java/com/external/connections/OAuth2ClientCredentials.java @@ -6,7 +6,9 @@ import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.OAuth2; import com.appsmith.external.models.UpdatableConnection; +import lombok.AccessLevel; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -31,6 +33,7 @@ import java.util.Map; @Setter @Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) public class OAuth2ClientCredentials extends APIConnection implements UpdatableConnection { private final Clock clock = Clock.systemUTC(); @@ -41,9 +44,6 @@ public class OAuth2ClientCredentials extends APIConnection implements UpdatableC private Object tokenResponse; private static final int MAX_IN_MEMORY_SIZE = 10 * 1024 * 1024; // 10 MB - private OAuth2ClientCredentials() { - } - public static Mono create(OAuth2 oAuth2) { if (oAuth2 == null) { return Mono.empty(); diff --git a/app/server/appsmith-plugins/restApiPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/restApiPlugin/src/main/resources/form.json index 9945c475ca..45e5258fb0 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/main/resources/form.json +++ b/app/server/appsmith-plugins/restApiPlugin/src/main/resources/form.json @@ -65,6 +65,10 @@ "label": "None", "value": "dbAuth" }, + { + "label": "Basic", + "value": "basic" + }, { "label": "OAuth2 (Client credentials)", "value": "oAuth2" diff --git a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/connections/BasicAuthenticationTest.java b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/connections/BasicAuthenticationTest.java new file mode 100644 index 0000000000..2ed3f751c2 --- /dev/null +++ b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/connections/BasicAuthenticationTest.java @@ -0,0 +1,26 @@ +package com.external.connections; + +import com.appsmith.external.models.BasicAuth; +import org.junit.Assert; +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Base64; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BasicAuthenticationTest { + + @Test + public void testCreate_validCredentials_ReturnsWithEncodedValue() { + BasicAuth basicAuth = new BasicAuth(); + basicAuth.setUsername("test"); + basicAuth.setPassword("password"); + BasicAuthentication connection = BasicAuthentication.create(basicAuth).block(Duration.ofMillis(100)); + assertThat(connection).isNotNull(); + Assert.assertEquals( + Base64.getEncoder().encodeToString("test:password".getBytes(StandardCharsets.UTF_8)), + connection.getEncodedAuthorizationHeader()); + } +} \ No newline at end of file From bf7a8b91a4f07f6080cb10ba502a8f3496689f52 Mon Sep 17 00:00:00 2001 From: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Date: Fri, 7 May 2021 20:03:20 +0530 Subject: [PATCH 08/22] Feature: Show widget error state (#4202) --- .../Debugger/Widget_Error_spec.js | 17 ++++ app/client/cypress/support/commands.js | 2 +- .../CodeEditor/styledComponents.ts | 10 +-- .../editorComponents/Debugger/index.tsx | 10 +-- .../WidgetNameComponent/SettingsControl.tsx | 47 +++++++++- .../WidgetNameComponent/index.tsx | 5 +- app/client/src/sagas/EvaluationsSaga.ts | 3 + app/client/src/utils/helpers.test.ts | 86 +++++++++++++++++++ app/client/src/utils/helpers.tsx | 25 ++++++ app/client/src/widgets/BaseWidget.tsx | 7 ++ 10 files changed, 191 insertions(+), 21 deletions(-) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js create mode 100644 app/client/src/utils/helpers.test.ts diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js new file mode 100644 index 0000000000..3daf51823b --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Debugger/Widget_Error_spec.js @@ -0,0 +1,17 @@ +const dsl = require("../../../../fixtures/buttondsl.json"); + +describe("Widget error state", function() { + before(() => { + cy.addDsl(dsl); + }); + it("Check widget error state", function() { + cy.openPropertyPane("buttonwidget"); + + cy.get(".t--property-control-visible") + .find(".t--js-toggle") + .click(); + cy.testJsontext("visible", "Test"); + + cy.contains(".t--widget-error-count", 1); + }); +}); diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 617eb1e958..0404b43bf3 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1132,7 +1132,7 @@ Cypress.Commands.add("widgetText", (text, inputcss, innercss) => { cy.get(inputcss) .first() .trigger("mouseover", { force: true }); - cy.get(innercss).should("have.text", text); + cy.contains(innercss, text); }); Cypress.Commands.add("editColName", (text) => { diff --git a/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts b/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts index 3272c2f646..1054a84b21 100644 --- a/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts +++ b/app/client/src/components/editorComponents/CodeEditor/styledComponents.ts @@ -422,20 +422,13 @@ export const DynamicAutocompleteInputWrapper = styled.div<{ height: 100%; flex: 1; position: relative; - border-color: ${(props) => - !props.isError && props.isActive && props.skin === Skin.DARK - ? Colors.ALABASTER - : "transparent"}; + border: 1px solid ${(props) => (!props.isError ? "transparent" : "red")}; > span:first-of-type { width: 30px; position: absolute; right: 0px; } &:hover { - border-color: ${(props) => - !props.isError && props.skin === Skin.DARK - ? Colors.ALABASTER - : "transparent"}; .lightning-menu { background: ${(props) => (!props.isNotHover ? "#090707" : "")}; svg { @@ -451,7 +444,6 @@ export const DynamicAutocompleteInputWrapper = styled.div<{ } } } - border: 0px; border-radius: 0px; .lightning-menu { z-index: 1 !important; diff --git a/app/client/src/components/editorComponents/Debugger/index.tsx b/app/client/src/components/editorComponents/Debugger/index.tsx index 2e7ebb0fb7..368d73be84 100644 --- a/app/client/src/components/editorComponents/Debugger/index.tsx +++ b/app/client/src/components/editorComponents/Debugger/index.tsx @@ -18,7 +18,7 @@ const Container = styled.div<{ errorCount: number }>` right: 20px; bottom: 20px; cursor: pointer; - padding: 19px; + padding: ${(props) => props.theme.spaces[6]}px; color: ${(props) => props.theme.colors.debugger.floatingButton.color}; border-radius: 50px; box-shadow: ${(props) => props.theme.colors.debugger.floatingButton.shadow}; @@ -33,11 +33,9 @@ const Container = styled.div<{ errorCount: number }>` .debugger-count { color: ${Colors.WHITE}; - font-size: 14px; - font-weight: 500; ${(props) => getTypographyByKey(props, "h6")} - height: 20px; - padding: 6px; + height: 16px; + padding: ${(props) => props.theme.spaces[1]}px; background-color: ${(props) => !!props.errorCount ? props.theme.colors.debugger.floatingButton.errorCount @@ -75,7 +73,7 @@ function Debugger() { errorCount={errorCount} onClick={onClick} > - +
{errorCount}
); diff --git a/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx b/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx index b33a28bd0f..6e3dfc2255 100644 --- a/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx +++ b/app/client/src/components/editorComponents/WidgetNameComponent/SettingsControl.tsx @@ -1,5 +1,6 @@ import React, { CSSProperties } from "react"; import { ControlIcons } from "icons/ControlIcons"; +import Icon, { IconSize } from "components/ads/Icon"; import { Colors } from "constants/Colors"; import styled from "styled-components"; import { Tooltip, Classes } from "@blueprintjs/core"; @@ -34,18 +35,41 @@ const SettingsWrapper = styled.div` `; const WidgetName = styled.span` - margin-right: 5px; + margin-right: ${(props) => props.theme.spaces[1] + 1}px; + margin-left: ${(props) => props.theme.spaces[3]}px; +`; + +const StyledErrorIcon = styled(Icon)` + &:hover { + svg { + path { + fill: ${Colors.WHITE}; + } + } + } + margin-right: ${(props) => props.theme.spaces[1]}px; `; type SettingsControlProps = { toggleSettings: (e: any) => void; activity: Activities; name: string; + errorCount: number; }; const SettingsIcon = ControlIcons.SETTINGS_CONTROL; -const getStyles = (activity: Activities): CSSProperties | undefined => { +const getStyles = ( + activity: Activities, + errorCount: number, +): CSSProperties | undefined => { + if (errorCount > 0) { + return { + background: "red", + color: Colors.WHITE, + }; + } + switch (activity) { case Activities.ACTIVE: return { @@ -69,7 +93,9 @@ export function SettingsControl(props: SettingsControlProps) { const settingsIcon = ( ); + const errorIcon = ( + + ); return ( + {!!props.errorCount && ( + <> + {errorIcon} + {props.errorCount} + + )} {props.name} {settingsIcon} diff --git a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx index 0e5a653bde..98d46c85d1 100644 --- a/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx +++ b/app/client/src/components/editorComponents/WidgetNameComponent/index.tsx @@ -41,6 +41,7 @@ type WidgetNameComponentProps = { parentId?: string; type: WidgetType; showControls?: boolean; + errorCount: number; }; export function WidgetNameComponent(props: WidgetNameComponentProps) { @@ -95,7 +96,8 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { props.showControls || ((focusedWidget === props.widgetId || selectedWidget === props.widgetId) && !isDragging && - !isResizing); + !isResizing) || + !!props.errorCount; let currentActivity = Activities.NONE; if (focusedWidget === props.widgetId) currentActivity = Activities.HOVERING; @@ -111,6 +113,7 @@ export function WidgetNameComponent(props: WidgetNameComponentProps) { diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index ef782826ee..ff7d79a46a 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -58,6 +58,9 @@ const evalErrorHandler = (errors: EvalError[]) => { text: `${error.message} Node was: ${node}`, variant: Variant.danger, }); + AppsmithConsole.error({ + text: `${error.message} Node was: ${node}`, + }); // Send the generic error message to sentry for better grouping Sentry.captureException(new Error(error.message), { tags: { diff --git a/app/client/src/utils/helpers.test.ts b/app/client/src/utils/helpers.test.ts new file mode 100644 index 0000000000..b9a613979c --- /dev/null +++ b/app/client/src/utils/helpers.test.ts @@ -0,0 +1,86 @@ +import { flattenObject } from "./helpers"; + +describe("flattenObject test", () => { + it("Check if non nested object is returned correctly", () => { + const testObject = { + isVisible: true, + isDisabled: false, + tableData: false, + }; + + expect(flattenObject(testObject)).toStrictEqual(testObject); + }); + + it("Check if nested objects are returned correctly", () => { + const tests = [ + { + input: { + isVisible: true, + isDisabled: false, + tableData: false, + settings: { + color: [ + { + headers: { + left: true, + }, + }, + ], + }, + }, + output: { + isVisible: true, + isDisabled: false, + tableData: false, + "settings.color[0].headers.left": true, + }, + }, + { + input: { + isVisible: true, + isDisabled: false, + tableData: false, + settings: { + color: true, + }, + }, + output: { + isVisible: true, + isDisabled: false, + tableData: false, + "settings.color": true, + }, + }, + { + input: { + numbers: [1, 2, 3], + color: { header: "red" }, + }, + output: { + "numbers[0]": 1, + "numbers[1]": 2, + "numbers[2]": 3, + "color.header": "red", + }, + }, + { + input: { + name: null, + color: { header: {} }, + users: { + id: undefined, + }, + }, + output: { + "color.header": {}, + name: null, + "users.id": undefined, + }, + }, + ]; + + tests.map((test) => + expect(flattenObject(test.input)).toStrictEqual(test.output), + ); + }); +}); diff --git a/app/client/src/utils/helpers.tsx b/app/client/src/utils/helpers.tsx index 6a9f52bf05..5534d658df 100644 --- a/app/client/src/utils/helpers.tsx +++ b/app/client/src/utils/helpers.tsx @@ -287,3 +287,28 @@ export const scrollbarWidth = () => { document.body.removeChild(scrollDiv); return scrollbarWidth; }; + +// Flatten object +// From { isValid: false, settings: { color: false}} +// To { isValid: false, settings.color: false} +export const flattenObject = (data: Record) => { + const result: Record = {}; + function recurse(cur: any, prop: any) { + if (Object(cur) !== cur) { + result[prop] = cur; + } else if (Array.isArray(cur)) { + for (let i = 0, l = cur.length; i < l; i++) + recurse(cur[i], prop + "[" + i + "]"); + if (cur.length == 0) result[prop] = []; + } else { + let isEmpty = true; + for (const p in cur) { + isEmpty = false; + recurse(cur[p], prop ? prop + "." + p : p); + } + if (isEmpty && prop) result[prop] = {}; + } + } + recurse(data, ""); + return result; +}; diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index baa8de6de3..0e47cd7579 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -15,6 +15,7 @@ import { CSSUnit, CONTAINER_GRID_PADDING, } from "constants/WidgetConstants"; +import { memoize } from "lodash"; import DraggableComponent from "components/editorComponents/DraggableComponent"; import ResizableComponent from "components/editorComponents/ResizableComponent"; import { WidgetExecuteActionPayload } from "constants/AppsmithActionConstants/ActionConstants"; @@ -35,6 +36,7 @@ import OverlayCommentsWrapper from "comments/inlineComments/OverlayCommentsWrapp import PreventInteractionsOverlay from "components/editorComponents/PreventInteractionsOverlay"; import AppsmithConsole from "utils/AppsmithConsole"; import { ENTITY_TYPE } from "entities/AppsmithConsole"; +import { flattenObject } from "utils/helpers"; /*** * BaseWidget @@ -176,6 +178,10 @@ abstract class BaseWidget< }; } + getErrorCount = memoize((invalidProps) => { + return Object.values(flattenObject(invalidProps)).filter((e) => !!e).length; + }, JSON.stringify); + render() { return this.getWidgetView(); } @@ -209,6 +215,7 @@ abstract class BaseWidget< <> {!this.props.disablePropertyPane && ( Date: Fri, 7 May 2021 23:22:20 +0530 Subject: [PATCH 09/22] Removed prod url from default config (#4377) --- .../appsmith-server/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/appsmith-server/src/main/resources/application.properties b/app/server/appsmith-server/src/main/resources/application.properties index 1b86904aa7..867f87d1aa 100644 --- a/app/server/appsmith-server/src/main/resources/application.properties +++ b/app/server/appsmith-server/src/main/resources/application.properties @@ -66,7 +66,7 @@ admin.emails = ${APPSMITH_ADMIN_EMAILS:} emails.welcome.enabled = ${APPSMITH_EMAILS_WELCOME_ENABLED:true} # Appsmith Cloud Services -appsmith.cloud_services.base_url = ${APPSMITH_CLOUD_SERVICES_BASE_URL:https://cs.appsmith.com} +appsmith.cloud_services.base_url = ${APPSMITH_CLOUD_SERVICES_BASE_URL:} appsmith.cloud_services.username = ${APPSMITH_CLOUD_SERVICES_USERNAME:} appsmith.cloud_services.password = ${APPSMITH_CLOUD_SERVICES_PASSWORD:} github_repo = ${APPSMITH_GITHUB_REPO:} From c31a288451d53e98674456cb432ccfd45289a015 Mon Sep 17 00:00:00 2001 From: Confidence Okoghenun Date: Sun, 9 May 2021 05:16:03 +0100 Subject: [PATCH 10/22] docs: Makes yarn install script more obvious (#4373) --- contributions/ClientSetup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributions/ClientSetup.md b/contributions/ClientSetup.md index 44b39ee148..13da4c58cf 100644 --- a/contributions/ClientSetup.md +++ b/contributions/ClientSetup.md @@ -51,7 +51,7 @@ On your development machine, please ensure that: ### Steps to build & run the code: -1. Run `yarn` +1. Run `yarn install` Note: From 11c1a0156633a28769994693255236b58db24d89 Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Sun, 9 May 2021 07:17:12 +0300 Subject: [PATCH 11/22] fix: upgrade org.springframework.boot:spring-boot-starter-actuator from 2.3.4.RELEASE to 2.4.4 (#4308) Snyk has created this PR to upgrade org.springframework.boot:spring-boot-starter-actuator from 2.3.4.RELEASE to 2.4.4. See this package in Maven Repository: https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator/ See this project in Snyk: https://app.snyk.io/org/nikhil-nu4/project/b1eb6091-e23c-476f-bcc4-c4ae240497f8?utm_source=github&utm_medium=upgrade-pr --- 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 584eba9d7c..da5ee2e4d5 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -131,7 +131,7 @@ org.springframework.boot spring-boot-starter-actuator - 2.3.4.RELEASE + 2.4.4 io.micrometer From 5b9b348035ff580bb517c831a032e07025620f5e Mon Sep 17 00:00:00 2001 From: Snyk bot Date: Sun, 9 May 2021 07:17:45 +0300 Subject: [PATCH 12/22] fix: upgrade org.springframework.boot:spring-boot-starter-mail from 2.2.1.RELEASE to 2.4.4 (#4305) Snyk has created this PR to upgrade org.springframework.boot:spring-boot-starter-mail from 2.2.1.RELEASE to 2.4.4. See this package in Maven Repository: https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-mail/ See this project in Snyk: https://app.snyk.io/org/nikhil-nu4/project/b1eb6091-e23c-476f-bcc4-c4ae240497f8?utm_source=github&utm_medium=upgrade-pr --- 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 da5ee2e4d5..bc1af61e88 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -66,7 +66,7 @@ org.springframework.boot spring-boot-starter-mail - 2.2.1.RELEASE + 2.4.4 org.springframework.boot From 5a289b51ede7d268a8d8ee212235f878a81f77fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 May 2021 09:49:19 +0530 Subject: [PATCH 13/22] Bump commons-io from 2.6 to 2.7 in /app/server/appsmith-server (#4171) Bumps commons-io from 2.6 to 2.7. 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 bc1af61e88..55a1bbe62c 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -121,7 +121,7 @@ commons-io commons-io - 2.6 + 2.7 commons-validator From 3573ea39132f004120faea773035dcf5cad51e68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 9 May 2021 09:49:43 +0530 Subject: [PATCH 14/22] Bump commons-io from 2.6 to 2.7 in /app/server/appsmith-interfaces (#4172) Bumps commons-io from 2.6 to 2.7. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/server/appsmith-interfaces/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/appsmith-interfaces/pom.xml b/app/server/appsmith-interfaces/pom.xml index 745124bb92..378f1c46d2 100644 --- a/app/server/appsmith-interfaces/pom.xml +++ b/app/server/appsmith-interfaces/pom.xml @@ -107,7 +107,7 @@ commons-io commons-io - 2.6 + 2.7 compile From 0ec3604eac2f2125476124fcd3ed85a995188a06 Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Mon, 10 May 2021 10:35:45 +0530 Subject: [PATCH 15/22] Feature: connect to mongo db via connection string URI (#4131) allow user to connect using a mongo connection string URI directly. --- .../com/external/plugins/MongoPlugin.java | 226 +++++++++++++++--- .../mongoPlugin/src/main/resources/form.json | 88 ++++++- .../com/external/plugins/MongoPluginTest.java | 119 ++++++++- .../server/migrations/DatabaseChangelog.java | 16 ++ 4 files changed, 401 insertions(+), 48 deletions(-) diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java index 72b7f0faf1..96ac1c4a82 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/java/com/external/plugins/MongoPlugin.java @@ -62,6 +62,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY; @@ -89,6 +91,40 @@ public class MongoPlugin extends BasePlugin { private static final int SMART_BSON_SUBSTITUTION_INDEX = 0; + /* + * - The regex matches the following two pattern types: + * - mongodb+srv://user:pass@some-url/some-db.... + * - mongodb://user:pass@some-url:port,some-url:port,../some-db.... + * - It has been grouped like this: (mongodb+srv://)((user):(pass))(@some-url/(some-db....)) + */ + private static final String MONGO_URI_REGEX = "^(mongodb(\\+srv)?:\\/\\/)((.+):(.+))(@.+\\/(.+))$"; + + private static final int REGEX_GROUP_HEAD = 1; + + private static final int REGEX_GROUP_USERNAME = 4; + + private static final int REGEX_GROUP_PASSWORD = 5; + + private static final int REGEX_GROUP_TAIL = 6; + + private static final int REGEX_GROUP_DBNAME = 7; + + private static final String KEY_USERNAME = "username"; + + private static final String KEY_PASSWORD = "password"; + + private static final String KEY_URI_HEAD = "uriHead"; + + private static final String KEY_URI_TAIL = "uriTail"; + + private static final String KEY_URI_DBNAME = "dbName"; + + private static final String YES = "Yes"; + + private static final int DATASOURCE_CONFIG_USE_MONGO_URI_PROPERTY_INDEX = 0; + + private static final int DATASOURCE_CONFIG_MONGO_URI_PROPERTY_INDEX = 1; + private static final Integer MONGO_COMMAND_EXCEPTION_UNAUTHORIZED_ERROR_CODE = 13; public MongoPlugin(PluginWrapper wrapper) { @@ -367,9 +403,83 @@ public class MongoPlugin extends BasePlugin { .subscribeOn(scheduler); } - public static String buildClientURI(DatasourceConfiguration datasourceConfiguration) throws AppsmithPluginException { - StringBuilder builder = new StringBuilder(); + private boolean isUsingURI(DatasourceConfiguration datasourceConfiguration) { + List properties = datasourceConfiguration.getProperties(); + if (properties != null && properties.size() > DATASOURCE_CONFIG_USE_MONGO_URI_PROPERTY_INDEX + && properties.get(DATASOURCE_CONFIG_USE_MONGO_URI_PROPERTY_INDEX) != null + && YES.equals(properties.get(DATASOURCE_CONFIG_USE_MONGO_URI_PROPERTY_INDEX).getValue())) { + return true; + } + return false; + } + + private boolean hasNonEmptyURI(DatasourceConfiguration datasourceConfiguration) { + List properties = datasourceConfiguration.getProperties(); + if (properties != null && properties.size() > DATASOURCE_CONFIG_MONGO_URI_PROPERTY_INDEX + && properties.get(DATASOURCE_CONFIG_MONGO_URI_PROPERTY_INDEX) != null + && !StringUtils.isEmpty(properties.get(DATASOURCE_CONFIG_MONGO_URI_PROPERTY_INDEX).getValue())) { + return true; + } + + return false; + } + + private Map extractInfoFromConnectionStringURI(String uri, String regex) { + if (uri.matches(regex)) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(uri); + if (matcher.find()) { + Map extractedInfoMap = new HashMap(); + String username = matcher.group(REGEX_GROUP_USERNAME); + extractedInfoMap.put(KEY_USERNAME, username == null ? "" : username); + String password = matcher.group(REGEX_GROUP_PASSWORD); + extractedInfoMap.put(KEY_PASSWORD, password == null ? "" : password); + extractedInfoMap.put(KEY_URI_HEAD, matcher.group(REGEX_GROUP_HEAD)); + extractedInfoMap.put(KEY_URI_TAIL, matcher.group(REGEX_GROUP_TAIL)); + extractedInfoMap.put(KEY_URI_DBNAME, matcher.group(REGEX_GROUP_DBNAME).split("\\?")[0]); + return extractedInfoMap; + } + } + + return null; + } + + private String buildURIfromExtractedInfo(Map extractedInfo, String password) { + return extractedInfo.get(KEY_URI_HEAD) + (extractedInfo.get(KEY_USERNAME) == null ? "" : + extractedInfo.get(KEY_USERNAME) + ":") + (password == null ? "" : password) + + extractedInfo.get(KEY_URI_TAIL); + } + + public String buildClientURI(DatasourceConfiguration datasourceConfiguration) throws AppsmithPluginException { + List properties = datasourceConfiguration.getProperties(); + if (isUsingURI(datasourceConfiguration)) { + if (hasNonEmptyURI(datasourceConfiguration)) { + String uriWithHiddenPassword = + (String)properties.get(DATASOURCE_CONFIG_MONGO_URI_PROPERTY_INDEX).getValue(); + Map extractedInfo = extractInfoFromConnectionStringURI(uriWithHiddenPassword, MONGO_URI_REGEX); + if (extractedInfo != null) { + String password = ((DBAuth)datasourceConfiguration.getAuthentication()).getPassword(); + return buildURIfromExtractedInfo(extractedInfo, password); + } + else { + throw new AppsmithPluginException( + AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, + "Appsmith server has failed to parse the Mongo connection string URI. Please check " + + "if the URI has the correct format." + ); + } + } + else { + throw new AppsmithPluginException( + AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, + "Could not find any Mongo connection string URI. Please edit the 'Mongo Connection String" + + " URI' field to provide the URI to connect to." + ); + } + } + + StringBuilder builder = new StringBuilder(); final Connection connection = datasourceConfiguration.getConnection(); final List endpoints = datasourceConfiguration.getEndpoints(); @@ -483,52 +593,89 @@ public class MongoPlugin extends BasePlugin { @Override public Set validateDatasource(DatasourceConfiguration datasourceConfiguration) { Set invalids = new HashSet<>(); + List properties = datasourceConfiguration.getProperties(); + if (isUsingURI(datasourceConfiguration)) { + if (!hasNonEmptyURI(datasourceConfiguration)) { + invalids.add("'Mongo Connection String URI' field is empty. Please edit the 'Mongo Connection " + + "URI' field to provide a connection uri to connect with."); + } else { + String mongoUri = (String)properties.get(DATASOURCE_CONFIG_MONGO_URI_PROPERTY_INDEX).getValue(); + if (!mongoUri.matches(MONGO_URI_REGEX)) { + invalids.add("Mongo Connection String URI does not seem to be in the correct format. Please " + + "check the URI once."); + } else { + Map extractedInfo = extractInfoFromConnectionStringURI(mongoUri, MONGO_URI_REGEX); + if (extractedInfo == null) { + invalids.add("Mongo Connection String URI does not seem to be in the correct format. " + + "Please check the URI once."); + } else { + String mongoUriWithHiddenPassword = buildURIfromExtractedInfo(extractedInfo, "****"); + properties.get(DATASOURCE_CONFIG_MONGO_URI_PROPERTY_INDEX).setValue(mongoUriWithHiddenPassword); + DBAuth authentication = datasourceConfiguration.getAuthentication() == null ? + new DBAuth() : (DBAuth) datasourceConfiguration.getAuthentication(); + authentication.setUsername((String) extractedInfo.get(KEY_USERNAME)); + authentication.setPassword((String) extractedInfo.get(KEY_PASSWORD)); + authentication.setDatabaseName((String) extractedInfo.get(KEY_URI_DBNAME)); + datasourceConfiguration.setAuthentication(authentication); - List endpoints = datasourceConfiguration.getEndpoints(); - if (CollectionUtils.isEmpty(endpoints)) { - invalids.add("Missing endpoint(s)."); + // remove any default db set via form auto-fill via browser + if (datasourceConfiguration.getConnection() != null) { + datasourceConfiguration.getConnection().setDefaultDatabaseName(null); + } + } + } + } + } else { + List endpoints = datasourceConfiguration.getEndpoints(); + if (CollectionUtils.isEmpty(endpoints)) { + invalids.add("Missing endpoint(s)."); + + } else if (Connection.Type.REPLICA_SET.equals(datasourceConfiguration.getConnection().getType())) { + if (endpoints.size() == 1 && endpoints.get(0).getPort() != null) { + invalids.add("REPLICA_SET connections should not be given a port." + + " If you are trying to specify all the shards, please add more than one."); + } - } else if (Connection.Type.REPLICA_SET.equals(datasourceConfiguration.getConnection().getType())) { - if (endpoints.size() == 1 && endpoints.get(0).getPort() != null) { - invalids.add("REPLICA_SET connections should not be given a port." + - " If you are trying to specify all the shards, please add more than one."); } - } + if (!CollectionUtils.isEmpty(endpoints)) { + boolean usingUri = endpoints + .stream() + .anyMatch(endPoint -> endPoint.getHost().matches(MONGO_URI_REGEX)); - if (!CollectionUtils.isEmpty(endpoints)) { - boolean usingSrvUrl = endpoints - .stream() - .anyMatch(endPoint -> endPoint.getHost().contains("mongodb+srv")); - - if (usingSrvUrl) { - invalids.add("MongoDb SRV URLs are not yet supported. Please extract the individual fields from " + - "the SRV URL into the datasource configuration form."); - } - } - - DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); - if (authentication != null) { - DBAuth.Type authType = authentication.getAuthType(); - - if (authType == null || !VALID_AUTH_TYPES.contains(authType)) { - invalids.add("Invalid authType. Must be one of " + VALID_AUTH_TYPES_STR); + if (usingUri) { + invalids.add("It seems that you are trying to use a mongo connection string URI. Please " + + "extract relevant fields and fill the form with extracted values. For " + + "details, please check out the Appsmith's documentation for Mongo database. " + + "Alternatively, you may use 'Import from Connection String URI' option from the " + + "dropdown labelled 'Use Mongo Connection String URI' to use the URI connection string" + + " directly."); + } } - if (StringUtils.isEmpty(authentication.getDatabaseName())) { - invalids.add("Missing database name."); + DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); + if (authentication != null) { + DBAuth.Type authType = authentication.getAuthType(); + + if (authType == null || !VALID_AUTH_TYPES.contains(authType)) { + invalids.add("Invalid authType. Must be one of " + VALID_AUTH_TYPES_STR); + } + + if (StringUtils.isEmpty(authentication.getDatabaseName())) { + invalids.add("Missing database name."); + } + } - } - - /* - * - Ideally, it is never expected to be null because the SSL dropdown is set to a initial value. - */ - if (datasourceConfiguration.getConnection() == null - || datasourceConfiguration.getConnection().getSsl() == null - || datasourceConfiguration.getConnection().getSsl().getAuthType() == null) { - invalids.add("Appsmith server has failed to fetch SSL configuration from datasource configuration " + - "form. Please reach out to Appsmith customer support to resolve this."); + /* + * - Ideally, it is never expected to be null because the SSL dropdown is set to a initial value. + */ + if (datasourceConfiguration.getConnection() == null + || datasourceConfiguration.getConnection().getSsl() == null + || datasourceConfiguration.getConnection().getSsl().getAuthType() == null) { + invalids.add("Appsmith server has failed to fetch SSL configuration from datasource configuration " + + "form. Please reach out to Appsmith customer support to resolve this."); + } } return invalids; @@ -581,6 +728,7 @@ public class MongoPlugin extends BasePlugin { final DatasourceStructure structure = new DatasourceStructure(); List tables = new ArrayList<>(); structure.setTables(tables); + final MongoDatabase database = mongoClient.getDatabase(getDatabaseName(datasourceConfiguration)); return Flux.from(database.listCollectionNames()) diff --git a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/form.json index 0ee963a8f7..1878ba55df 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/main/resources/form.json +++ b/app/server/appsmith-plugins/mongoPlugin/src/main/resources/form.json @@ -3,6 +3,47 @@ { "sectionName": "Connection", "children": [ + { + "label": "Use Mongo Connection String URI Key", + "configProperty": "datasourceConfiguration.properties[0].key", + "controlType": "INPUT_TEXT", + "initialValue": "Use Mongo Connection String URI", + "hidden": true + }, + { + "label": "Use Mongo Connection String URI", + "configProperty": "datasourceConfiguration.properties[0].value", + "controlType": "DROP_DOWN", + "initialValue": "No", + "options": [ + { + "label": "Yes", + "value": "Yes" + }, + { + "label": "No", + "value": "No" + } + ] + }, + { + "label": "Connection String URI Key", + "configProperty": "datasourceConfiguration.properties[1].key", + "controlType": "INPUT_TEXT", + "initialValue": "Connection String URI", + "hidden": true + }, + { + "label": "Connection String URI", + "placeholderText": "mongodb+srv://:@test-db.swrsq.mongodb.net/myDatabase", + "configProperty": "datasourceConfiguration.properties[1].value", + "controlType": "INPUT_TEXT", + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "NOT_EQUALS", + "value": "Yes" + } + }, { "label": "Connection Mode", "configProperty": "datasourceConfiguration.connection.mode", @@ -17,7 +58,12 @@ "label": "Read / Write", "value": "READ_WRITE" } - ] + ], + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "EQUALS", + "value": "Yes" + } }, { "label": "Connection Type", @@ -33,7 +79,12 @@ "label": "Replica set", "value": "REPLICA_SET" } - ] + ], + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "EQUALS", + "value": "Yes" + } }, { "sectionName": null, @@ -44,13 +95,23 @@ "controlType": "KEYVALUE_ARRAY", "validationMessage": "Please enter a valid host", "validationRegex": "^((?![/:]).)*$", - "placeholderText": "myapp.abcde.mongodb.net" + "placeholderText": "myapp.abcde.mongodb.net", + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "EQUALS", + "value": "Yes" + } }, { "label": "Port", "configProperty": "datasourceConfiguration.endpoints[*].port", "dataType": "NUMBER", - "controlType": "KEYVALUE_ARRAY" + "controlType": "KEYVALUE_ARRAY", + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "EQUALS", + "value": "Yes" + } } ] }, @@ -58,12 +119,22 @@ "label": "Default Database Name", "placeholderText": "(Optional)", "configProperty": "datasourceConfiguration.connection.defaultDatabaseName", - "controlType": "INPUT_TEXT" + "controlType": "INPUT_TEXT", + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "EQUALS", + "value": "Yes" + } } ] }, { "sectionName": "Authentication", + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "EQUALS", + "value": "Yes" + }, "children": [ { "label": "Database Name", @@ -108,13 +179,18 @@ "controlType": "INPUT_TEXT", "placeholderText": "Password", "encrypted": true - } + } ] } ] }, { "sectionName": "SSL (optional)", + "hidden": { + "path": "datasourceConfiguration.properties[0].value", + "comparison": "EQUALS", + "value": "Yes" + }, "children": [ { "label": "SSL Mode", diff --git a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java index 7013c2f308..1709486140 100644 --- a/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java +++ b/app/server/appsmith-plugins/mongoPlugin/src/test/java/com/external/plugins/MongoPluginTest.java @@ -473,16 +473,129 @@ public class MongoPluginTest { } @Test - public void testErrorMessageOnSrvUrl() { + public void testErrorMessageOnSrvUriWithFormInterface() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - dsConfig.getEndpoints().get(0).setHost("mongodb+srv:://url.net"); + dsConfig.getEndpoints().get(0).setHost("mongodb+srv://user:pass@url.net/dbName"); + dsConfig.setProperties(List.of(new Property("Import from URI", "No"))); Mono> invalidsMono = Mono.just(pluginExecutor.validateDatasource(dsConfig)); StepVerifier.create(invalidsMono) .assertNext(invalids -> { assertTrue(invalids .stream() - .anyMatch(error -> error.contains("MongoDb SRV URLs are not yet supported"))); + .anyMatch(error -> error.contains("It seems that you are trying to use a mongo connection" + + " string URI. Please extract relevant fields and fill the form with extracted " + + "values. For details, please check out the Appsmith's documentation for Mongo " + + "database. Alternatively, you may use 'Import from Connection String URI' option " + + "from the dropdown labelled 'Use Mongo Connection String URI' to use the URI " + + "connection string directly."))); + }) + .verifyComplete(); + } + + @Test + public void testErrorMessageOnNonSrvUri() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + dsConfig.getEndpoints().get(0).setHost("mongodb://user:pass@url.net:1234,url.net:1234/dbName"); + dsConfig.setProperties(List.of(new Property("Import from URI", "No"))); + Mono> invalidsMono = Mono.just(pluginExecutor.validateDatasource(dsConfig)); + + StepVerifier.create(invalidsMono) + .assertNext(invalids -> { + assertTrue(invalids + .stream() + .anyMatch(error -> error.contains("It seems that you are trying to use a mongo connection" + + " string URI. Please extract relevant fields and fill the form with extracted " + + "values. For details, please check out the Appsmith's documentation for Mongo " + + "database. Alternatively, you may use 'Import from Connection String URI' option " + + "from the dropdown labelled 'Use Mongo Connection String URI' to use the URI " + + "connection string directly."))); + }) + .verifyComplete(); + } + + @Test + public void testInvalidsOnMissingUri() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + dsConfig.setProperties(List.of(new Property("Import from URI", "Yes"))); + Mono> invalidsMono = Mono.just(pluginExecutor.validateDatasource(dsConfig)); + + StepVerifier.create(invalidsMono) + .assertNext(invalids -> { + assertTrue(invalids + .stream() + .anyMatch(error -> error.contains("'Mongo Connection String URI' field is empty. Please " + + "edit the 'Mongo Connection URI' field to provide a connection uri to connect with."))); + }) + .verifyComplete(); + } + + @Test + public void testInvalidsOnBadSrvUriFormat() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + List properties = new ArrayList<>(); + properties.add(new Property("Import from URI", "Yes")); + properties.add(new Property("Srv Url", "mongodb+srv::username:password//url.net")); + dsConfig.setProperties(properties); + Mono> invalidsMono = Mono.just(pluginExecutor.validateDatasource(dsConfig)); + + StepVerifier.create(invalidsMono) + .assertNext(invalids -> { + assertTrue(invalids + .stream() + .anyMatch(error -> error.contains("Mongo Connection String URI does not seem to be in the" + + " correct format. Please check the URI once."))); + }) + .verifyComplete(); + } + + @Test + public void testInvalidsOnBadNonSrvUriFormat() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + List properties = new ArrayList<>(); + properties.add(new Property("Import from URI", "Yes")); + properties.add(new Property("Srv Url", "mongodb::username:password//url.net")); + dsConfig.setProperties(properties); + Mono> invalidsMono = Mono.just(pluginExecutor.validateDatasource(dsConfig)); + + StepVerifier.create(invalidsMono) + .assertNext(invalids -> { + assertTrue(invalids + .stream() + .anyMatch(error -> error.contains("Mongo Connection String URI does not seem to be in the" + + " correct format. Please check the URI once."))); + }) + .verifyComplete(); + } + + @Test + public void testInvalidsEmptyOnCorrectSrvUriFormat() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + List properties = new ArrayList<>(); + properties.add(new Property("Import from URI", "Yes")); + properties.add(new Property("Srv Url", "mongodb+srv://username:password@url.net/dbname")); + dsConfig.setProperties(properties); + Mono> invalidsMono = Mono.just(pluginExecutor.validateDatasource(dsConfig)); + + StepVerifier.create(invalidsMono) + .assertNext(invalids -> { + assertTrue(invalids.isEmpty()); + }) + .verifyComplete(); + } + + @Test + public void testInvalidsEmptyOnCorrectNonSrvUriFormat() { + DatasourceConfiguration dsConfig = createDatasourceConfiguration(); + List properties = new ArrayList<>(); + properties.add(new Property("Import from URI", "Yes")); + properties.add(new Property("Srv Url", "mongodb://username:password@url-1.net:1234,url-2:1234/dbname")); + dsConfig.setProperties(properties); + Mono> invalidsMono = Mono.just(pluginExecutor.validateDatasource(dsConfig)); + + StepVerifier.create(invalidsMono) + .assertNext(invalids -> { + assertTrue(invalids.isEmpty()); }) .verifyComplete(); } 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 b214257337..c2a6a750e9 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 @@ -2242,4 +2242,20 @@ public class DatabaseChangelog { NewAction.class ); } + + @ChangeSet(order = "067", id = "update-mongo-import-from-srv-field", author = "") + public void updateMongoImportFromSrvField(MongoTemplate mongoTemplate) { + Plugin mongoPlugin = mongoTemplate + .findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); + + List mongoDatasources = mongoTemplate + .find(query(where("pluginId").is(mongoPlugin.getId())), Datasource.class); + + mongoDatasources.stream() + .forEach(datasource -> { + datasource.getDatasourceConfiguration().setProperties(List.of(new Property("Use Mongo Connection " + + "String URI", "No"))); + mongoTemplate.save(datasource); + }); + } } From 78ccfadb61eb83d9ba380a6627dae05f4d65aed8 Mon Sep 17 00:00:00 2001 From: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Date: Mon, 10 May 2021 13:08:39 +0530 Subject: [PATCH 16/22] Fix: Make contents of tab selectable (#4390) --- app/client/src/components/ads/Tabs.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/client/src/components/ads/Tabs.tsx b/app/client/src/components/ads/Tabs.tsx index a70206d531..296a31abe4 100644 --- a/app/client/src/components/ads/Tabs.tsx +++ b/app/client/src/components/ads/Tabs.tsx @@ -14,7 +14,6 @@ export type TabProp = { }; const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>` - user-select: none; border-radius: 0px; height: 100%; .react-tabs { From 359d104f5f31939cfd04f46fa5919c4f5efd2110 Mon Sep 17 00:00:00 2001 From: akash-codemonk <67054171+akash-codemonk@users.noreply.github.com> Date: Mon, 10 May 2021 13:13:12 +0530 Subject: [PATCH 17/22] Show debug button in toast only for some particular errors (#4311) --- app/client/src/components/ads/Toast.tsx | 3 ++- app/client/src/sagas/ActionExecutionSagas.ts | 4 +++- app/client/src/sagas/EvaluationsSaga.ts | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/client/src/components/ads/Toast.tsx b/app/client/src/components/ads/Toast.tsx index 8685e03e1c..34154c8fb8 100644 --- a/app/client/src/components/ads/Toast.tsx +++ b/app/client/src/components/ads/Toast.tsx @@ -17,6 +17,7 @@ type ToastProps = ToastOptions & duration?: number; onUndo?: () => void; dispatchableAction?: { type: ReduxActionType; payload: any }; + showDebugButton?: boolean; hideProgressBar?: boolean; }; @@ -131,7 +132,7 @@ function ToastComponent(props: ToastProps & { undoAction?: () => void }) { ) : null}
{props.text} - {props.variant === Variant.danger ? ( + {props.variant === Variant.danger && props.showDebugButton ? ( ) : null}
diff --git a/app/client/src/sagas/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecutionSagas.ts index 06148a2194..dcd97f86c4 100644 --- a/app/client/src/sagas/ActionExecutionSagas.ts +++ b/app/client/src/sagas/ActionExecutionSagas.ts @@ -553,6 +553,7 @@ export function* executeActionSaga( Toaster.show({ text: createMessage(ERROR_API_EXECUTE, api.name), variant: Variant.danger, + showDebugButton: true, }); } else { PerformanceTracker.stopAsyncTracking( @@ -607,6 +608,7 @@ export function* executeActionSaga( Toaster.show({ text: createMessage(ERROR_API_EXECUTE, api.name), variant: Variant.danger, + showDebugButton: true, }); if (onError) { yield put( @@ -836,7 +838,7 @@ function* runActionSaga( Toaster.show({ text: createMessage(ERROR_ACTION_EXECUTE_FAIL, actionObject.name), - variant: Variant.warning, + variant: Variant.danger, }); } } else { diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index ff7d79a46a..a7e3fddc2d 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -97,6 +97,10 @@ const evalErrorHandler = (errors: EvalError[]) => { Toaster.show({ text: createMessage(ERROR_EVAL_TRIGGER, error.message), variant: Variant.danger, + showDebugButton: true, + }); + AppsmithConsole.error({ + text: createMessage(ERROR_EVAL_TRIGGER, error.message), }); break; } From 7b222b27fba2103ff3adedbb1e2da6e333e0f6b1 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Mon, 10 May 2021 13:51:43 +0530 Subject: [PATCH 18/22] Don't create new org, if the new user has been invited to one (#4380) --- .../server/services/UserServiceImpl.java | 31 ++++++++++--------- .../solutions/ExamplesOrganizationCloner.java | 5 +-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index f87b372dc2..31c51aeb28 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -398,19 +398,6 @@ public class UserServiceImpl extends BaseService i return Mono.just(user) .flatMap(this::validateObject) .flatMap(repository::save) - .zipWith(configService.getTemplateOrganizationId().defaultIfEmpty("")) - .flatMap(tuple -> { - final String templateOrganizationId = tuple.getT2(); - - if (!StringUtils.hasText(templateOrganizationId)) { - // Since template organization is not configured, we create an empty default organization. - final User savedUser = tuple.getT1(); - log.debug("Creating blank default organization for user '{}'.", savedUser.getEmail()); - return organizationService.createDefault(new Organization(), savedUser); - } - - return Mono.empty(); - }) .then(repository.findByEmail(user.getUsername())) .flatMap(analyticsService::trackNewUser); } @@ -456,7 +443,23 @@ public class UserServiceImpl extends BaseService i } return Mono.error(new AppsmithException(AppsmithError.USER_ALREADY_EXISTS_SIGNUP, user.getUsername())); }) - .switchIfEmpty(Mono.defer(() -> signupIfAllowed(user))) + .switchIfEmpty(Mono.defer(() -> { + return signupIfAllowed(user) + .zipWith(configService.getTemplateOrganizationId().defaultIfEmpty("")) + .flatMap(tuple -> { + final User savedUser = tuple.getT1(); + final String templateOrganizationId = tuple.getT2(); + + if (!StringUtils.hasText(templateOrganizationId)) { + // Since template organization is not configured, we create an empty default organization. + log.debug("Creating blank default organization for user '{}'.", savedUser.getEmail()); + return organizationService.createDefault(new Organization(), savedUser).thenReturn(savedUser); + } + + return Mono.just(savedUser); + }) + .flatMap(savedUser -> findByEmail(savedUser.getEmail())); + })) .flatMap(savedUser -> emailConfig.isWelcomeEmailEnabled() ? sendWelcomeEmail(savedUser, finalOriginHeader) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java index 74c861374e..221541a3ff 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java @@ -80,8 +80,9 @@ public class ExamplesOrganizationCloner { * @return Empty Mono. */ private Mono cloneExamplesOrganization(User user) { - if (user.getExamplesOrganizationId() != null) { - // This user already has an examples organization, don't have to do anything. + if (!CollectionUtils.isEmpty(user.getOrganizationIds())) { + // Don't create an examples organization if the user already has some organizations, perhaps because they + // were invited to some. return Mono.empty(); } From fc10625d410cb825efe36df490f87802fad2acaf Mon Sep 17 00:00:00 2001 From: Pawan Kumar Date: Mon, 10 May 2021 15:22:17 +0530 Subject: [PATCH 19/22] Fix: Cannot read property 'label' of undefined & Cannot read property 'length' of undefined (#4389) * fix for label of undefined * add check for data type Co-authored-by: root --- .../components/designSystems/appsmith/ChartComponent.tsx | 6 ++++-- .../designSystems/blueprint/DropdownComponent.tsx | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx index ca49f9b40b..43b3bf3b7b 100644 --- a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx @@ -185,7 +185,9 @@ class ChartComponent extends React.Component { getSeriesChartData = (data: ChartDataPoint[], categories: string[]) => { const dataMap: { [key: string]: string } = {}; - if (data.length === 0) { + + // if not array or (is array and array length is zero) + if (!Array.isArray(data) || (Array.isArray(data) && data.length === 0)) { return [ { value: "", @@ -218,7 +220,7 @@ class ChartComponent extends React.Component { const seriesChartData: Array> = this.getSeriesChartData(item.data, categories); + >> = this.getSeriesChartData(get(item, "data", []), categories); return { seriesName: item.seriesName, data: seriesChartData, diff --git a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx index f5a45770fe..3487aab4b8 100644 --- a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx @@ -337,7 +337,7 @@ class DropDownComponent extends React.Component { }; renderTag = (option: DropdownOption) => { - return option.label; + return option?.label; }; isOptionSelected = (selectedOption: DropdownOption) => { From 408517de39e3eeb98e9a4da81c21fb3f3bf8a4e0 Mon Sep 17 00:00:00 2001 From: NandanAnantharamu <67676905+NandanAnantharamu@users.noreply.github.com> Date: Mon, 10 May 2021 17:27:54 +0530 Subject: [PATCH 20/22] Datasource test with basic profile added (#4374) --- app/client/cypress/fixtures/testdata.json | 3 +- .../DatasourceBasicProfile_spec.js | 22 ++++++++++++++ .../cypress/locators/DatasourcesEditor.json | 5 +++- app/client/cypress/support/commands.js | 29 +++++++++++++------ 4 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceBasicProfile_spec.js diff --git a/app/client/cypress/fixtures/testdata.json b/app/client/cypress/fixtures/testdata.json index 622aac0aae..61d619d292 100644 --- a/app/client/cypress/fixtures/testdata.json +++ b/app/client/cypress/fixtures/testdata.json @@ -121,5 +121,6 @@ "clientSecret": "505dac16a21681f277b5fde97445be18", "accessTokenUrl": "https://oauth.mocklab.io/oauth/token", "oauthResponse": "169444434892406", - "authorizationURL": "https://oauth.mocklab.io/oauth/authorize" + "authorizationURL": "https://oauth.mocklab.io/oauth/authorize", + "basicURl": "https://envyenksqii9nf3.m.pipedream.net" } diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceBasicProfile_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceBasicProfile_spec.js new file mode 100644 index 0000000000..cbfdba5572 --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/DatasourceBasicProfile_spec.js @@ -0,0 +1,22 @@ +const testdata = require("../../../../fixtures/testdata.json"); +describe("Create a rest datasource", function() { + beforeEach(() => { + cy.startRoutesForDatasource(); + }); + + it("Create a rest datasource", function() { + cy.NavigateToAPI_Panel(); + cy.CreateAPI("Testapi"); + cy.enterDatasource(testdata.basicURl); + cy.get(".t--store-as-datasource").click(); + cy.addBasicProfileDetails("test", "test@123"); + cy.saveDatasource(); + cy.contains(".datasource-highlight", "envyenksqii9nf3.m.pipedream.net"); + cy.SaveAndRunAPI(); + cy.wait(2000); + var encodedStringBtoA = btoa("test:test@123"); + cy.log(encodedStringBtoA); + cy.ResponseStatusCheck(testdata.successStatusCode); + cy.ResponseTextCheck("Basic ".concat(encodedStringBtoA)); + }); +}); diff --git a/app/client/cypress/locators/DatasourcesEditor.json b/app/client/cypress/locators/DatasourcesEditor.json index 47aa855c89..defed053bb 100644 --- a/app/client/cypress/locators/DatasourcesEditor.json +++ b/app/client/cypress/locators/DatasourcesEditor.json @@ -35,5 +35,8 @@ "grantType": "[data-cy='authentication.grantType']", "authorizationURL":"[data-cy='authentication.authorizationUrl'] input", "authorisecode": "//div[contains(@class,'option') and text()='Authorization Code']", - "saveAndAuthorize": "button:contains('Save and Authorize')" + "saveAndAuthorize": "button:contains('Save and Authorize')", + "basic": "//div[contains(@class,'option') and text()='Basic']", + "basicUsername": "input[name='authentication.username']", + "basicPassword": "input[name='authentication.password']" } diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 0404b43bf3..bcf23bfc1a 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -286,6 +286,13 @@ Cypress.Commands.add( }, ); +Cypress.Commands.add("addBasicProfileDetails", (username, password) => { + cy.get(datasource.authType).click(); + cy.xpath(datasource.basic).click(); + cy.get(datasource.basicUsername).type(username); + cy.get(datasource.basicPassword).type(password); +}); + Cypress.Commands.add("firestoreDatasourceForm", () => { cy.get(datasourceEditor.datasourceConfigUrl).type( datasourceFormData["database-url"], @@ -474,6 +481,11 @@ Cypress.Commands.add("ResponseCheck", (textTocheck) => { cy.get(apiwidget.responseText).should("be.visible"); }); +Cypress.Commands.add("ResponseTextCheck", (textTocheck) => { + cy.ResponseCheck(); + cy.get(apiwidget.responseText).contains(textTocheck); +}); + Cypress.Commands.add("NavigateToAPI_Panel", () => { cy.get(pages.addEntityAPI) .should("be.visible") @@ -649,21 +661,20 @@ Cypress.Commands.add("SearchEntityandOpen", (apiname1) => { }); Cypress.Commands.add("enterDatasourceAndPath", (datasource, path) => { - cy.get(apiwidget.resourceUrl) - .first() - .click({ force: true }) - .type(datasource); - /* - cy.xpath(apiwidget.autoSuggest) - .first() - .click({ force: true }); - */ + cy.enterDatasource(datasource); cy.get(apiwidget.editResourceUrl) .first() .click({ force: true }) .type(path, { parseSpecialCharSequences: false }); }); +Cypress.Commands.add("enterDatasource", (datasource) => { + cy.get(apiwidget.resourceUrl) + .first() + .click({ force: true }) + .type(datasource); +}); + Cypress.Commands.add("changeZoomLevel", (zoomValue) => { cy.get(commonlocators.changeZoomlevel) .last() From b65466b11e3388e3e1b38251d576b299c5cdba0f Mon Sep 17 00:00:00 2001 From: Confidence Okoghenun Date: Mon, 10 May 2021 13:49:18 +0100 Subject: [PATCH 21/22] docs: Adds updates for live demo #2 --- office_hours.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/office_hours.md b/office_hours.md index d020c0d34d..d4b42be753 100644 --- a/office_hours.md +++ b/office_hours.md @@ -28,6 +28,15 @@ You can find the archives of the calls below with a brief summary of each sessio ## Archives +Appsmith Live Demo #2, 6th May 2021: Building a support helpdesk using Gmail and Postgres + +Video Link + +#### Summary + +Nikhil shows the community how to build a ticket support dashboard to assign emails to various org members using the Gmail API and Postgres. Questions from members of our community was also discussed. + +------------------ 29th April 2021: List widget, Release roadmap and more From 3b61af395e11143c43faa897f08c8928e54ac6e6 Mon Sep 17 00:00:00 2001 From: arunvjn <32433245+arunvjn@users.noreply.github.com> Date: Mon, 10 May 2021 18:58:38 +0530 Subject: [PATCH 22/22] Fix/text overflow json viewer (#4367) CSS fix to wrap long urls/text in json viewer. GSheets to occupy full width --- app/client/src/components/editorComponents/ApiResponseView.tsx | 1 + app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx | 1 + app/client/src/pages/Editor/routes.tsx | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/client/src/components/editorComponents/ApiResponseView.tsx b/app/client/src/components/editorComponents/ApiResponseView.tsx index 09dc38510e..145807ae65 100644 --- a/app/client/src/components/editorComponents/ApiResponseView.tsx +++ b/app/client/src/components/editorComponents/ApiResponseView.tsx @@ -30,6 +30,7 @@ const ResponseContainer = styled.div` ${ResizerCSS} // Initial height of bottom tabs height: 60%; + width: 100%; // Minimum height of bottom tabs as it can be resized min-height: 36px; background-color: ${(props) => props.theme.colors.apiPane.responseBody.bg}; diff --git a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx index d3c888987a..0dad7349a8 100644 --- a/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx +++ b/app/client/src/pages/Editor/QueryEditor/EditorJSONtoForm.tsx @@ -84,6 +84,7 @@ const TabbedViewContainer = styled.div` height: 50%; // Minimum height of bottom tabs as it can be resized min-height: 36px; + width: 100%; .react-tabs__tab-panel { overflow: hidden; } diff --git a/app/client/src/pages/Editor/routes.tsx b/app/client/src/pages/Editor/routes.tsx index 83210415aa..aef4477ba6 100644 --- a/app/client/src/pages/Editor/routes.tsx +++ b/app/client/src/pages/Editor/routes.tsx @@ -58,8 +58,7 @@ const DrawerWrapper = styled.div<{ isActionPath: any; }>` background-color: white; - width: ${(props) => - !props.isVisible ? "0px" : props.isActionPath ? "100%" : "75%"}; + width: ${(props) => (!props.isVisible ? "0" : "100%")}; height: 100%; `;