Bug Fix: Fix dynamic binding substitution failure with Firestore where condition input boxes. (#5280)

Modify dynamic binding substitution method to handle list and map types containing generic type values.
This commit is contained in:
Sumit Kumar 2021-06-21 19:36:06 +05:30 committed by GitHub
parent a89990e157
commit 7787a0ddac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 131 additions and 47 deletions

View File

@ -4,11 +4,13 @@ import com.appsmith.external.models.PaginationField;
import com.appsmith.external.models.Param;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
@Getter
@Setter
@ToString
public class ExecuteActionDTO {
String actionId;

View File

@ -4,12 +4,14 @@ import com.appsmith.external.models.ActionConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@ -255,56 +257,57 @@ public class MustacheHelper {
}
}
/**
* - If object is null, then return object.
* - If object is an Appsmith domain object then iterate over all fields of the object and render field values
* for each of them.
* - If object is list type then iterate over each item in the list and render field value for them.
* - If object is map type, then iterate over each value in the map and render field value for them.
* - If the object is string type (base case), then do the binding substitution if applicable.
* - If the object falls under none of the above conditions then return the object without doing anything.
*/
public static <T> T renderFieldValues(T object, Map<String, String> context) {
final BeanWrapper sourceBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(object);
if (object == null) {
return object;
}
try {
for (PropertyDescriptor propertyDescriptor : sourceBeanWrapper.getPropertyDescriptors()) {
// For properties like `class` that don't have a set method, just ignore them.
if (propertyDescriptor.getWriteMethod() == null) {
continue;
}
String name = propertyDescriptor.getName();
Object value = sourceBeanWrapper.getPropertyValue(name);
// If value is null, don't copy it over to target and just move on to the next property.
if (value == null) {
continue;
}
if (isDomainModel(propertyDescriptor.getPropertyType())) {
// Go deeper *only* if the property belongs to Appsmith's models, and both the source and target
// values are not null.
renderFieldValues(value, context);
} else if (value instanceof List) {
for (Object childValue : (List) value) {
if (childValue != null && isDomainModel(childValue.getClass())) {
renderFieldValues(childValue, context);
}
if (isDomainModel(object.getClass())) {
try {
final BeanWrapper sourceBeanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(object);
for (PropertyDescriptor propertyDescriptor : sourceBeanWrapper.getPropertyDescriptors()) {
// For properties like `class` that don't have a set method, just ignore them.
if (propertyDescriptor.getWriteMethod() == null) {
continue;
}
} else if (value instanceof Map) {
for (Object childValue : ((Map) value).values()) {
if (childValue != null && isDomainModel(childValue.getClass())) {
renderFieldValues(childValue, context);
}
}
} else if (value instanceof String) {
sourceBeanWrapper.setPropertyValue(
name,
render((String) value, context)
);
String name = propertyDescriptor.getName();
Object value = sourceBeanWrapper.getPropertyValue(name);
sourceBeanWrapper.setPropertyValue(name, renderFieldValues(value, context));
}
} catch (BeansException e) {
log.error("Exception caught while substituting values in mustache template.", e);
}
} else if (object instanceof List) {
List renderedList = new ArrayList();
for (Object childValue : (List) object) {
renderedList.add(renderFieldValues(childValue, context));
}
} catch (Exception e) {
log.error("Exception caught while substituting values in mustache template.", e);
return (T) renderedList;
} else if (object instanceof Map) {
Map renderedMap = new HashMap();
for (Object entry : ((Map)object).entrySet()) {
renderedMap.put(
((Map.Entry) entry).getKey(), // key
renderFieldValues(((Map.Entry) entry).getValue(), context) // value
);
}
return (T) renderedMap;
} else if (object instanceof String) {
return (T) render((String) object, context);
}
return object;

View File

@ -1,10 +1,14 @@
package com.appsmith.external.models;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class Param {
String key;

View File

@ -9,6 +9,7 @@ import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.DBAuth;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.PaginationField;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.RequestParamDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
@ -710,9 +711,9 @@ public class FirestorePluginTest {
* - this returns 2 documents.
*/
((List)whereProperty.getValue()).add(new HashMap<String, Object>() {{
put("path", "category");
put("path", "{{Input1.text}}");
put("operator", "EQ");
put("value", "test");
put("value", "{{Input2.text}}");
}});
/*
@ -720,16 +721,36 @@ public class FirestorePluginTest {
* - Of the two documents returned by above condition, this will narrow it down to one.
*/
((List)whereProperty.getValue()).add(new HashMap<String, Object>() {{
put("path", "name");
put("path", "{{Input3.text}}");
put("operator", "EQ");
put("value", "two");
put("value", "{{Input4.text}}");
}});
pluginSpecifiedTemplates.add(whereProperty);
actionConfiguration.setPluginSpecifiedTemplates(pluginSpecifiedTemplates);
List params = new ArrayList();
Param param = new Param();
param.setKey("Input1.text");
param.setValue("category");
params.add(param);
param = new Param();
param.setKey("Input2.text");
param.setValue("test");
params.add(param);
param = new Param();
param.setKey("Input3.text");
param.setValue("name");
params.add(param);
param = new Param();
param.setKey("Input4.text");
param.setValue("two");
params.add(param);
ExecuteActionDTO executeActionDTO = new ExecuteActionDTO();
executeActionDTO.setParams(params);
Mono<ActionExecutionResult> resultMono = pluginExecutor
.executeParameterized(firestoreConnection, null, dsConfig, actionConfiguration);
.executeParameterized(firestoreConnection, executeActionDTO, dsConfig, actionConfiguration);
StepVerifier.create(resultMono)
.assertNext(result -> {
@ -755,6 +776,7 @@ public class FirestorePluginTest {
.verifyComplete();
}
@Test
public void testUpdateDocumentWithFieldValueTimestamp() {
List<Property> properties = new ArrayList<>();
properties.add(new Property("method", "UPDATE_DOCUMENT")); // index 0
@ -1011,4 +1033,57 @@ public class FirestorePluginTest {
})
.verifyComplete();
}
@Test
public void testDynamicBindingSubstitutionInActionConfiguration() {
ActionConfiguration actionConfiguration = new ActionConfiguration();
actionConfiguration.setPath("{{Input1.text}}");
List<Property> pluginSpecifiedTemplates = new ArrayList<>();
pluginSpecifiedTemplates.add(new Property("method", "GET_COLLECTION"));
pluginSpecifiedTemplates.add(new Property("order", null));
pluginSpecifiedTemplates.add(new Property("limit", null));
Property whereProperty = new Property("where", null);
whereProperty.setValue(new ArrayList<>());
/*
* - get all documents where category == test.
* - this returns 2 documents.
*/
((List)whereProperty.getValue()).add(new HashMap<String, Object>() {{
put("path", "{{Input2.text}}");
put("operator", "EQ");
put("value", "{{Input3.text}}");
}});
pluginSpecifiedTemplates.add(whereProperty);
actionConfiguration.setPluginSpecifiedTemplates(pluginSpecifiedTemplates);
List params = new ArrayList();
Param param = new Param();
param.setKey("Input1.text");
param.setValue("initial");
params.add(param);
param = new Param();
param.setKey("Input2.text");
param.setValue("category");
params.add(param);
param = new Param();
param.setKey("Input3.text");
param.setValue("test");
params.add(param);
ExecuteActionDTO executeActionDTO = new ExecuteActionDTO();
executeActionDTO.setParams(params);
// Substitute dynamic binding values
pluginExecutor
.prepareConfigurationsForExecution(executeActionDTO, actionConfiguration, null);
// check if dynamic binding values have been substituted correctly
assertEquals("initial", actionConfiguration.getPath());
assertEquals("category",
((Map)((List)actionConfiguration.getPluginSpecifiedTemplates().get(3).getValue()).get(0)).get(
"path"));
assertEquals("test",
((Map)((List)actionConfiguration.getPluginSpecifiedTemplates().get(3).getValue()).get(0)).get(
"value"));
}
}