Db Migration : Delete unreferenced dynamic binding paths in actions (#3310)

* WIP compute if the dynamic binding path list is correct.

* Tested code for deleting the incorrect dynamic binding path list from actions.

* Added comments for code readability
This commit is contained in:
Trisha Anand 2021-03-03 17:11:03 +05:30 committed by GitHub
parent 9c50182f0e
commit ca8be4c7f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,5 +1,6 @@
package com.appsmith.server.migrations;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.external.models.DBAuth;
import com.appsmith.external.models.DatasourceConfiguration;
@ -40,6 +41,7 @@ import com.appsmith.server.dtos.OrganizationPluginStatus;
import com.appsmith.server.dtos.PageDTO;
import com.appsmith.server.services.EncryptionService;
import com.appsmith.server.services.OrganizationService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.cloudyrock.mongock.ChangeLog;
import com.github.cloudyrock.mongock.ChangeSet;
import com.google.gson.Gson;
@ -71,15 +73,18 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.appsmith.external.helpers.BeanCopyUtils.copyNewFieldValuesIntoOldObject;
@ -87,7 +92,10 @@ import static com.appsmith.server.acl.AclPermission.EXECUTE_ACTIONS;
import static com.appsmith.server.acl.AclPermission.MAKE_PUBLIC_APPLICATIONS;
import static com.appsmith.server.acl.AclPermission.ORGANIZATION_INVITE_USERS;
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
import static com.appsmith.server.helpers.CollectionUtils.isNullOrEmpty;
import static com.appsmith.server.repositories.BaseAppsmithRepositoryImpl.fieldName;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
import static org.springframework.data.mongodb.core.query.Update.update;
@ -787,7 +795,7 @@ public class DatabaseChangelog {
final Map<String, Object> datasource = (Map) action.get("datasource");
datasource.put("pluginId", plugins.get(datasource.remove("$pluginPackageName")));
datasource.put(FieldName.ORGANIZATION_ID, organizationId);
if (Boolean.FALSE.equals(datasource.remove("$isEmbedded"))) {
if (FALSE.equals(datasource.remove("$isEmbedded"))) {
datasource.put("_id", new ObjectId(datasourceIdsByName.get(datasource.get("name"))));
}
action.put(FieldName.ORGANIZATION_ID, organizationId);
@ -1700,19 +1708,17 @@ public class DatabaseChangelog {
for (NewAction action : mongoTemplate.findAll(NewAction.class)) {
if (action.getPluginType() != null && action.getPluginType().equals("API")) {
if (action.getUnpublishedAction() != null
&& action.getUnpublishedAction().getActionConfiguration() != null) {
action.getUnpublishedAction().getActionConfiguration().setEncodeParamsToggle(true);
}
if (action.getPublishedAction() != null
&& action.getPublishedAction().getActionConfiguration() != null) {
action.getPublishedAction().getActionConfiguration().setEncodeParamsToggle(true);
}
mongoTemplate.save(action);
}
if (action.getUnpublishedAction() != null
&& action.getUnpublishedAction().getActionConfiguration() != null) {
action.getUnpublishedAction().getActionConfiguration().setEncodeParamsToggle(true);
}
if (action.getPublishedAction() != null
&& action.getPublishedAction().getActionConfiguration() != null) {
action.getPublishedAction().getActionConfiguration().setEncodeParamsToggle(true);
}
mongoTemplate.save(action);
}
}
@ -1755,4 +1761,109 @@ public class DatabaseChangelog {
mongoTemplate.save(action);
}
}
@ChangeSet(order = "056", id = "fix-dynamicBindingPathListForActions", author = "")
public void fixDynamicBindingPathListForExistingActions(MongoTemplate mongoTemplate) {
ObjectMapper mapper = new ObjectMapper();
for (NewAction action : mongoTemplate.findAll(NewAction.class)) {
// We have found an action with dynamic binding path list set by the client.
List<Property> dynamicBindingPaths = action.getUnpublishedAction().getDynamicBindingPathList();
// Only investigate actions which have atleast one dynamic binding path list
if (action.getUnpublishedAction().getActionConfiguration() != null && !isNullOrEmpty(dynamicBindingPaths)) {
List<String> dynamicBindingPathNames = dynamicBindingPaths
.stream()
.map(property -> property.getKey())
.collect(Collectors.toList());
// Initialize the final updated binding path list with the existing path names.
List<String> finalDynamicBindingPathList = new ArrayList<>();
finalDynamicBindingPathList.addAll(dynamicBindingPathNames);
Set<String> pathsToRemove = new HashSet<>();
for (String path : dynamicBindingPathNames) {
if (path != null) {
String[] fields = path.split("[].\\[]");
// Convert actionConfiguration into JSON Object and then walk till we reach the path specified.
Map<String, Object> actionConfigurationMap = mapper.convertValue(action.getUnpublishedAction().getActionConfiguration(), Map.class);
Object parent = new JSONObject(actionConfigurationMap);
Iterator<String> fieldsIterator = Arrays.stream(fields).filter(fieldToken -> !fieldToken.isBlank()).iterator();
Boolean isLeafNode = false;
while (fieldsIterator.hasNext()) {
String nextKey = fieldsIterator.next();
if (parent instanceof JSONObject) {
parent = ((JSONObject) parent).get(nextKey);
} else if (parent instanceof Map) {
parent = ((Map<String, ?>) parent).get(nextKey);
} else if (parent instanceof List) {
if (Pattern.matches(Pattern.compile("[0-9]+").toString(), nextKey)) {
try {
parent = ((List) parent).get(Integer.parseInt(nextKey));
} catch (IndexOutOfBoundsException e) {
// The index being referred does not exist. Hence the path would not exist.
pathsToRemove.add(path);
}
} else {
// Parent is a list but does not match the pattern. Hence the path would not exist.
pathsToRemove.add(path);
break;
}
}
// After updating the parent, check for the types
if (parent == null) {
pathsToRemove.add(path);
break;
} else if (parent instanceof String) {
// If we get String value, then this is a leaf node
isLeafNode = true;
}
}
// Only extract mustache keys from leaf nodes
if (parent != null && isLeafNode) {
Set<String> mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent);
// We found the path. But if the path does not have any mustache bindings, remove it from the path list
if (mustacheKeysFromFields.isEmpty()) {
pathsToRemove.add(path);
}
}
}
}
Boolean actionEdited = pathsToRemove.size() > 0 ? TRUE : FALSE;
// Only update the action if required
if (actionEdited) {
// We have walked all the dynamic binding paths which either dont exist or they exist but don't contain any mustache bindings
for (String path : dynamicBindingPathNames) {
if (pathsToRemove.contains(path)) {
finalDynamicBindingPathList.remove(path);
}
}
List<Property> updatedDynamicBindingPathList = finalDynamicBindingPathList
.stream()
.map(path -> {
Property property = new Property();
property.setKey(path);
return property;
})
.collect(Collectors.toList());
action.getUnpublishedAction().setDynamicBindingPathList(updatedDynamicBindingPathList);
mongoTemplate.save(action);
}
}
}
}
}