From 973cd850e752d8c89b126ca463d6dbd2658fb0bf Mon Sep 17 00:00:00 2001 From: Nidhi Date: Mon, 8 Feb 2021 17:11:31 +0530 Subject: [PATCH 01/19] Modified documentation links for plugins (#2909) * Modified documentation links for plugins * Modified documentation links for plugins --- .../server/migrations/DatabaseChangelog.java | 39 +++++++++++++++++++ 1 file changed, 39 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 97f912197a..bae4585458 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 @@ -1578,4 +1578,43 @@ public class DatabaseChangelog { public void clearUserDataCollection(MongoTemplate mongoTemplate) { mongoTemplate.dropCollection(UserData.class); } + + @ChangeSet(order = "050", id = "update-database-documentation-links-v1-2-1", author = "") + public void updateDatabaseDocumentationLinks_v1_2_1(MongoTemplate mongoTemplate) { + for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { + switch (plugin.getPackageName()) { + case "postgres-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-postgres"); + break; + case "mongo-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-mongodb"); + break; + case "elasticsearch-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-elasticsearch"); + break; + case "dynamo-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-dynamodb"); + break; + case "redis-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-redis"); + break; + case "mssql-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-mssql"); + break; + case "firestore-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-firestore"); + break; + case "redshift-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-redshift"); + break; + case "mysql-plugin": + plugin.setDocumentationLink("https://docs.appsmith.com/v/v1.2.1/datasource-reference/querying-mysql"); + break; + default: + continue; + } + + mongoTemplate.save(plugin); + } + } } From fbf6021080fd02be1b61cd6f9f613d86ea7d16f6 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Tue, 9 Feb 2021 06:59:45 +0530 Subject: [PATCH 02/19] Fix nested structure translation in DynamoDB (#2915) --- .../com/external/plugins/DynamoPlugin.java | 96 +++++++++---------- .../external/plugins/DynamoPluginTest.java | 17 ++++ 2 files changed, 63 insertions(+), 50 deletions(-) diff --git a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java index a75a070bba..e9f05096db 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java +++ b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java @@ -1,13 +1,13 @@ package com.external.plugins; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.Endpoint; -import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; -import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import lombok.NonNull; @@ -73,15 +73,15 @@ public class DynamoPlugin extends BasePlugin { DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { - return (Mono) Mono.fromCallable(() -> { + return Mono.fromCallable(() -> { ActionExecutionResult result = new ActionExecutionResult(); final String action = actionConfiguration.getPath(); if (StringUtils.isEmpty(action)) { - return Mono.error(new AppsmithPluginException( + throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Missing action name (like `ListTables`, `GetItem` etc.)." - )); + ); } final String body = actionConfiguration.getBody(); @@ -93,17 +93,17 @@ public class DynamoPlugin extends BasePlugin { } catch (IOException e) { final String message = "Error parsing the JSON body: " + e.getMessage(); log.warn(message, e); - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, message)); + throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, message); } final Class requestClass; try { requestClass = Class.forName("software.amazon.awssdk.services.dynamodb.model." + action + "Request"); } catch (ClassNotFoundException e) { - return Mono.error(new AppsmithPluginException( + throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_ERROR, "Unknown action: `" + action + "`. Note that action names are case-sensitive." - )); + ); } try { @@ -117,21 +117,20 @@ public class DynamoPlugin extends BasePlugin { } catch (AppsmithPluginException | InvocationTargetException | IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) { final String message = "Error executing the DynamoDB Action: " + (e.getCause() == null ? e : e.getCause()).getMessage(); log.warn(message, e); - return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message)); + throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, message); } result.setIsExecutionSuccess(true); System.out.println(Thread.currentThread().getName() + ": In the DynamoPlugin, got action execution result"); - return Mono.just(result); + return result; }) - .flatMap(obj -> obj) .subscribeOn(scheduler); } @Override public Mono datasourceCreate(DatasourceConfiguration datasourceConfiguration) { - return (Mono) Mono.fromCallable(() -> { + return Mono.fromCallable(() -> { final DynamoDbClientBuilder builder = DynamoDbClient.builder(); if (!CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { @@ -141,10 +140,10 @@ public class DynamoPlugin extends BasePlugin { final DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); if (authentication == null || StringUtils.isEmpty(authentication.getDatabaseName())) { - return Mono.error(new AppsmithPluginException( + throw new AppsmithPluginException( AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, "Missing region in datasource." - )); + ); } builder.region(Region.of(authentication.getDatabaseName())); @@ -153,9 +152,8 @@ public class DynamoPlugin extends BasePlugin { AwsBasicCredentials.create(authentication.getUsername(), authentication.getPassword()) )); - return Mono.justOrEmpty(builder.build()); + return builder.build(); }) - .flatMap(obj -> obj) .subscribeOn(scheduler); } @@ -259,7 +257,8 @@ public class DynamoPlugin extends BasePlugin { || value instanceof Integer || value instanceof Float || value instanceof Double) { - // These data types have a setter method that takes a the value as is. Nothing fancy here. + // This will *never* be successful. DynamoDB takes in numeric values as strings, which means the + // control should never flow here for numeric types. builderType.getMethod(setterName, value.getClass()).invoke(builder, value); } else if (value instanceof Map) { @@ -337,44 +336,41 @@ public class DynamoPlugin extends BasePlugin { } } - private static Map sdkToPlain(SdkPojo response) { - final Map plain = new HashMap<>(); + private static Object sdkToPlain(Object valueObj) { + if (valueObj instanceof SdkPojo) { + final SdkPojo response = (SdkPojo) valueObj; + final Map plain = new HashMap<>(); - for (final SdkField field : response.sdkFields()) { - Object value = field.getValueOrDefault(response); - - if (value instanceof SdkPojo) { - value = sdkToPlain((SdkPojo) value); - - } else if (value instanceof Map) { - final Map valueAsMap = (Map) value; - final Map plainMap = new HashMap<>(); - for (final Map.Entry entry : valueAsMap.entrySet()) { - final var key = entry.getKey(); - Object innerValue = entry.getValue(); - if (innerValue instanceof SdkPojo) { - innerValue = sdkToPlain((SdkPojo) innerValue); - } - plainMap.put(key, innerValue); - } - value = plainMap; - - } else if (value instanceof List) { - final List valueAsList = (List) value; - final List plainList = new ArrayList<>(); - for (Object item : valueAsList) { - if (item instanceof SdkPojo) { - item = sdkToPlain((SdkPojo) item); - } - plainList.add(item); - } - value = plainList; + for (final SdkField field : response.sdkFields()) { + Object value = field.getValueOrDefault(response); + plain.put(field.memberName(), sdkToPlain(value)); } - plain.put(field.memberName(), value); + return plain; + + } else if (valueObj instanceof Map) { + final Map valueAsMap = (Map) valueObj; + final Map plainMap = new HashMap<>(); + + for (final Map.Entry entry : valueAsMap.entrySet()) { + plainMap.put(entry.getKey(), sdkToPlain(entry.getValue())); + } + + return plainMap; + + } else if (valueObj instanceof Collection) { + final List valueAsList = (List) valueObj; + final List plainList = new ArrayList<>(); + + for (Object item : valueAsList) { + plainList.add(sdkToPlain(item)); + } + + return plainList; + } - return plain; + return valueObj; } private static boolean isUpperCase(String s) { diff --git a/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java b/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java index 7bfcb0a9c9..65ffd2675f 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java +++ b/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java @@ -221,4 +221,21 @@ public class DynamoPluginTest { .verifyComplete(); } + @Test + public void testScan() { + final String body = "{\n" + + " \"TableName\": \"cities\"\n" + + "}\n"; + + StepVerifier.create(execute("Scan", body)) + .assertNext(result -> { + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + final List items = (List) ((Map) result.getBody()).get("Items"); + assertEquals(2, items.size()); + }) + .verifyComplete(); + } + } From 078870f7c929fafe6a4cce3bbb36703aedb7f19f Mon Sep 17 00:00:00 2001 From: Trisha Anand Date: Tue, 9 Feb 2021 10:39:08 +0530 Subject: [PATCH 03/19] Communicate action execute on load changes in update layout (#2825) * 1. Update on load actions correctly 2. Send the changed actions with their changes as well as messages back to the client as part of the response. * Added test case to assert that the action updates are correctly recorded in updateLayout. * Code cleanup + added more comments for code readability * Incorporated review comments. --- .../server/controllers/ActionController.java | 4 +- .../server/controllers/LayoutController.java | 5 +- .../server/dtos/LayoutActionUpdateDTO.java | 16 ++ .../com/appsmith/server/dtos/LayoutDTO.java | 31 ++++ .../server/services/LayoutActionService.java | 7 +- .../services/LayoutActionServiceImpl.java | 37 ++++- .../server/services/NewActionService.java | 3 +- .../server/services/NewActionServiceImpl.java | 155 ++++++++++++++++-- .../services/LayoutActionServiceTest.java | 103 +++++++++++- .../server/services/LayoutServiceTest.java | 11 +- 10 files changed, 334 insertions(+), 38 deletions(-) create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutActionUpdateDTO.java create mode 100644 app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutDTO.java diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java index 7f34fb6d82..11062409f0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ActionController.java @@ -2,11 +2,11 @@ package com.appsmith.server.controllers; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.server.constants.Url; -import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ExecuteActionDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.ActionCollectionService; @@ -82,7 +82,7 @@ public class ActionController { } @PutMapping("/refactor") - public Mono> refactorActionName(@RequestBody RefactorNameDTO refactorNameDTO) { + public Mono> refactorActionName(@RequestBody RefactorNameDTO refactorNameDTO) { return layoutActionService.refactorActionName(refactorNameDTO) .map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java index 18996edb58..5cb2d43be2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/LayoutController.java @@ -2,6 +2,7 @@ package com.appsmith.server.controllers; import com.appsmith.server.constants.Url; import com.appsmith.server.domains.Layout; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.LayoutActionService; @@ -46,7 +47,7 @@ public class LayoutController { } @PutMapping("/{layoutId}/pages/{pageId}") - public Mono> updateLayout(@PathVariable String pageId, @PathVariable String layoutId, @RequestBody Layout layout) { + public Mono> updateLayout(@PathVariable String pageId, @PathVariable String layoutId, @RequestBody Layout layout) { return layoutActionService.updateLayout(pageId, layoutId, layout) .map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null)); } @@ -58,7 +59,7 @@ public class LayoutController { } @PutMapping("/refactor") - public Mono> refactorWidgetName(@RequestBody RefactorNameDTO refactorNameDTO) { + public Mono> refactorWidgetName(@RequestBody RefactorNameDTO refactorNameDTO) { return layoutActionService.refactorWidgetName(refactorNameDTO) .map(created -> new ResponseDTO<>(HttpStatus.OK.value(), created, null)); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutActionUpdateDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutActionUpdateDTO.java new file mode 100644 index 0000000000..0a3775fcca --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutActionUpdateDTO.java @@ -0,0 +1,16 @@ +package com.appsmith.server.dtos; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +/** + * This class would be used to send any action updates that have happened as part of update layout. The client should + * consume this structure to update the actions in its local storage (instead of fetching all the page actions afresh). + */ +public class LayoutActionUpdateDTO { + String id; + String name; + Boolean executeOnLoad; +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutDTO.java new file mode 100644 index 0000000000..97ddeed54a --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/LayoutDTO.java @@ -0,0 +1,31 @@ +package com.appsmith.server.dtos; + +import com.appsmith.server.domains.ScreenType; +import lombok.Getter; +import lombok.Setter; +import net.minidev.json.JSONObject; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Getter +@Setter +public class LayoutDTO { + + private String id; + + ScreenType screen; + + JSONObject dsl; + + List> layoutOnLoadActions; + + // All the actions which have been updated as part of updateLayout function call + List actionUpdates; + + // All the toast messages that the developer user should be displayed to inform about the consequences of update layout. + List messages; + + public Set userPermissions = new HashSet<>(); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java index 3929aadeba..cb43f494ac 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionService.java @@ -4,16 +4,17 @@ import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.RefactorNameDTO; +import com.appsmith.server.dtos.LayoutDTO; import reactor.core.publisher.Mono; public interface LayoutActionService { - Mono updateLayout(String pageId, String layoutId, Layout layout); + Mono updateLayout(String pageId, String layoutId, Layout layout); Mono moveAction(ActionMoveDTO actionMoveDTO); - Mono refactorWidgetName(RefactorNameDTO refactorNameDTO); + Mono refactorWidgetName(RefactorNameDTO refactorNameDTO); - Mono refactorActionName(RefactorNameDTO refactorNameDTO); + Mono refactorActionName(RefactorNameDTO refactorNameDTO); Mono updateAction(String id, ActionDTO action); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java index c4f4ac3462..d9ad17dda7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java @@ -7,8 +7,10 @@ import com.appsmith.server.domains.Layout; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.DslActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.RefactorNameDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.MustacheHelper; @@ -121,7 +123,7 @@ public class LayoutActionServiceImpl implements LayoutActionService { } @Override - public Mono refactorWidgetName(RefactorNameDTO refactorNameDTO) { + public Mono refactorWidgetName(RefactorNameDTO refactorNameDTO) { String pageId = refactorNameDTO.getPageId(); String layoutId = refactorNameDTO.getLayoutId(); String oldName = refactorNameDTO.getOldName(); @@ -136,7 +138,7 @@ public class LayoutActionServiceImpl implements LayoutActionService { } @Override - public Mono refactorActionName(RefactorNameDTO refactorNameDTO) { + public Mono refactorActionName(RefactorNameDTO refactorNameDTO) { String pageId = refactorNameDTO.getPageId(); String layoutId = refactorNameDTO.getLayoutId(); String oldName = refactorNameDTO.getOldName(); @@ -167,7 +169,7 @@ public class LayoutActionServiceImpl implements LayoutActionService { * @param newName * @return */ - private Mono refactorName(String pageId, String layoutId, String oldName, String newName) { + private Mono refactorName(String pageId, String layoutId, String oldName, String newName) { String regexPattern = preWord + oldName + postWord; Pattern oldNamePattern = Pattern.compile(regexPattern); @@ -466,11 +468,11 @@ public class LayoutActionServiceImpl implements LayoutActionService { } @Override - public Mono updateLayout(String pageId, String layoutId, Layout layout) { + public Mono updateLayout(String pageId, String layoutId, Layout layout) { JSONObject dsl = layout.getDsl(); if (dsl == null) { // There is no DSL here. No need to process anything. Return as is. - return Mono.just(layout); + return Mono.just(generateResponseDTO(layout)); } Set widgetNames = new HashSet<>(); @@ -496,6 +498,8 @@ public class LayoutActionServiceImpl implements LayoutActionService { Set edges = new HashSet<>(); Set actionsUsedInDSL = new HashSet<>(); List flatmapPageLoadActions = new ArrayList<>(); + List actionUpdates = new ArrayList<>(); + List messages = new ArrayList<>(); Mono>> allOnLoadActionsMono = pageLoadActionsUtil .findAllOnLoadActions(dynamicBindingNames, actionNames, pageId, edges, actionsUsedInDSL, flatmapPageLoadActions); @@ -504,7 +508,9 @@ public class LayoutActionServiceImpl implements LayoutActionService { return allOnLoadActionsMono .flatMap(allOnLoadActions -> { // Update these actions to be executed on load, unless the user has touched the executeOnLoad setting for this - return newActionService.setOnLoad((flatmapPageLoadActions)).thenReturn(allOnLoadActions); + return newActionService + .updateActionsExecuteOnLoad(flatmapPageLoadActions, pageId, actionUpdates, messages) + .thenReturn(allOnLoadActions); }) .zipWith(newPageService.findByIdAndLayoutsId(pageId, layoutId, MANAGE_PAGES, false) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.ACL_NO_RESOURCE_FOUND, @@ -542,7 +548,26 @@ public class LayoutActionServiceImpl implements LayoutActionService { } } return Mono.empty(); + }) + .map(savedLayout -> { + LayoutDTO layoutDTO = generateResponseDTO(savedLayout); + layoutDTO.setActionUpdates(actionUpdates); + layoutDTO.setMessages(messages); + return layoutDTO; }); } + private LayoutDTO generateResponseDTO(Layout layout) { + + LayoutDTO layoutDTO = new LayoutDTO(); + + layoutDTO.setId(layout.getId()); + layoutDTO.setDsl(layout.getDsl()); + layoutDTO.setScreen(layout.getScreen()); + layoutDTO.setLayoutOnLoadActions(layout.getLayoutOnLoadActions()); + layoutDTO.setUserPermissions(layout.getUserPermissions()); + + return layoutDTO; + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java index 64a6204d1d..d34efe9756 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionService.java @@ -6,6 +6,7 @@ import com.appsmith.server.domains.NewAction; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ExecuteActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; import org.springframework.data.domain.Sort; import org.springframework.util.MultiValueMap; import reactor.core.publisher.Flux; @@ -57,5 +58,5 @@ public interface NewActionService extends CrudService { Flux findByPageId(String pageId); - Mono setOnLoad(List actions); + Mono updateActionsExecuteOnLoad(List actions, String pageId, List actionUpdates, List messages); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java index 4c91518940..afd677a35a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java @@ -30,6 +30,7 @@ import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ExecuteActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.MustacheHelper; @@ -42,6 +43,7 @@ import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; @@ -57,6 +59,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; @@ -916,25 +919,147 @@ public class NewActionServiceImpl extends BaseService setOnLoad(List actions) { - if (actions == null) { - return Mono.just(FALSE); - } + public Mono updateActionsExecuteOnLoad(List onLoadActions, + String pageId, + List actionUpdates, + List messages) { List toUpdateActions = new ArrayList<>(); - for (ActionDTO action : actions) { - // If a user has ever set execute on load, this field can not be changed automatically. It has to be - // explicitly changed by the user again. Add the action to update only if this condition is false. - if (FALSE.equals(action.getUserSetOnLoad())) { - action.setExecuteOnLoad(TRUE); - toUpdateActions.add(action); - } - } - return Flux.fromIterable(toUpdateActions) - .flatMap(actionDTO -> updateUnpublishedAction(actionDTO.getId(), actionDTO)) - .then(Mono.just(TRUE)); + MultiValueMap params = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)); + params.add(FieldName.PAGE_ID, pageId); + + // Fetch all the actions which exist in this page. + Flux pageActionsFlux = this.getUnpublishedActions(params).cache(); + + // Before we update the actions, fetch all the actions which are currently set to execute on load. + Mono> existingOnPageLoadActionsMono = pageActionsFlux + .flatMap(action -> { + if (TRUE.equals(action.getExecuteOnLoad())) { + return Mono.just(action); + } + return Mono.empty(); + }) + .collectList(); + + return existingOnPageLoadActionsMono + .zipWith(pageActionsFlux.collectList()) + .flatMap( tuple -> { + List existingOnPageLoadActions = tuple.getT1(); + List pageActions = tuple.getT2(); + + // There are no actions in this page. No need to proceed further since no actions would get updated + if (pageActions.isEmpty()) { + return Mono.just(FALSE); + } + + // No actions require an update if no actions have been found as page load actions as well as + // existing on load page actions are empty + if (existingOnPageLoadActions.isEmpty() && (onLoadActions == null || onLoadActions.isEmpty())) { + return Mono.just(FALSE); + } + + // Extract names of existing pageload actions and new page load actions for quick lookup. + Set existingOnPageLoadActionNames = existingOnPageLoadActions + .stream() + .map(action -> action.getName()) + .collect(Collectors.toSet()); + + Set newOnLoadActionNames = onLoadActions + .stream() + .map(action -> action.getName()) + .collect(Collectors.toSet()); + + + // Calculate the actions which would need to be updated from execute on load TRUE to FALSE. + Set turnedOffActionNames = new HashSet<>(); + turnedOffActionNames.addAll(existingOnPageLoadActionNames); + turnedOffActionNames.removeAll(newOnLoadActionNames); + + // Calculate the actions which would need to be updated from execute on load FALSE to TRUE + Set turnedOnActionNames = new HashSet<>(); + turnedOnActionNames.addAll(newOnLoadActionNames); + turnedOnActionNames.removeAll(existingOnPageLoadActionNames); + + for (ActionDTO action : pageActions) { + + String actionName = action.getName(); + // If a user has ever set execute on load, this field can not be changed automatically. It has to be + // explicitly changed by the user again. Add the action to update only if this condition is false. + if (FALSE.equals(action.getUserSetOnLoad())) { + + // If this action is no longer an onload action, turn the execute on load to false + if (turnedOffActionNames.contains(actionName)) { + action.setExecuteOnLoad(FALSE); + toUpdateActions.add(action); + } + + // If this action is newly found to be on load, turn execute on load to true + if (turnedOnActionNames.contains(actionName)) { + action.setExecuteOnLoad(TRUE); + toUpdateActions.add(action); + } + } else { + // Remove the action name from either of the lists (if present) because this action should + // not be updated + turnedOnActionNames.remove(actionName); + turnedOffActionNames.remove(actionName); + } + } + + // Add newly turned on page actions to report back to the caller + actionUpdates.addAll( + addActionUpdatesForActionNames(pageActions, turnedOnActionNames) + ); + + // Add newly turned off page actions to report back to the caller + actionUpdates.addAll( + addActionUpdatesForActionNames(pageActions, turnedOffActionNames) + ); + + // Now add messages that would eventually be displayed to the developer user informing them + // about the action setting change. + if (!turnedOffActionNames.isEmpty()) { + messages.add(turnedOffActionNames.toString() + " will no longer be executed on page load"); + } + + if (!turnedOnActionNames.isEmpty()) { + messages.add(turnedOnActionNames.toString() + " will be executed automatically on page load"); + } + + // Finally update the actions which require an update + return Flux.fromIterable(toUpdateActions) + .flatMap(actionDTO -> updateUnpublishedAction(actionDTO.getId(), actionDTO)) + .then(Mono.just(TRUE)); + }); + } + + private List addActionUpdatesForActionNames(List pageActions, + Set actionNames) { + + return pageActions + .stream() + .filter(pageAction -> actionNames.contains(pageAction.getName())) + .map(pageAction -> { + LayoutActionUpdateDTO layoutActionUpdateDTO = new LayoutActionUpdateDTO(); + layoutActionUpdateDTO.setId(pageAction.getId()); + layoutActionUpdateDTO.setName(pageAction.getName()); + layoutActionUpdateDTO.setExecuteOnLoad(pageAction.getExecuteOnLoad()); + return layoutActionUpdateDTO; + }) + .collect(Collectors.toList()); } @Override diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java index f850509da5..1702990a76 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java @@ -11,6 +11,8 @@ import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.DslActionDTO; +import com.appsmith.server.dtos.LayoutActionUpdateDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.dtos.RefactorNameDTO; import com.appsmith.server.helpers.MockPluginExecutor; @@ -37,6 +39,7 @@ import reactor.test.StepVerifier; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -216,7 +219,7 @@ public class LayoutActionServiceTest { ActionDTO createdAction = newActionService.createAction(action).block(); - Layout firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); + LayoutDTO firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); RefactorNameDTO refactorNameDTO = new RefactorNameDTO(); @@ -225,7 +228,7 @@ public class LayoutActionServiceTest { refactorNameDTO.setOldName("beforeNameChange"); refactorNameDTO.setNewName("PostNameChange"); - Layout postNameChangeLayout = layoutActionService.refactorActionName(refactorNameDTO).block(); + LayoutDTO postNameChangeLayout = layoutActionService.refactorActionName(refactorNameDTO).block(); Mono postNameChangeActionMono = newActionService.findById(createdAction.getId(), READ_ACTIONS); @@ -238,11 +241,105 @@ public class LayoutActionServiceTest { DslActionDTO actionDTO = postNameChangeLayout.getLayoutOnLoadActions().get(0).iterator().next(); assertThat(actionDTO.getName()).isEqualTo("PostNameChange"); -// JSONObject newDsl = new JSONObject(Map.of("widgetName", "firstWidget", "mustacheProp", "{{ PostNameChange.data }}")); dsl.put("testField", "{{ PostNameChange.data }}"); assertThat(postNameChangeLayout.getDsl()).isEqualTo(dsl); }) .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void actionExecuteOnLoadChangeOnUpdateLayout() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor())); + + ActionDTO action1 = new ActionDTO(); + action1.setName("firstAction"); + action1.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration1 = new ActionConfiguration(); + actionConfiguration1.setHttpMethod(HttpMethod.GET); + action1.setActionConfiguration(actionConfiguration1); + action1.setDatasource(datasource); + + ActionDTO action2 = new ActionDTO(); + action2.setName("secondAction"); + action2.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration2 = new ActionConfiguration(); + actionConfiguration2.setHttpMethod(HttpMethod.GET); + action2.setActionConfiguration(actionConfiguration2); + action2.setDatasource(datasource); + + JSONObject dsl = new JSONObject(); + dsl.put("widgetName", "firstWidget"); + JSONArray temp = new JSONArray(); + temp.addAll(List.of(new JSONObject(Map.of("key", "testField")))); + dsl.put("dynamicBindingPathList", temp); + dsl.put("testField", "{{ firstAction.data }}"); + + Layout layout = testPage.getLayouts().get(0); + layout.setDsl(dsl); + + ActionDTO createdAction1 = newActionService.createAction(action1).block(); + ActionDTO createdAction2 = newActionService.createAction(action2).block(); + + Mono updateLayoutMono = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout); + + StepVerifier.create(updateLayoutMono) + .assertNext(updatedLayout -> { + log.debug("{}", updatedLayout.getMessages()); + DslActionDTO actionDTO = updatedLayout.getLayoutOnLoadActions().get(0).iterator().next(); + assertThat(actionDTO.getName()).isEqualTo("firstAction"); + + List actionUpdates = updatedLayout.getActionUpdates(); + assertThat(actionUpdates.size()).isEqualTo(1); + assertThat(actionUpdates.get(0).getName()).isEqualTo("firstAction"); + assertThat(actionUpdates.get(0).getExecuteOnLoad()).isTrue(); + }) + .verifyComplete(); + + StepVerifier.create(newActionService.findById(createdAction1.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isTrue()); + + StepVerifier.create(newActionService.findById(createdAction2.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isFalse()); + + dsl = new JSONObject(); + dsl.put("widgetName", "firstWidget"); + temp = new JSONArray(); + temp.addAll(List.of(new JSONObject(Map.of("key", "testField")))); + dsl.put("dynamicBindingPathList", temp); + dsl.put("testField", "{{ secondAction.data }}"); + + layout.setDsl(dsl); + + updateLayoutMono = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout); + + StepVerifier.create(updateLayoutMono) + .assertNext(updatedLayout -> { + log.debug("{}", updatedLayout.getMessages()); + DslActionDTO actionDTO = updatedLayout.getLayoutOnLoadActions().get(0).iterator().next(); + assertThat(actionDTO.getName()).isEqualTo("secondAction"); + + List actionUpdates = updatedLayout.getActionUpdates(); + assertThat(actionUpdates.size()).isEqualTo(2); + + Optional firstActionUpdateOptional = actionUpdates.stream().filter(actionUpdate -> actionUpdate.getName().equals("firstAction")).findFirst(); + LayoutActionUpdateDTO firstActionUpdate = firstActionUpdateOptional.get(); + assertThat(firstActionUpdate).isNotNull(); + assertThat(firstActionUpdate.getExecuteOnLoad()).isFalse(); + + Optional secondActionUpdateOptional = actionUpdates.stream().filter(actionUpdate -> actionUpdate.getName().equals("secondAction")).findFirst(); + LayoutActionUpdateDTO secondActionUpdate = secondActionUpdateOptional.get(); + assertThat(secondActionUpdate).isNotNull(); + assertThat(secondActionUpdate.getExecuteOnLoad()).isTrue(); + }) + .verifyComplete(); + + StepVerifier.create(newActionService.findById(createdAction1.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isFalse()); + + StepVerifier.create(newActionService.findById(createdAction2.getId())) + .assertNext(newAction -> assertThat(newAction.getUnpublishedAction().getExecuteOnLoad()).isTrue()); + + } + } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java index 401edcf19f..078f6d498e 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutServiceTest.java @@ -7,13 +7,12 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Datasource; import com.appsmith.server.domains.Layout; -import com.appsmith.server.domains.NewAction; -import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.PluginType; import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.DslActionDTO; +import com.appsmith.server.dtos.LayoutDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; @@ -205,7 +204,7 @@ public class LayoutServiceTest { Layout startLayout = layoutService.createLayout(page.getId(), testLayout).block(); - Mono updatedLayoutMono = layoutActionService.updateLayout("random-impossible-id-page", startLayout.getId(), updateLayout); + Mono updatedLayoutMono = layoutActionService.updateLayout("random-impossible-id-page", startLayout.getId(), updateLayout); StepVerifier .create(updatedLayoutMono) @@ -238,7 +237,7 @@ public class LayoutServiceTest { Mono startLayoutMono = pageMono.flatMap(page -> layoutService.createLayout(page.getId(), testLayout)); - Mono updatedLayoutMono = Mono.zip(pageMono, startLayoutMono) + Mono updatedLayoutMono = Mono.zip(pageMono, startLayoutMono) .flatMap(tuple -> { PageDTO page = tuple.getT1(); Layout startLayout = tuple.getT2(); @@ -276,7 +275,7 @@ public class LayoutServiceTest { Mono pageMono = createPage(app, testPage).cache(); - Mono testMono = pageMono + Mono testMono = pageMono .flatMap(page1 -> { List> monos = new ArrayList<>(); @@ -464,7 +463,7 @@ public class LayoutServiceTest { Mono pageMono = createPage(app, testPage).cache(); - Mono testMono = pageMono + Mono testMono = pageMono .flatMap(page1 -> { List> monos = new ArrayList<>(); From 36021f9abaeb75f8de65a2ab2b55dccd915a54eb Mon Sep 17 00:00:00 2001 From: Rishabh Saxena Date: Tue, 9 Feb 2021 11:05:06 +0530 Subject: [PATCH 04/19] fix viewer tabs arrow btns on mobile (#2910) --- app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx b/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx index b3e4d17239..f3922758af 100644 --- a/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx +++ b/app/client/src/pages/AppViewer/viewer/PageTabsContainer.tsx @@ -122,6 +122,8 @@ export const PageTabsContainer = (props: AppViewerHeaderProps) => { onMouseDown={() => startScrolling(true)} onMouseUp={stopScrolling} onMouseLeave={stopScrolling} + onTouchStart={() => startScrolling(true)} + onTouchEnd={stopScrolling} visible={shouldShowLeftArrow} > @@ -137,6 +139,8 @@ export const PageTabsContainer = (props: AppViewerHeaderProps) => { onMouseDown={() => startScrolling(false)} onMouseUp={stopScrolling} onMouseLeave={stopScrolling} + onTouchStart={() => startScrolling(false)} + onTouchEnd={stopScrolling} visible={shouldShowRightArrow} > From ac49a5aa873cabf7c2a01fee662ed8854961404b Mon Sep 17 00:00:00 2001 From: Shubhendra Singh Chauhan Date: Tue, 9 Feb 2021 11:38:54 +0530 Subject: [PATCH 05/19] Remove `true` from boolean attribute (#2892) Added .deepsource.toml file --- .deepsource.toml | 19 +++++++++++++++++++ app/client/src/components/ads/Button.tsx | 2 +- .../src/components/ads/EditableText.tsx | 2 +- .../designSystems/appsmith/PopoverVideo.tsx | 2 +- .../designSystems/appsmith/TableHeader.tsx | 2 +- .../designSystems/appsmith/help/HelpModal.tsx | 2 +- 6 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 .deepsource.toml diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000000..dfbccd88ae --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,19 @@ +version = 1 + +exclude_patterns = [ + "static/**", + "deploy/**", + "contributions/**" +] + +[[analyzers]] +name = "javascript" +enabled = true + + [analyzers.meta] + plugins = ["react"] + environment = [ + "nodejs", + "browser" + ] + dialect = "typescript" diff --git a/app/client/src/components/ads/Button.tsx b/app/client/src/components/ads/Button.tsx index 11c877be92..5afc28c04a 100644 --- a/app/client/src/components/ads/Button.tsx +++ b/app/client/src/components/ads/Button.tsx @@ -381,7 +381,7 @@ const TextLoadingState = ({ text }: { text?: string }) => ( ); const IconLoadingState = ({ size, icon }: { size?: Size; icon?: IconName }) => ( - + ); const getIconContent = (props: ButtonProps) => diff --git a/app/client/src/components/ads/EditableText.tsx b/app/client/src/components/ads/EditableText.tsx index 6346f67c16..3656267c22 100644 --- a/app/client/src/components/ads/EditableText.tsx +++ b/app/client/src/components/ads/EditableText.tsx @@ -263,7 +263,7 @@ export const EditableText = (props: EditableTextProps) => { onChange={onInputchange} onConfirm={onConfirm} value={value} - selectAllOnFocus={true} + selectAllOnFocus placeholder={props.placeholder || defaultValue} className={props.className} onCancel={onConfirm} diff --git a/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx b/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx index b08c3191dd..dc4d490fa0 100644 --- a/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx +++ b/app/client/src/components/designSystems/appsmith/PopoverVideo.tsx @@ -35,7 +35,7 @@ const PopoverVideo = (props: VideoComponentProps) => { minimal usePortal enforceFocus={false} - lazy={true} + lazy modifiers={{ flip: { behavior: ["right", "left", "bottom", "top"], diff --git a/app/client/src/components/designSystems/appsmith/TableHeader.tsx b/app/client/src/components/designSystems/appsmith/TableHeader.tsx index 8e94ede01d..eb96bcc07b 100644 --- a/app/client/src/components/designSystems/appsmith/TableHeader.tsx +++ b/app/client/src/components/designSystems/appsmith/TableHeader.tsx @@ -55,7 +55,7 @@ const PageNumberInput = (props: { min={1} max={props.pageCount || 1} buttonPosition="none" - clampValueOnBlur={true} + clampValueOnBlur onBlur={(e: any) => { const oldPageNo = Number(props.pageNo || 0); const value = e.target.value; diff --git a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx index d455d75cd3..87efe89db2 100644 --- a/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx +++ b/app/client/src/components/designSystems/appsmith/help/HelpModal.tsx @@ -114,7 +114,7 @@ class HelpModal extends React.Component { <> {isHelpModalOpen && ( Date: Tue, 9 Feb 2021 12:57:10 +0530 Subject: [PATCH 06/19] When fetching unpublished actions and unpublished pages, check if the undeployed version is not deleted before fetching. (#2792) * When fetching unpublished actions and unpublished pages, check if the undeployed version is not deleted before fetching. * Only add unpublished page deleted at check if the view mode is false (aka edit mode) * 1. Update action repository to fetch only non deleted actions if fetching actions in edit mode. 2. Added test cases to assert re-using of deleted page and action names. --- .../CustomNewActionRepositoryImpl.java | 51 ++++++++++++++++--- .../CustomNewPageRepositoryImpl.java | 39 ++++++++++++-- .../services/LayoutActionServiceTest.java | 47 +++++++++++++++++ .../server/services/PageServiceTest.java | 40 +++++++++++++++ 4 files changed, 165 insertions(+), 12 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java index ee484ccee3..fb7582bef1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewActionRepositoryImpl.java @@ -37,8 +37,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl findByUnpublishedNameAndPageId(String name, String pageId, AclPermission aclPermission) { Criteria nameCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.name)).is(name); Criteria pageCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId); + // In case an action has been deleted in edit mode, but still exists in deployed mode, NewAction object would exist. To handle this, only fetch non-deleted actions + Criteria deletedCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.deletedAt)).is(null); - return queryOne(List.of(nameCriteria, pageCriteria), aclPermission); + return queryOne(List.of(nameCriteria, pageCriteria, deletedCriteria), aclPermission); } @Override @@ -77,17 +79,26 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl findByPageIdAndViewMode(String pageId, Boolean viewMode, AclPermission aclPermission) { - Criteria pageCriteria; + + List criteria = new ArrayList<>(); + + Criteria pageCriterion; // Fetch published actions if (Boolean.TRUE.equals(viewMode)) { - pageCriteria = where(fieldName(QNewAction.newAction.publishedAction) + "." + fieldName(QNewAction.newAction.publishedAction.pageId)).is(pageId); + pageCriterion = where(fieldName(QNewAction.newAction.publishedAction) + "." + fieldName(QNewAction.newAction.publishedAction.pageId)).is(pageId); + criteria.add(pageCriterion); } // Fetch unpublished actions else { - pageCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId); + pageCriterion = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.pageId)).is(pageId); + criteria.add(pageCriterion); + + // In case an action has been deleted in edit mode, but still exists in deployed mode, NewAction object would exist. To handle this, only fetch non-deleted actions + Criteria deletedCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.deletedAt)).is(null); + criteria.add(deletedCriteria); } - return queryAll(List.of(pageCriteria), aclPermission); + return queryAll(criteria, aclPermission); } @Override @@ -163,6 +174,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl findUnpublishedActionsByNameInAndPageId(Set names, String pageId, AclPermission permission) { List criteriaList = new ArrayList<>(); + if (names != null) { Criteria namesCriteria = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.name)).in(names); criteriaList.add(namesCriteria); @@ -196,6 +216,10 @@ public class CustomNewActionRepositoryImpl extends BaseAppsmithRepositoryImpl criteria = new ArrayList<>(); - return queryAll(List.of(applicationCriteria), aclPermission); + Criteria applicationCriterion = where(fieldName(QNewAction.newAction.applicationId)).is(applicationId); + criteria.add(applicationCriterion); + + if (Boolean.FALSE.equals(viewMode)) { + // In case an action has been deleted in edit mode, but still exists in deployed mode, NewAction object would exist. To handle this, only fetch non-deleted actions + Criteria deletedCriterion = where(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.deletedAt)).is(null); + criteria.add(deletedCriterion); + } + + return queryAll(criteria, aclPermission); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java index 426e9c263b..2f6852ebbb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/CustomNewPageRepositoryImpl.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.ArrayList; import java.util.List; import static org.springframework.data.mongodb.core.query.Criteria.where; @@ -33,37 +34,65 @@ public class CustomNewPageRepositoryImpl extends BaseAppsmithRepositoryImpl findByIdAndLayoutsIdAndViewMode(String id, String layoutId, AclPermission aclPermission, Boolean viewMode) { - Criteria idCriterion = getIdCriteria(id); String layoutsIdKey; String layoutsKey; + List criteria = new ArrayList<>(); + Criteria idCriterion = getIdCriteria(id); + criteria.add(idCriterion); + if (Boolean.TRUE.equals(viewMode)) { layoutsKey = fieldName(QNewPage.newPage.publishedPage) + "." + fieldName(QNewPage.newPage.publishedPage.layouts); } else { layoutsKey = fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.layouts); + + // In case a page has been deleted in edit mode, but still exists in deployed mode, NewPage object would exist. To handle this, only fetch non-deleted pages + Criteria deletedCriterion = where (fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.deletedAt)).is(null); + criteria.add(deletedCriterion); } layoutsIdKey = layoutsKey + "." + fieldName(QLayout.layout.id); Criteria layoutCriterion = where(layoutsIdKey).is(layoutId); + criteria.add(layoutCriterion); - List criteria = List.of(idCriterion, layoutCriterion); return queryOne(criteria, aclPermission); } @Override public Mono findByNameAndViewMode(String name, AclPermission aclPermission, Boolean viewMode) { - Criteria nameCriterion = getNameCriterion(name, viewMode); - return queryOne(List.of(nameCriterion), aclPermission); + List criteria = new ArrayList<>(); + + Criteria nameCriterion = getNameCriterion(name, viewMode); + criteria.add(nameCriterion); + + if (Boolean.FALSE.equals(viewMode)) { + // In case a page has been deleted in edit mode, but still exists in deployed mode, NewPage object would exist. To handle this, only fetch non-deleted pages + Criteria deletedCriterion = where (fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.deletedAt)).is(null); + criteria.add(deletedCriterion); + } + + return queryOne(criteria, aclPermission); } @Override public Mono findByNameAndApplicationIdAndViewMode(String name, String applicationId, AclPermission aclPermission, Boolean viewMode) { + + List criteria = new ArrayList<>(); + Criteria nameCriterion = getNameCriterion(name, viewMode); + criteria.add(nameCriterion); Criteria applicationIdCriterion = where(fieldName(QNewPage.newPage.applicationId)).is(applicationId); + criteria.add(applicationIdCriterion); - return queryOne(List.of(nameCriterion, applicationIdCriterion), aclPermission); + if (Boolean.FALSE.equals(viewMode)) { + // In case a page has been deleted in edit mode, but still exists in deployed mode, NewPage object would exist. To handle this, only fetch non-deleted pages + Criteria deletedCriteria = where (fieldName(QNewPage.newPage.unpublishedPage) + "." + fieldName(QNewPage.newPage.unpublishedPage.deletedAt)).is(null); + criteria.add(deletedCriteria); + } + + return queryOne(criteria, aclPermission); } @Override diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java index 1702990a76..f776a86215 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/LayoutActionServiceTest.java @@ -247,6 +247,53 @@ public class LayoutActionServiceTest { .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void refactorActionNameToDeletedName() { + Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())).thenReturn(Mono.just(new MockPluginExecutor())); + + ActionDTO action = new ActionDTO(); + action.setName("Query1"); + action.setPageId(testPage.getId()); + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHttpMethod(HttpMethod.GET); + action.setActionConfiguration(actionConfiguration); + action.setDatasource(datasource); + + Layout layout = testPage.getLayouts().get(0); + + ActionDTO firstAction = newActionService.createAction(action).block(); + + LayoutDTO firstLayout = layoutActionService.updateLayout(testPage.getId(), layout.getId(), layout).block(); + + applicationPageService.publish(testPage.getApplicationId()).block(); + + newActionService.deleteUnpublishedAction(firstAction.getId()).block(); + + // Create another action with the same name as the erstwhile deleted action + action.setId(null); + ActionDTO secondAction = newActionService.createAction(action).block(); + + RefactorNameDTO refactorNameDTO = new RefactorNameDTO(); + refactorNameDTO.setPageId(testPage.getId()); + refactorNameDTO.setLayoutId(firstLayout.getId()); + refactorNameDTO.setOldName("Query1"); + refactorNameDTO.setNewName("NewActionName"); + + layoutActionService.refactorActionName(refactorNameDTO).block(); + + Mono postNameChangeActionMono = newActionService.findById(secondAction.getId(), READ_ACTIONS); + + StepVerifier + .create(postNameChangeActionMono) + .assertNext(updatedAction -> { + + assertThat(updatedAction.getUnpublishedAction().getName()).isEqualTo("NewActionName"); + + }) + .verifyComplete(); + } + @Test @WithUserDetails(value = "api_user") public void actionExecuteOnLoadChangeOnUpdateLayout() { diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java index c8ecef61c7..c8cff36efa 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java @@ -266,6 +266,46 @@ public class PageServiceTest { .verifyComplete(); } + @Test + @WithUserDetails(value = "api_user") + public void reuseDeletedPageName() { + + PageDTO testPage = new PageDTO(); + testPage.setName("reuseDeletedPageName"); + setupTestApplication(); + testPage.setApplicationId(application.getId()); + + // Create Page + PageDTO firstPage = applicationPageService.createPage(testPage).block(); + + // Publish the application + applicationPageService.publish(application.getId()); + + //Delete Page in edit mode + applicationPageService.deleteUnpublishedPage(firstPage.getId()).block(); + + testPage.setId(null); + testPage.setName("New Page Name"); + // Create Second Page + PageDTO secondPage = applicationPageService.createPage(testPage).block(); + + //Update the name of the new page + PageDTO newPage = new PageDTO(); + newPage.setId(secondPage.getId()); + newPage.setName("reuseDeletedPageName"); + Mono updatePageNameMono = newPageService.updatePage(secondPage.getId(), newPage); + + StepVerifier + .create(updatePageNameMono) + .assertNext(page -> { + assertThat(page).isNotNull(); + assertThat(page.getId()).isNotNull(); + assertThat("reuseDeletedPageName".equals(page.getName())); + + }) + .verifyComplete(); + } + @After public void purgeAllPages() { From c0e44d0cd911c48517dd979bbcd05ac68d2b220a Mon Sep 17 00:00:00 2001 From: Nidhi Date: Tue, 9 Feb 2021 15:00:22 +0530 Subject: [PATCH 07/19] Added null check for error messages (#2922) * Added null check for error messages * Added a generic null check for future null pointers from other sources. --- .../pluginExceptions/AppsmithPluginError.java | 2 ++ .../external/models/DatasourceTestResult.java | 12 +++++++++ .../DatasourceTestResultTest.java | 21 +++++++++++++++ .../com/external/plugins/MySqlPlugin.java | 26 ++++++++++--------- 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 app/server/appsmith-interfaces/src/test/java/com.appsmith.external.models/DatasourceTestResultTest.java diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java index 2d6aabd1c7..b5fbe1d8bb 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/exceptions/pluginExceptions/AppsmithPluginError.java @@ -21,6 +21,8 @@ public enum AppsmithPluginError { AppsmithErrorAction.DEFAULT), PLUGIN_JSON_PARSE_ERROR(500, 5006, "Plugin failed to parse JSON \"{0}\" with error: {1}", AppsmithErrorAction.DEFAULT), + PLUGIN_DATASOURCE_TEST_GENERIC_ERROR(500, 5007, "Plugin failed to test with the given configuration. Please reach out to Appsmith customer support to report this", + AppsmithErrorAction.LOG_EXTERNALLY), ; private final Integer httpErrorCode; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java index df69c176e6..09caf20151 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceTestResult.java @@ -1,11 +1,14 @@ package com.appsmith.external.models; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.util.CollectionUtils; +import java.util.Arrays; import java.util.Set; +import java.util.stream.Collectors; @Getter @Setter @@ -21,6 +24,15 @@ public class DatasourceTestResult { * @param invalids String messages that explain why the test failed. */ public DatasourceTestResult(String... invalids) { + if (invalids == null) { + invalids = new String[]{AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage()}; + } else { + invalids = Arrays + .stream(invalids) + .map(x -> x == null ? AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage() : x) + .toArray(String[]::new); + } + this.invalids = Set.of(invalids); } diff --git a/app/server/appsmith-interfaces/src/test/java/com.appsmith.external.models/DatasourceTestResultTest.java b/app/server/appsmith-interfaces/src/test/java/com.appsmith.external.models/DatasourceTestResultTest.java new file mode 100644 index 0000000000..d31b18ba0c --- /dev/null +++ b/app/server/appsmith-interfaces/src/test/java/com.appsmith.external.models/DatasourceTestResultTest.java @@ -0,0 +1,21 @@ +package com.appsmith.external.models; + +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class DatasourceTestResultTest { + + @Test + public void testNewDatasourceTestResult_NullInvalidArray() { + DatasourceTestResult nullInvalidsResult = new DatasourceTestResult((String) null); + assertNotNull(nullInvalidsResult); + assertTrue(nullInvalidsResult.getInvalids().contains(AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage())); + + nullInvalidsResult = new DatasourceTestResult(new String[]{null}); + assertNotNull(nullInvalidsResult); + assertTrue(nullInvalidsResult.getInvalids().contains(AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage())); + } +} \ No newline at end of file diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java index a4d0ba4fe3..3f95085eec 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java @@ -347,13 +347,15 @@ public class MySqlPlugin extends BasePlugin { @Override public Mono testDatasource(DatasourceConfiguration datasourceConfiguration) { return datasourceCreate(datasourceConfiguration) - .flatMap(connection -> { - return Mono.from(connection.close()); - }) + .flatMap(connection -> Mono.from(connection.close())) .then(Mono.just(new DatasourceTestResult())) .onErrorResume(error -> { - log.error("Error when testing MySQL datasource.", error); - return Mono.just(new DatasourceTestResult(error.getMessage())); + // We always expect to have an error object, but the error object may not be well formed + final String errorMessage = error.getMessage() == null + ? AppsmithPluginError.PLUGIN_DATASOURCE_TEST_GENERIC_ERROR.getMessage() + : error.getMessage(); + System.out.println("Error when testing MySQL datasource. " + errorMessage); + return Mono.just(new DatasourceTestResult(errorMessage)); }) .subscribeOn(scheduler); @@ -541,14 +543,14 @@ public class MySqlPlugin extends BasePlugin { return structure; }) .onErrorMap(e -> { - if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) { - return new AppsmithPluginException( - AppsmithPluginError.PLUGIN_ERROR, - e.getMessage() - ); - } + if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) { + return new AppsmithPluginException( + AppsmithPluginError.PLUGIN_ERROR, + e.getMessage() + ); + } - return e; + return e; }) .subscribeOn(scheduler); } From 785c81df2335fe474aa18d017deec6d911e4ecd0 Mon Sep 17 00:00:00 2001 From: abhishek nayak Date: Tue, 9 Feb 2021 17:38:43 +0530 Subject: [PATCH 08/19] Update messages.ts --- app/client/src/constants/messages.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/client/src/constants/messages.ts b/app/client/src/constants/messages.ts index 01964f6296..b0bf40aefd 100644 --- a/app/client/src/constants/messages.ts +++ b/app/client/src/constants/messages.ts @@ -8,12 +8,12 @@ export const FIELD_REQUIRED_ERROR = "This field is required"; export const VALID_FUNCTION_NAME_ERROR = "Must be a valid variable name (camelCase)"; export const UNIQUE_NAME_ERROR = "Name must be unique"; -export const NAME_SPACE_ERROR = "Name must not have spaces"; +export const NAME_SPACE_ERROR = "Name cannot have spaces"; export const FORM_VALIDATION_EMPTY_EMAIL = "Please enter an email"; export const FORM_VALIDATION_INVALID_EMAIL = "Please provide a valid email address"; -export const ENTER_VIDEO_URL = "Please provide a valid url"; +export const ENTER_VIDEO_URL = "Please provide a valid URL"; export const FORM_VALIDATION_EMPTY_PASSWORD = "Please enter the password"; export const FORM_VALIDATION_PASSWORD_RULE = @@ -27,11 +27,11 @@ export const LOGIN_PAGE_PASSWORD_INPUT_LABEL = "Password"; export const LOGIN_PAGE_EMAIL_INPUT_PLACEHOLDER = "Email"; export const LOGIN_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password"; export const LOGIN_PAGE_INVALID_CREDS_ERROR = - "It looks like you may have entered incorrect/invalid credentials. Please try again or reset password using the button below."; + "You may have entered incorrect/invalid credentials. Please try again or reset your password."; export const LOGIN_PAGE_INVALID_CREDS_FORGOT_PASSWORD_LINK = "Reset Password"; export const NEW_TO_APPSMITH = "New to Appsmith?"; -export const LOGIN_PAGE_LOGIN_BUTTON_TEXT = "sign in"; +export const LOGIN_PAGE_LOGIN_BUTTON_TEXT = "Sign in"; export const LOGIN_PAGE_FORGOT_PASSWORD_TEXT = "Forgot Password"; export const LOGIN_PAGE_REMEMBER_ME_LABEL = "Remember"; export const LOGIN_PAGE_SIGN_UP_LINK_TEXT = "Sign up"; @@ -44,16 +44,16 @@ export const SIGNUP_PAGE_NAME_INPUT_LABEL = "Name"; export const SIGNUP_PAGE_PASSWORD_INPUT_LABEL = "Password"; export const SIGNUP_PAGE_PASSWORD_INPUT_PLACEHOLDER = "Password"; export const SIGNUP_PAGE_LOGIN_LINK_TEXT = "Sign In"; -export const SIGNUP_PAGE_NAME_INPUT_SUBTEXT = "How should we call you?"; +export const SIGNUP_PAGE_NAME_INPUT_SUBTEXT = "What should we call you?"; export const SIGNUP_PAGE_SUBMIT_BUTTON_TEXT = "Sign Up"; -export const ALREADY_HAVE_AN_ACCOUNT = "Already have an account?"; +export const ALREADY_HAVE_AN_ACCOUNT = "You may already have an account."; export const SIGNUP_PAGE_SUCCESS = "Awesome! You have successfully registered."; export const SIGNUP_PAGE_SUCCESS_LOGIN_BUTTON_TEXT = "Login"; export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_LABEL = "New Password"; export const RESET_PASSWORD_PAGE_PASSWORD_INPUT_PLACEHOLDER = "New Password"; -export const RESET_PASSWORD_LOGIN_LINK_TEXT = "Back to Sign In"; +export const RESET_PASSWORD_LOGIN_LINK_TEXT = "Back to sign in page"; export const RESET_PASSWORD_PAGE_TITLE = "Reset Password"; export const RESET_PASSWORD_SUBMIT_BUTTON_TEXT = "Reset"; export const RESET_PASSWORD_PAGE_SUBTITLE = @@ -63,9 +63,9 @@ export const RESET_PASSWORD_RESET_SUCCESS = "Your password has been reset"; //"Y export const RESET_PASSWORD_RESET_SUCCESS_LOGIN_LINK = "Login"; export const RESET_PASSWORD_EXPIRED_TOKEN = - "The password reset link has expired. Please try generating a new link"; + "This password reset link has expired. Please try generating a new link"; export const RESET_PASSWORD_INVALID_TOKEN = - "The password reset link is invalid. Please try generating a new link"; + "This password reset link is invalid. Please try generating a new link"; export const RESET_PASSWORD_FORGOT_PASSWORD_LINK = "Forgot Password"; export const FORGOT_PASSWORD_PAGE_EMAIL_INPUT_LABEL = "Email"; @@ -81,7 +81,7 @@ export const PRIVACY_POLICY_LINK = "Privacy Policy"; export const TERMS_AND_CONDITIONS_LINK = "Terms and Conditions"; export const ERROR_500 = - "We apologize, Something went wrong. We're working to fix things."; + "Whoops, something went wrong. This is unexpected, and we'll look into this."; export const ERROR_0 = "We could not connect to our servers. Please check your network connection"; export const ERROR_401 = @@ -159,7 +159,7 @@ export const LIGHTNING_MENU_API_CREATE_NEW = "Create new API"; export const LIGHTNING_MENU_OPTION_TEXT = "Plain Text"; export const LIGHTNING_MENU_OPTION_JS = "Write JS"; export const LIGHTNING_MENU_OPTION_HTML = "Write HTML"; -export const CHECK_REQUEST_BODY = "Check Request body to debug?"; +export const CHECK_REQUEST_BODY = "Please check request body to debug?"; export const DONT_SHOW_THIS_AGAIN = "Don't show this again"; export const SHOW_REQUEST = "Show Request"; @@ -168,7 +168,7 @@ export const TABLE_FILTER_COLUMN_TYPE_CALLOUT = export const WIDGET_SIDEBAR_TITLE = "Widgets"; export const WIDGET_SIDEBAR_CAPTION = - "To add a widget, please drag and drop a widget on the canvas to the right"; + "To add a widget, please click + and add widget to the canvas."; export const GOOGLE_RECAPTCHA_KEY_ERROR = "Google Re-Captcha Token Generation failed! Please check the Re-captcha Site Key."; export const GOOGLE_RECAPTCHA_DOMAIN_ERROR = From b83bdd9b30be2fd78bc810107719f9d88684fdf6 Mon Sep 17 00:00:00 2001 From: hetunandu Date: Tue, 9 Feb 2021 17:52:34 +0530 Subject: [PATCH 09/19] update workflow node version --- .github/workflows/client.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index b67ebd449a..3fe7cc7651 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -48,10 +48,10 @@ jobs: - name: Figure out the PR number run: echo ${{ github.event.pull_request.number }} - - name: Use Node.js 10.16.3 + - name: Use Node.js 14.15.4 uses: actions/setup-node@v1 with: - node-version: "10.16.3" + node-version: "14.15.4" # Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again - name: Cache npm dependencies From f109c9bb9cc03261b0ad654980ab23d29d700414 Mon Sep 17 00:00:00 2001 From: hetunandu Date: Tue, 9 Feb 2021 17:56:31 +0530 Subject: [PATCH 10/19] Update one more node js version in the client workflow --- .github/workflows/client.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index 3fe7cc7651..566b38f060 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -151,10 +151,10 @@ jobs: if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' uses: actions/checkout@v2 - - name: Use Node.js 10.16.3 + - name: Use Node.js 14.15.4 uses: actions/setup-node@v1 with: - node-version: "10.16.3" + node-version: "14.15.4" # Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again - name: Cache npm dependencies From ac67514d12d51b57bc6c0ee8749a5a3908fd4eaf Mon Sep 17 00:00:00 2001 From: Sumit Kumar Date: Tue, 9 Feb 2021 18:05:59 +0530 Subject: [PATCH 11/19] Add discord channel link to Server Setup guide. (#2932) --- contributions/ServerSetup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contributions/ServerSetup.md b/contributions/ServerSetup.md index ca9b4111e1..69d117a400 100644 --- a/contributions/ServerSetup.md +++ b/contributions/ServerSetup.md @@ -100,5 +100,5 @@ example: ![381611580157_ pic_hd](https://user-images.githubusercontent.com/4025839/105710505-2ead5300-5f52-11eb-9549-531e459e86ea.jpg) ## Need Assistance -- If you are unable to resolve any issue while doing the setup, please initiate a Github discussion or send an email to support@appsmith.com. We'll be happy to help you. +- If you are unable to resolve any issue while doing the setup, please feel free to ask questions on our [Discord channel](https://discord.com/invite/rBTTVJp) or initiate a [Github discussion](https://github.com/appsmithorg/appsmith/discussions) or send an email to `support@appsmith.com`. We'll be happy to help you. - In case you notice any discrepancy, please raise an issue on Github and/or send an email to support@appsmith.com. From 0861836db4494ab4a1e6a2944e097be55cab2069 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Tue, 9 Feb 2021 19:11:16 +0530 Subject: [PATCH 12/19] Fix bindings in Firestore plugin's where condition value (#2934) --- .../server/helpers/MustacheHelper.java | 4 +-- .../server/helpers/MustacheHelperTest.java | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java index 349ff4e16c..a68bd66e68 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/MustacheHelper.java @@ -251,14 +251,14 @@ public class MustacheHelper { } else if (value instanceof List) { for (Object childValue : (List) value) { - if (isDomainModel(childValue.getClass())) { + if (childValue != null && isDomainModel(childValue.getClass())) { renderFieldValues(childValue, context); } } } else if (value instanceof Map) { for (Object childValue : ((Map) value).values()) { - if (isDomainModel(childValue.getClass())) { + if (childValue != null && isDomainModel(childValue.getClass())) { renderFieldValues(childValue, context); } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java index dffc93b718..d9b550a883 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/MustacheHelperTest.java @@ -5,15 +5,11 @@ import com.appsmith.external.models.Connection; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Endpoint; import com.appsmith.external.models.Property; -import com.fasterxml.jackson.databind.ObjectMapper; import org.assertj.core.api.IterableAssert; import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -29,13 +25,8 @@ import static org.assertj.core.api.Assertions.assertThat; // Disabling this so we may use `Arrays.asList` with single argument, which is easier to refactor, just for tests. "ArraysAsListWithZeroOrOneArgument" ) -@RunWith(SpringRunner.class) -@SpringBootTest public class MustacheHelperTest { - @Autowired - private ObjectMapper objectMapper; - private void checkTokens(String template, List expected) { assertThat(tokenize(template)).isEqualTo(expected); } @@ -406,7 +397,14 @@ public class MustacheHelperTest { new Property("param2", "{{ queryParam2 }}") )); - final Map context = Map.of( + configuration.setPluginSpecifiedTemplates(Arrays.asList( + null, + new Property("prop1", "{{ pluginSpecifiedProp1 }}"), + null, + new Property("prop2", "{{ pluginSpecifiedProp2 }}") + )); + + final Map context = new HashMap<>(Map.of( "body", "rendered body", "path", "rendered path", "next", "rendered next", @@ -416,7 +414,12 @@ public class MustacheHelperTest { "bodyParam2", "rendered bodyParam2", "queryParam1", "rendered queryParam1", "queryParam2", "rendered queryParam2" - ); + )); + + context.putAll(Map.of( + "pluginSpecifiedProp1", "rendered pluginSpecifiedProp1", + "pluginSpecifiedProp2", "rendered pluginSpecifiedProp2" + )); assertKeys(configuration).hasSameElementsAs(context.keySet()); @@ -440,6 +443,13 @@ public class MustacheHelperTest { new Property("param1", "rendered queryParam1"), new Property("param2", "rendered queryParam2") ); + + assertThat(configuration.getPluginSpecifiedTemplates()).containsExactly( + null, + new Property("prop1", "rendered pluginSpecifiedProp1"), + null, + new Property("prop2", "rendered pluginSpecifiedProp2") + ); } @Test From 8439affd67ef3eaa920ef0ec888d309c72b76eb6 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Tue, 9 Feb 2021 19:14:50 +0530 Subject: [PATCH 13/19] Update node to latest LTS version (#2931) --- app/client/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/client/package.json b/app/client/package.json index db2892f260..03a669a856 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "engines": { - "node": "^10.16.3", - "npm": "^6.9.0" + "node": "^14.15.4", + "npm": "^6.14.10" }, "cracoConfig": "craco.dev.config.js", "dependencies": { From eafadb8988271f6633b5dc39387c260bfd342043 Mon Sep 17 00:00:00 2001 From: Abhinav Jha Date: Tue, 9 Feb 2021 20:53:05 +0530 Subject: [PATCH 14/19] Update nvmrc to match the node version in package.json (#2943) --- app/client/.nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/client/.nvmrc b/app/client/.nvmrc index 03128968bf..518633e168 100644 --- a/app/client/.nvmrc +++ b/app/client/.nvmrc @@ -1 +1 @@ -lts/dubnium +lts/fermium From c78a578247ee7805f41f574756c7b89969ccb252 Mon Sep 17 00:00:00 2001 From: Pawan Kumar Date: Wed, 10 Feb 2021 10:29:47 +0530 Subject: [PATCH 15/19] Fix: Cannot read property 'closeModal' of null (#2911) * update filepicker uppy implementation * update uppy implementation * fix uppy implementation * remove typo * remove consoles * remove unused variable Co-authored-by: Pawan Kumar --- app/client/package.json | 16 +- app/client/src/widgets/FilepickerWidget.tsx | 149 ++++++++----- app/client/yarn.lock | 224 ++++++++++---------- 3 files changed, 221 insertions(+), 168 deletions(-) diff --git a/app/client/package.json b/app/client/package.json index 03a669a856..effa27e4af 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -35,14 +35,14 @@ "@types/react-table": "^7.0.13", "@types/styled-components": "^5.1.3", "@types/tinycolor2": "^1.4.2", - "@uppy/core": "^1.8.2", - "@uppy/dashboard": "^1.6.2", - "@uppy/file-input": "^1.3.1", - "@uppy/google-drive": "^1.3.2", - "@uppy/onedrive": "^0.1.1", - "@uppy/react": "^1.4.5", - "@uppy/url": "^1.3.2", - "@uppy/webcam": "^1.3.1", + "@uppy/core": "^1.16.0", + "@uppy/dashboard": "^1.16.0", + "@uppy/file-input": "^1.4.22", + "@uppy/google-drive": "^1.5.22", + "@uppy/onedrive": "^1.1.22", + "@uppy/react": "^1.11.2", + "@uppy/url": "^1.5.16", + "@uppy/webcam": "^1.8.4", "@welldone-software/why-did-you-render": "^4.2.5", "algoliasearch": "^4.2.0", "axios": "^0.21.1", diff --git a/app/client/src/widgets/FilepickerWidget.tsx b/app/client/src/widgets/FilepickerWidget.tsx index 67e0371412..ed05de6c88 100644 --- a/app/client/src/widgets/FilepickerWidget.tsx +++ b/app/client/src/widgets/FilepickerWidget.tsx @@ -27,13 +27,11 @@ class FilePickerWidget extends BaseWidget< FilePickerWidgetProps, FilePickerWidgetState > { - uppy: any; - constructor(props: FilePickerWidgetProps) { super(props); this.state = { - version: 0, isLoading: false, + uppy: this.initializeUppy(), }; } @@ -63,24 +61,70 @@ class FilePickerWidget extends BaseWidget< }; } - refreshUppy = (props: FilePickerWidgetProps) => { - this.uppy = Uppy({ + static getTriggerPropertyMap(): TriggerPropertiesMap { + return { + onFilesSelected: true, + }; + } + + /** + * if uppy is not initialized before, initialize it + * else setState of uppy instance + */ + initializeUppy = () => { + const uppyState = { id: this.props.widgetId, autoProceed: false, allowMultipleUploads: true, debug: false, + restrictions: { + maxFileSize: this.props.maxFileSize + ? this.props.maxFileSize * 1024 * 1024 + : null, + maxNumberOfFiles: this.props.maxNumFiles, + minNumberOfFiles: null, + allowedFileTypes: + this.props.allowedFileTypes && + (this.props.allowedFileTypes.includes("*") || + _.isEmpty(this.props.allowedFileTypes)) + ? null + : this.props.allowedFileTypes, + }, + }; + + return Uppy(uppyState); + }; + + /** + * set states on the uppy instance with new values + */ + reinitializeUppy = (props: FilePickerWidgetProps) => { + const uppyState = { + id: props.widgetId, + autoProceed: false, + allowMultipleUploads: true, + debug: false, restrictions: { maxFileSize: props.maxFileSize ? props.maxFileSize * 1024 * 1024 : null, maxNumberOfFiles: props.maxNumFiles, minNumberOfFiles: null, allowedFileTypes: props.allowedFileTypes && - (props.allowedFileTypes.includes("*") || + (this.props.allowedFileTypes.includes("*") || _.isEmpty(props.allowedFileTypes)) ? null : props.allowedFileTypes, }, - }) + }; + + this.state.uppy.setOptions(uppyState); + }; + + /** + * add all uppy events listeners needed + */ + initializeUppyEventListeners = () => { + this.state.uppy .use(Dashboard, { target: "body", metaFields: [], @@ -101,7 +145,11 @@ class FilePickerWidget extends BaseWidget< disablePageScrollWhenModalOpen: true, proudlyDisplayPoweredByUppy: false, onRequestCloseModal: () => { - this.uppy.getPlugin("Dashboard").closeModal(); + const plugin = this.state.uppy.getPlugin("Dashboard"); + + if (plugin) { + plugin.closeModal(); + } }, locale: {}, }) @@ -117,7 +165,8 @@ class FilePickerWidget extends BaseWidget< facingMode: "user", locale: {}, }); - this.uppy.on("file-removed", (file: any) => { + + this.state.uppy.on("file-removed", (file: any) => { const updatedFiles = this.props.files ? this.props.files.filter((dslFile) => { return file.id !== dslFile.id; @@ -125,47 +174,50 @@ class FilePickerWidget extends BaseWidget< : []; this.props.updateWidgetMetaProperty("files", updatedFiles); }); - this.uppy.on("file-added", (file: any) => { - const dslFiles = this.props.files ? [...this.props.files] : []; - const reader = new FileReader(); - reader.readAsDataURL(file.data); - reader.onloadend = () => { - const base64data = reader.result; - const binaryReader = new FileReader(); - binaryReader.readAsBinaryString(file.data); - binaryReader.onloadend = () => { - const rawData = binaryReader.result; - const textReader = new FileReader(); - textReader.readAsText(file.data); - textReader.onloadend = () => { - const text = textReader.result; - const newFile = { - id: file.id, - base64: base64data, - blob: file.data, - raw: rawData, - text: text, - name: file.meta ? file.meta.name : undefined, + this.state.uppy.on("files-added", (files: any[]) => { + const dslFiles = this.props.files ? [...this.props.files] : []; + + const fileReaderPromises = files.map((file) => { + const reader = new FileReader(); + return new Promise((resolve) => { + reader.readAsDataURL(file.data); + reader.onloadend = () => { + const base64data = reader.result; + const binaryReader = new FileReader(); + binaryReader.readAsBinaryString(file.data); + binaryReader.onloadend = () => { + const rawData = binaryReader.result; + const textReader = new FileReader(); + textReader.readAsText(file.data); + textReader.onloadend = () => { + const text = textReader.result; + const newFile = { + id: file.id, + base64: base64data, + blob: file.data, + raw: rawData, + text: text, + name: file.meta ? file.meta.name : undefined, + }; + + resolve(newFile); + }; }; - dslFiles.push(newFile); - this.props.updateWidgetMetaProperty("files", dslFiles); }; - }; - }; + }); + }); + + Promise.all(fileReaderPromises).then((files) => { + this.props.updateWidgetMetaProperty("files", dslFiles.concat(files)); + }); }); - this.uppy.on("upload", () => { + + this.state.uppy.on("upload", () => { this.onFilesSelected(); }); - this.setState({ version: this.state.version + 1 }); }; - static getTriggerPropertyMap(): TriggerPropertiesMap { - return { - onFilesSelected: true, - }; - } - /** * this function is called when user selects the files and it do two things: * 1. calls the action if any @@ -208,29 +260,30 @@ class FilePickerWidget extends BaseWidget< prevProps.files.length > 0 && this.props.files === undefined ) { - this.uppy.reset(); + this.state.uppy.reset(); } else if ( !shallowequal(prevProps.allowedFileTypes, this.props.allowedFileTypes) || prevProps.maxNumFiles !== this.props.maxNumFiles || prevProps.maxFileSize !== this.props.maxFileSize ) { - this.refreshUppy(this.props); + this.reinitializeUppy(this.props); } } componentDidMount() { super.componentDidMount(); - this.refreshUppy(this.props); + + this.initializeUppyEventListeners(); } componentWillUnmount() { - this.uppy.close(); + this.state.uppy.close(); } getPageView() { return ( Date: Wed, 10 Feb 2021 11:27:21 +0530 Subject: [PATCH 16/19] Add IN operator support for forms' hidden fields (#2939) --- .../src/components/formControls/BaseControl.tsx | 4 +++- .../src/components/formControls/utils.test.ts | 16 ++++++++++++++++ app/client/src/components/formControls/utils.ts | 4 ++++ .../src/main/resources/editor.json | 7 ++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/client/src/components/formControls/BaseControl.tsx b/app/client/src/components/formControls/BaseControl.tsx index 61afa2872e..6b5dfc1120 100644 --- a/app/client/src/components/formControls/BaseControl.tsx +++ b/app/client/src/components/formControls/BaseControl.tsx @@ -13,7 +13,9 @@ export type ComparisonOperations = | "EQUALS" | "NOT_EQUALS" | "LESSER" - | "GREATER"; + | "GREATER" + | "IN" + | "NOT_IN"; export type HiddenType = | boolean diff --git a/app/client/src/components/formControls/utils.test.ts b/app/client/src/components/formControls/utils.test.ts index 0aabdc5685..8eb25215da 100644 --- a/app/client/src/components/formControls/utils.test.ts +++ b/app/client/src/components/formControls/utils.test.ts @@ -54,6 +54,22 @@ describe("isHidden test", () => { comparison: "NOT_EQUALS", }, }, + { + values: { name: "Name", config: { type: "Different BODY" } }, + hidden: { + path: "config.type", + value: ["EMAIL", "BODY"], + comparison: "IN", + }, + }, + { + values: { name: "Name", config: { type: "BODY" } }, + hidden: { + path: "config.type", + value: ["EMAIL", "BODY"], + comparison: "NOT_IN", + }, + }, { values: undefined, hidden: false, diff --git a/app/client/src/components/formControls/utils.ts b/app/client/src/components/formControls/utils.ts index 2e2616d803..0afa6604b0 100644 --- a/app/client/src/components/formControls/utils.ts +++ b/app/client/src/components/formControls/utils.ts @@ -15,6 +15,10 @@ export const isHidden = (values: any, hiddenConfig?: HiddenType) => { return valueAtPath > value; case "LESSER": return valueAtPath < value; + case "IN": + return Array.isArray(value) && value.includes(valueAtPath); + case "NOT_IN": + return Array.isArray(value) && !value.includes(valueAtPath); default: return true; } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json index 97f2e828ee..e592ec6a3a 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor.json @@ -188,7 +188,12 @@ { "label": "Body", "configProperty": "actionConfiguration.body", - "controlType": "QUERY_DYNAMIC_TEXT" + "controlType": "QUERY_DYNAMIC_TEXT", + "hidden": { + "path": "actionConfiguration.pluginSpecifiedTemplates[0].value", + "comparison": "IN", + "value": ["GET_DOCUMENT", "GET_COLLECTION", "DELETE_DOCUMENT"] + } } ] } From 4becc500059891b428c7a1e05dcacdb9e29f800e Mon Sep 17 00:00:00 2001 From: Abhinav Jha Date: Wed, 10 Feb 2021 12:34:11 +0530 Subject: [PATCH 17/19] Fix issue where the parentRowSpace of the new widget generated from the query was incorrect. Resulting in incorrect pageSize calculations (#2944) --- app/client/src/sagas/WidgetOperationSagas.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 4334f3de20..c00400bba5 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -1346,10 +1346,10 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction) { rightColumn: columns, columns, rows, - parentId: "0", + parentId: MAIN_CONTAINER_WIDGET_ID, widgetName, renderMode: RenderModes.CANVAS, - parentRowSpace: 1, + parentRowSpace: GridDefaults.DEFAULT_GRID_ROW_HEIGHT, parentColumnSpace: 1, isLoading: false, props: { @@ -1362,7 +1362,11 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction) { topRow, rightColumn, bottomRow, - } = yield calculateNewWidgetPosition(newWidget, "0", widgets); + } = yield calculateNewWidgetPosition( + newWidget, + MAIN_CONTAINER_WIDGET_ID, + widgets, + ); newWidget = { ...newWidget, From 8c72e901ee8fbb526adb83edc93059dced510e2d Mon Sep 17 00:00:00 2001 From: Rishabh Saxena Date: Wed, 10 Feb 2021 13:13:41 +0530 Subject: [PATCH 18/19] Fix input's onSubmit action being triggered if its validation is failing (#2960) --- app/client/src/widgets/InputWidget.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/client/src/widgets/InputWidget.tsx b/app/client/src/widgets/InputWidget.tsx index 4388ec1122..1957c0dbcd 100644 --- a/app/client/src/widgets/InputWidget.tsx +++ b/app/client/src/widgets/InputWidget.tsx @@ -154,10 +154,11 @@ class InputWidget extends BaseWidget { | React.KeyboardEvent | React.KeyboardEvent, ) => { + const { isValid, onSubmit } = this.props; const isEnterKey = e.key === "Enter" || e.keyCode === 13; - if (isEnterKey && this.props.onSubmit) { + if (isEnterKey && onSubmit && isValid) { super.executeAction({ - dynamicString: this.props.onSubmit, + dynamicString: onSubmit, event: { type: EventType.ON_SUBMIT, callback: this.onSubmitSuccess, From ed1c926c8a69fb340f2044c587f3784989c99064 Mon Sep 17 00:00:00 2001 From: Shrikant Sharat Kandula Date: Wed, 10 Feb 2021 13:28:35 +0530 Subject: [PATCH 19/19] Add datasource structure support for DynamoDB (#2919) --- .../external/models/DatasourceStructure.java | 1 + .../com/external/plugins/DynamoPlugin.java | 25 +++++++++++++++++++ .../external/plugins/DynamoPluginTest.java | 24 ++++++++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java index f2c5144060..99eb13633b 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/DatasourceStructure.java @@ -8,6 +8,7 @@ import java.util.List; @Data @NoArgsConstructor +@AllArgsConstructor public class DatasourceStructure { List tables; diff --git a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java index e9f05096db..883db8e1f2 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java +++ b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java @@ -6,6 +6,7 @@ import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.Endpoint; import com.appsmith.external.plugins.BasePlugin; @@ -29,6 +30,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClientBuilder; import software.amazon.awssdk.services.dynamodb.model.DynamoDbResponse; +import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -38,6 +40,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -202,6 +205,28 @@ public class DynamoPlugin extends BasePlugin { ) .subscribeOn(scheduler); } + + @Override + public Mono getStructure(DynamoDbClient ddb, DatasourceConfiguration datasourceConfiguration) { + return Mono.fromCallable(() -> { + final ListTablesResponse listTablesResponse = ddb.listTables(); + + List tables = new ArrayList<>(); + for (final String tableName : listTablesResponse.tableNames()) { + tables.add(new DatasourceStructure.Table( + DatasourceStructure.TableType.TABLE, + tableName, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList() + )); + } + + return new DatasourceStructure(tables); + + }).subscribeOn(scheduler); + } + } private static String toLowerCamelCase(String action) { diff --git a/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java b/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java index 65ffd2675f..39b6c03dae 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java +++ b/app/server/appsmith-plugins/dynamoPlugin/src/test/java/com/external/plugins/DynamoPluginTest.java @@ -4,6 +4,7 @@ import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.Endpoint; import lombok.extern.log4j.Log4j; import org.junit.BeforeClass; @@ -29,6 +30,7 @@ import java.net.URI; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -91,8 +93,6 @@ public class DynamoPluginTest { )) .build()); - System.out.println(ddb.listTables()); - Endpoint endpoint = new Endpoint(); endpoint.setHost(host); endpoint.setPort(port.longValue()); @@ -238,4 +238,24 @@ public class DynamoPluginTest { .verifyComplete(); } + @Test + public void testStructure() { + final Mono structureMono = pluginExecutor + .datasourceCreate(dsConfig) + .flatMap(conn -> pluginExecutor.getStructure(conn, dsConfig)); + + StepVerifier.create(structureMono) + .assertNext(structure -> { + assertNotNull(structure); + assertNotNull(structure.getTables()); + assertEquals( + List.of("cities"), + structure.getTables().stream() + .map(DatasourceStructure.Table::getName) + .collect(Collectors.toList()) + ); + }) + .verifyComplete(); + } + }