diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java index 059205809b..0a11ec5ded 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/PluginExecutor.java @@ -21,6 +21,7 @@ import reactor.core.scheduler.Schedulers; import reactor.util.function.Tuple2; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -313,4 +314,15 @@ public interface PluginExecutor extends ExtensionPoint, CrudTemplateService { default Mono getDatasourceMetadata(DatasourceConfiguration datasourceConfiguration) { return Mono.just(datasourceConfiguration); } + + /** + * This method is supposed to provide help with any update required to template queries that are used to create + * the actual select, updated, insert etc. queries as part of the generate CRUD page feature. Any plugin that + * needs special handling should override this method. e.g. in case of the S3 plugin some special handling is + * required because (a) it uses UQI config form (b) it has concept of bucket instead of table. + */ + default Mono sanitizeGenerateCRUDPageTemplateInfo( + List actionConfigurationList, Object... args) { + return Mono.empty(); + } } diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java index 284ba9e91d..57edae8fd1 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java @@ -87,6 +87,8 @@ import static com.external.plugins.constants.FieldName.BUCKET; import static com.external.plugins.constants.FieldName.COMMAND; import static com.external.plugins.constants.FieldName.CREATE_DATATYPE; import static com.external.plugins.constants.FieldName.CREATE_EXPIRY; +import static com.external.plugins.constants.FieldName.KEY_BUCKET; +import static com.external.plugins.constants.FieldName.KEY_DATA; import static com.external.plugins.constants.FieldName.LIST_EXPIRY; import static com.external.plugins.constants.FieldName.LIST_PAGINATE; import static com.external.plugins.constants.FieldName.LIST_PREFIX; @@ -110,6 +112,7 @@ import static com.external.plugins.constants.S3PluginConstants.YES; import static com.external.utils.DatasourceUtils.getS3ClientBuilder; import static com.external.utils.TemplateUtils.getTemplates; import static java.lang.Boolean.TRUE; +import static org.apache.commons.collections.CollectionUtils.isEmpty; @Slf4j public class AmazonS3Plugin extends BasePlugin { @@ -1035,5 +1038,28 @@ public class AmazonS3Plugin extends BasePlugin { .upload(bucketName, path, inputStream, objectMetadata) .waitForUploadResult(); } + + /** + * This method is supposed to provide help with any update required to template queries that are used to create + * the actual select, updated, insert etc. queries as part of the generate CRUD page feature. Any plugin that + * needs special handling should override this method. e.g. in case of the S3 plugin some special handling is + * required because (a) it uses UQI config form (b) it has concept of bucket instead of table. + */ + @Override + public Mono sanitizeGenerateCRUDPageTemplateInfo( + List actionConfigurationList, Object... args) { + if (isEmpty(actionConfigurationList)) { + return Mono.empty(); + } + + /* Add mapping to replace template bucket name with user chosen bucket everywhere in the template */ + Map mappedColumnsAndTableName = (Map) args[0]; + final String userSelectedBucketName = (String) args[1]; + Map formData = actionConfigurationList.get(0).getFormData(); + mappedColumnsAndTableName.put( + (String) ((Map) formData.get(KEY_BUCKET)).get(KEY_DATA), userSelectedBucketName); + + return Mono.empty(); + } } } diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/constants/FieldName.java b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/constants/FieldName.java index 103610a82a..80b7533e36 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/constants/FieldName.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/constants/FieldName.java @@ -36,4 +36,6 @@ public class FieldName { public static final String LIST_SORT = LIST + "." + SORT; public static final String LIST_PAGINATE = LIST + "." + PAGINATE; public static final String SMART_SUBSTITUTION = "smartSubstitution"; + public static final String KEY_BUCKET = "bucket"; + public static final String KEY_DATA = "data"; } diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java b/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java index 784b67059c..97e3f69b35 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/test/java/com/external/plugins/AmazonS3PluginTest.java @@ -98,6 +98,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import static org.springframework.util.CollectionUtils.isEmpty; @Slf4j public class AmazonS3PluginTest { @@ -1497,4 +1498,32 @@ public class AmazonS3PluginTest { .assertNext(result -> assertEquals(0, result.getInvalids().size())) .verifyComplete(); } + + @Test + public void verify_sanitizeGenerateCRUDPageTemplateInfo_doesNothing_onEmptyActionConfig() { + AmazonS3Plugin.S3PluginExecutor pluginExecutor = new AmazonS3Plugin.S3PluginExecutor(); + List actionConfigurationList = new ArrayList<>(); + Map mappedColumnsAndTableName = new HashMap<>(); + pluginExecutor + .sanitizeGenerateCRUDPageTemplateInfo(actionConfigurationList, mappedColumnsAndTableName, "test") + .block(); + assertEquals(0, actionConfigurationList.size()); + assertEquals(true, isEmpty(mappedColumnsAndTableName)); + } + + @Test + public void verify_sanitizeGenerateCRUDPageTemplateInfo_addsInfoToReplaceTemplateBucket_withUserSelectedBucket() { + Map mappedColumnsAndTableName = new HashMap<>(); + String userSelectedBucketName = "userBucket"; + ActionConfiguration actionConfiguration = new ActionConfiguration(); + Map formData = new HashMap<>(); + setDataValueSafelyInFormData(formData, "bucket", "templateBucket"); + actionConfiguration.setFormData(formData); + AmazonS3Plugin.S3PluginExecutor pluginExecutor = new AmazonS3Plugin.S3PluginExecutor(); + pluginExecutor + .sanitizeGenerateCRUDPageTemplateInfo( + List.of(actionConfiguration), mappedColumnsAndTableName, userSelectedBucketName) + .block(); + assertEquals(userSelectedBucketName, mappedColumnsAndTableName.get("templateBucket")); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java index 451d2447c7..e23fdd1035 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java @@ -387,24 +387,26 @@ public class CreateDBTablePageSolutionCEImpl implements CreateDBTablePageSolutio .peek(newAction -> newAction.setDefaultResources(page.getDefaultResources())) .collect(Collectors.toList()); - // Extract S3 bucket name from template application and map to users bucket. Bucket name is stored - // at - // index 1 in plugin specified templates + List templateUnpublishedActionConfigList = templateActionList.stream() + .map(NewAction::getUnpublishedAction) + .map(ActionDTO::getActionConfiguration) + .collect(Collectors.toList()); - if (Entity.S3_PLUGIN_PACKAGE_NAME.equals(plugin.getPackageName()) - && !CollectionUtils.isEmpty(templateActionList)) { - final Map formData = templateActionList - .get(0) - .getUnpublishedAction() - .getActionConfiguration() - .getFormData(); - mappedColumnsAndTableName.put( - (String) ((Map) formData.get("bucket")).get("data"), tableName); - } + /** + * Any plugin specific update to the template queries should be defined by overriding the + * `sanitizeGenerateCRUDPageTemplateInfo` method in the respective plugin. In the default case no + * changes are made to the template. e.g. please check the sanitizeGenerateCRUDPageTemplateInfo + * method defined in AmazonS3Plugin.java . + */ + Mono sanitizeTemplateInfoMono = pluginExecutorHelper + .getPluginExecutorFromPackageName(plugin.getPackageName()) + .flatMap(pluginExecutor -> pluginExecutor.sanitizeGenerateCRUDPageTemplateInfo( + templateUnpublishedActionConfigList, mappedColumnsAndTableName, tableName)); log.debug("Going to update layout for page {} and layout {}", savedPageId, layoutId); - return layoutActionService - .updateLayout(savedPageId, page.getApplicationId(), layoutId, layout) + return sanitizeTemplateInfoMono + .then(layoutActionService.updateLayout( + savedPageId, page.getApplicationId(), layoutId, layout)) .then(Mono.zip( Mono.just(datasourceStorage), Mono.just(templateActionList), diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java index f793bac8ce..5a033b56f4 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java @@ -13,6 +13,7 @@ import com.appsmith.external.models.DatasourceStructure.Table; import com.appsmith.external.models.DatasourceStructure.TableType; import com.appsmith.external.models.DefaultResources; import com.appsmith.external.models.Property; +import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.GitApplicationMetadata; @@ -60,6 +61,9 @@ import java.util.UUID; import static com.appsmith.server.acl.AclPermission.READ_PAGES; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; @Slf4j @ExtendWith(SpringExtension.class) @@ -158,16 +162,18 @@ public class CreateDBTablePageSolutionTests { @MockBean private PluginExecutorHelper pluginExecutorHelper; + private PluginExecutor spyMockPluginExecutor = spy(new MockPluginExecutor()); + private final CRUDPageResourceDTO resource = new CRUDPageResourceDTO(); @BeforeEach @WithUserDetails(value = "api_user") public void setup() { - Mockito.when(pluginExecutorHelper.getPluginExecutor(Mockito.any())) - .thenReturn(Mono.just(new MockPluginExecutor())); + Mockito.when(pluginExecutorHelper.getPluginExecutor(any())).thenReturn(Mono.just(spyMockPluginExecutor)); Mockito.when(pluginExecutorHelper.getPluginExecutorFromPackageName(Mockito.anyString())) - .thenReturn(Mono.just(new MockPluginExecutor())); + .thenReturn(Mono.just(spyMockPluginExecutor)) + .thenReturn(Mono.just(spyMockPluginExecutor)); if (testWorkspace == null) { Workspace workspace = new Workspace(); @@ -905,6 +911,21 @@ public class CreateDBTablePageSolutionTests { @Test @WithUserDetails(value = "api_user") public void createPageWithNullPageIdForS3() { + /** + * Define handling for sanitizeGenerateCRUDPageTemplateInfo method for S3 plugin on spyMockPluginExecutor. + */ + doAnswer(invocation -> { + List actionConfigurationList = invocation.getArgument(0); + Map mappedColumnsAndTableName = invocation.getArgument(1); + String bucketName = invocation.getArgument(2); + Map formData = + actionConfigurationList.get(0).getFormData(); + mappedColumnsAndTableName.put( + (String) ((Map) formData.get("bucket")).get("data"), bucketName); + return Mono.empty(); + }) + .when(spyMockPluginExecutor) + .sanitizeGenerateCRUDPageTemplateInfo(any(), any(), any()); resource.setApplicationId(testApp.getId()); StringBuilder pluginName = new StringBuilder();