fix: Refactor entities based on AST parsing logic (#18517)

* feat: Refactor entities based on AST parsing logic

* Deleted jmh file

* rts updates for the edge cases

* adding jest test cases

* update review comments

* Fixed issue with references outside of bindings and some other stuff

* Added tests for DSLUtils

* bug fix 18699

* Test fixes

* Test fixes

* Review comments

* Changed type to boxed

Co-authored-by: ChandanBalajiBP <chandan@appsmith.com>
Co-authored-by: ChandanBalajiBP <104058110+ChandanBalajiBP@users.noreply.github.com>
This commit is contained in:
Nidhi 2022-12-12 08:12:21 +03:00 committed by GitHub
parent 284571803b
commit 5329010415
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 704 additions and 355 deletions

View File

@ -36,7 +36,6 @@
"dependencies": {
"express-validator": "^6.14.2",
"http-status-codes": "^2.2.0",
"morgan": "^1.10.0",
"supertest": "^6.2.4",
"tsc-alias": "^1.7.0"
}

View File

@ -1,7 +1,6 @@
import http from "http";
import path from "path";
import express from "express";
import morgan from "morgan";
import { Server } from "socket.io";
import log, { LogLevelDesc } from "loglevel";
import { VERSION as buildVersion } from "./version"; // release version of the api
@ -49,8 +48,6 @@ const io = new Server(server, {
// Initializing Sockets
initializeSockets(io);
//Track perf metrics for each call
app.use(morgan('tiny'));
// parse incoming json requests
app.use(express.json({ limit: "5mb" }));
// Initializing Routes

View File

@ -50,6 +50,44 @@ const entityRefactor = [
isJSObject: true,
evalVersion: 2,
},
{
script:
"// ApiNever \n function ApiNever(abc) {let foo = \"I'm getting data from ApiNever but don't rename this string\" + ApiNever.data; \n if(true) { return ApiNever }}",
oldName: "ApiNever.data",
newName: "ApiNever.input",
isJSObject: false,
evalVersion: 2,
},
{
script:
"// ApiNever \n function ApiNever(abc) {let foo = \"I'm getting data from ApiNever but don't rename this string\" + ApiNever.data; \n if(true) { return ApiNever }}",
oldName: "ApiNever.dat",
newName: "ApiNever.input",
isJSObject: false,
evalVersion: 2,
},
{
script: "\tApiNever.data",
oldName: "ApiNever",
newName: "ApiForever",
isJSObject: false,
evalVersion: 2,
},
{
script: "ApiNever.data + ApiNever.data",
oldName: "ApiNever",
newName: "ApiForever",
isJSObject: false,
evalVersion: 2,
},
{
script:
'export default {\n\tmyVar1: [],\n\tmyVar2: {},\n\tmyFun1: () => {\n\t\t//write code here\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiNever.text\n\t},\n\tmyFun2: async () => {\n\t\t//use async-await or promises\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiNever.text\n\t}\n}',
oldName: "ApiNever",
newName: "ApiForever",
isJSObject: true,
evalVersion: 2,
},
];
afterAll((done) => {
@ -124,6 +162,29 @@ describe("AST tests", () => {
"export default {\n\tmyVar1: [],\n\tmyVar2: {},\n\t\tsearch: () => {\n\t\tif(Input1.text.length==0){\n\t\t\treturn select_repair_db.data\n\t\t}\n\t\telse{\n\t\t\treturn(select_repair_db.data.filter(word => word.cust_name.toLowerCase().includes(Input1.text.toLowerCase())))\n\t\t}\n\t},\n}",
refactorCount: 2,
},
{
script:
"// ApiNever \n function ApiNever(abc) {let foo = \"I'm getting data from ApiNever but don't rename this string\" + ApiNever.input; \n if(true) { return ApiNever }}",
refactorCount: 1,
},
{
script:
"// ApiNever \n function ApiNever(abc) {let foo = \"I'm getting data from ApiNever but don't rename this string\" + ApiNever.data; \n if(true) { return ApiNever }}",
refactorCount: 0,
},
{
script: "\tApiForever.data",
refactorCount: 1,
},
{
script: "ApiForever.data + ApiForever.data",
refactorCount: 2,
},
{
script:
'export default {\n\tmyVar1: [],\n\tmyVar2: {},\n\tmyFun1: () => {\n\t\t//write code here\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiForever.text\n\t},\n\tmyFun2: async () => {\n\t\t//use async-await or promises\n\t\t// ApiNever.text\n\t\treturn "ApiNever.text" + ApiForever.text\n\t}\n}',
refactorCount: 2,
},
];
await supertest(app)

View File

@ -951,13 +951,6 @@ base64id@2.0.0, base64id@~2.0.0:
resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6"
integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==
basic-auth@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
dependencies:
safe-buffer "5.1.2"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@ -1283,7 +1276,7 @@ denque@^1.4.1:
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de"
integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==
depd@2.0.0, depd@~2.0.0:
depd@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@ -2475,17 +2468,6 @@ mongodb@^3.6.4:
optionalDependencies:
saslprep "^1.0.0"
morgan@^1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
dependencies:
basic-auth "~2.0.1"
debug "2.6.9"
depd "~2.0.0"
on-finished "~2.3.0"
on-headers "~1.0.2"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -2560,18 +2542,6 @@ on-finished@2.4.1:
dependencies:
ee-first "1.1.1"
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
dependencies:
ee-first "1.1.1"
on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
once@1.4.0, once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
@ -2857,16 +2827,16 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@5.2.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
"safer-buffer@>= 2.1.2 < 3":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"

View File

@ -60,6 +60,13 @@ public enum AnalyticsEvents {
// Events to log execution time
GIT_SERIALIZE_APP_RESOURCES_TO_LOCAL_FILE,
GIT_DESERIALIZE_APP_RESOURCES_FROM_FILE,
// Entity refactor related events
REFACTOR_JSOBJECT,
REFACTOR_ACTION,
REFACTOR_JSACTION,
REFACTOR_WIDGET,
INVITE_USERS_TO_USER_GROUPS,
REMOVE_USERS_FROM_USER_GROUPS,
ASSIGNED_TO_PERMISSION_GROUP,

View File

@ -3,6 +3,7 @@ package com.appsmith.external.helpers;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.EntityDependencyNode;
import com.appsmith.external.models.EntityReferenceType;
import com.appsmith.external.models.MustacheBindingToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.beans.BeanWrapper;
@ -23,7 +24,6 @@ import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@ -79,12 +79,12 @@ public class MustacheHelper {
* should give the original template back. The tokens are split such that alternative strings in the list are plain
* text and the others are mustache interpolations.
*/
public static List<String> tokenize(String template) {
public static List<MustacheBindingToken> tokenize(String template) {
if (!StringUtils.hasLength(template)) {
return Collections.emptyList();
}
List<String> tokens = new ArrayList<>();
List<MustacheBindingToken> tokens = new ArrayList<>();
int length = template.length();
@ -100,6 +100,7 @@ public class MustacheHelper {
int braceDepth = 0;
StringBuilder currentToken = new StringBuilder().append(template.charAt(0));
int currentTokenStartIndex = 0;
// The parser is implemented as a pointer (marked by `i`) that loops over each character in the template string.
// There's majorly two states for the parser, plain-text-mode and mustache-mode, with the current state
@ -118,8 +119,9 @@ public class MustacheHelper {
isInsideMustache = true;
// Remove the `{` added to the builder.
currentToken.deleteCharAt(currentToken.length() - 1);
clearAndPushToken(currentToken, tokens);
clearAndPushToken(currentToken, currentTokenStartIndex, tokens, false);
currentToken.append(prevChar);
currentTokenStartIndex = i - 1;
braceDepth = 2;
}
@ -157,13 +159,12 @@ public class MustacheHelper {
--braceDepth;
currentToken.append(currentChar);
if (prevChar == '}' && braceDepth <= 0) {
clearAndPushToken(currentToken, tokens);
clearAndPushToken(currentToken, currentTokenStartIndex, tokens, true);
isInsideMustache = false;
}
} else {
currentToken.append(currentChar);
}
}
@ -171,7 +172,7 @@ public class MustacheHelper {
}
if (currentToken.length() > 0) {
tokens.add(currentToken.toString());
tokens.add(new MustacheBindingToken(currentToken.toString(), currentTokenStartIndex, false));
}
return tokens;
@ -185,15 +186,15 @@ public class MustacheHelper {
* @return A Set of strings that serve as replacement keys, with the surrounding double braces stripped and then
* trimmed.
*/
public static Set<String> extractMustacheKeys(String template) {
Set<String> keys = new HashSet<>();
public static Set<MustacheBindingToken> extractMustacheKeys(String template) {
Set<MustacheBindingToken> keys = new HashSet<>();
for (String token : tokenize(template)) {
if (token.startsWith("{{") && token.endsWith("}}")) {
for (MustacheBindingToken token : tokenize(template)) {
if (token.getValue().startsWith("{{") && token.getValue().endsWith("}}")) {
// Allowing empty tokens to be added, to be compatible with the previous `extractMustacheKeys` method.
// Calling `.trim()` before adding because Mustache compiler strips keys in the template before looking
// up a value. Addresses https://www.notion.so/appsmith/Bindings-with-a-space-at-the-start-fail-to-execute-properly-in-the-API-pane-2eb65d5c6064466b9ef059fa01ef3261
keys.add(token.substring(2, token.length() - 2).trim());
keys.add(new MustacheBindingToken(token.getValue().substring(2, token.getValue().length() - 2), (token.getStartIndex() + 2), false));
}
}
@ -201,23 +202,23 @@ public class MustacheHelper {
}
// For prepared statements we should extract the bindings in order in a list and include duplicate bindings as well.
public static List<String> extractMustacheKeysInOrder(String template) {
List<String> keys = new ArrayList<>();
public static List<MustacheBindingToken> extractMustacheKeysInOrder(String template) {
List<MustacheBindingToken> keys = new ArrayList<>();
for (String token : tokenize(template)) {
if (token.startsWith("{{") && token.endsWith("}}")) {
for (MustacheBindingToken token : tokenize(template)) {
if (token.getValue().startsWith("{{") && token.getValue().endsWith("}}")) {
// Allowing empty tokens to be added, to be compatible with the previous `extractMustacheKeys` method.
// Calling `.trim()` before adding because Mustache compiler strips keys in the template before looking
// up a value. Addresses https://www.notion.so/appsmith/Bindings-with-a-space-at-the-start-fail-to-execute-properly-in-the-API-pane-2eb65d5c6064466b9ef059fa01ef3261
keys.add(token.substring(2, token.length() - 2).trim());
keys.add(new MustacheBindingToken(token.getValue().substring(2, token.getValue().length() - 2).trim(), (token.getStartIndex() + 2), false));
}
}
return keys;
}
public static Set<String> extractMustacheKeysFromFields(Object object) {
final Set<String> keys = new HashSet<>();
public static Set<MustacheBindingToken> extractMustacheKeysFromFields(Object object) {
final Set<MustacheBindingToken> keys = new HashSet<>();
// Linearized recursive search. Instead of calling this function recursively for nested values, we add them to
// the end of the queue and process them in a linear fashion. This strategy doesn't suffer from a stack overflow
@ -252,9 +253,9 @@ public class MustacheHelper {
return keys;
}
private static void clearAndPushToken(StringBuilder tokenBuilder, List<String> tokenList) {
private static void clearAndPushToken(StringBuilder tokenBuilder, int tokenStartIndex, List<MustacheBindingToken> tokenList, boolean includesHandleBars) {
if (tokenBuilder.length() > 0) {
tokenList.add(tokenBuilder.toString());
tokenList.add(new MustacheBindingToken(tokenBuilder.toString(), tokenStartIndex, includesHandleBars));
tokenBuilder.setLength(0);
}
}
@ -322,11 +323,11 @@ public class MustacheHelper {
public static String render(String template, Map<String, String> keyValueMap) {
final StringBuilder rendered = new StringBuilder();
for (String token : tokenize(template)) {
if (token.startsWith("{{") && token.endsWith("}}")) {
rendered.append(keyValueMap.get(token.substring(2, token.length() - 2).trim()));
for (MustacheBindingToken token : tokenize(template)) {
if (token.getValue().startsWith("{{") && token.getValue().endsWith("}}")) {
rendered.append(keyValueMap.get(token.getValue().substring(2, token.getValue().length() - 2).trim()));
} else {
rendered.append(token);
rendered.append(token.getValue());
}
}
@ -500,26 +501,30 @@ public class MustacheHelper {
return bindingNames;
}
public static String replaceMustacheWithPlaceholder(String query, List<String> mustacheBindings) {
public static String replaceMustacheWithPlaceholder(String query, List<MustacheBindingToken> mustacheBindings) {
return replaceMustacheUsingPatterns(query, APPSMITH_SUBSTITUTION_PLACEHOLDER, mustacheBindings, placeholderTrimmingPattern, APPSMITH_SUBSTITUTION_PLACEHOLDER);
}
public static String replaceMustacheWithQuestionMark(String query, List<String> mustacheBindings) {
public static String replaceMustacheWithQuestionMark(String query, List<MustacheBindingToken> mustacheBindings) {
return replaceMustacheUsingPatterns(query, "?", mustacheBindings, quoteQuestionPattern, postQuoteTrimmingQuestionMark);
}
private static String replaceMustacheUsingPatterns(String query,
String placeholder,
List<String> mustacheBindings,
List<MustacheBindingToken> mustacheBindings,
Pattern sanitizePattern,
String replacement) {
ActionConfiguration actionConfiguration = new ActionConfiguration();
actionConfiguration.setBody(query);
Set<String> mustacheSet = new HashSet<>(mustacheBindings);
Set<MustacheBindingToken> mustacheSet = new HashSet<>(mustacheBindings);
Map<String, String> replaceParamsMap = mustacheSet.stream().collect(Collectors.toMap(Function.identity(), v -> placeholder));
Map<String, String> replaceParamsMap = mustacheSet
.stream()
.map(mustacheToken -> mustacheToken.getValue())
.distinct()
.collect(Collectors.toMap(k -> k, v -> placeholder));
// Replace the mustaches with the values mapped to each mustache in replaceParamsMap
ActionConfiguration updatedActionConfiguration = renderFieldValues(actionConfiguration, replaceParamsMap);

View File

@ -0,0 +1,22 @@
package com.appsmith.external.models;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString
public class MustacheBindingToken {
String value;
int startIndex;
// A token can be with or without handlebars in the value. This boolean value represents the state of the current token.
boolean includesHandleBars = false;
}

View File

@ -3,6 +3,7 @@ package com.appsmith.external.plugins;
import com.appsmith.external.constants.DataType;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.Param;
import java.util.Arrays;
@ -15,6 +16,7 @@ public interface SmartSubstitutionInterface {
/**
* !Warning! - This function changes the values of arraylist insertedParams which can then be returned
* back to the caller with all the values that were finally put during substitution
*
* @param input
* @param mustacheValuesInOrder
* @param evaluatedParams
@ -24,7 +26,7 @@ public interface SmartSubstitutionInterface {
* @throws AppsmithPluginException
*/
default Object smartSubstitutionOfBindings(Object input,
List<String> mustacheValuesInOrder,
List<MustacheBindingToken> mustacheValuesInOrder,
List<Param> evaluatedParams,
List<Map.Entry<String, String>> insertedParams,
Object... args) throws AppsmithPluginException {
@ -32,7 +34,7 @@ public interface SmartSubstitutionInterface {
if (mustacheValuesInOrder != null && !mustacheValuesInOrder.isEmpty()) {
for (int i = 0; i < mustacheValuesInOrder.size(); i++) {
String key = mustacheValuesInOrder.get(i);
String key = mustacheValuesInOrder.get(i).getValue();
Optional<Param> matchingParam = evaluatedParams.stream().filter(param -> param.getKey().trim().equals(key)).findFirst();
// If the evaluated value of the mustache binding is present, set it in the prepared statement
@ -75,7 +77,7 @@ public interface SmartSubstitutionInterface {
static <T> T[] append(T[] arr, T lastElement) {
final int N = arr.length;
arr = Arrays.copyOf(arr, N+1);
arr = Arrays.copyOf(arr, N + 1);
arr[N] = lastElement;
return arr;
}

View File

@ -4,6 +4,7 @@ import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.Connection;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Endpoint;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.Property;
import org.assertj.core.api.AbstractCollectionAssert;
import org.assertj.core.api.ObjectAssert;
@ -15,6 +16,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static com.appsmith.external.helpers.MustacheHelper.extractMustacheKeys;
import static com.appsmith.external.helpers.MustacheHelper.extractMustacheKeysFromFields;
@ -29,15 +31,15 @@ import static org.assertj.core.api.Assertions.assertThat;
)
public class MustacheHelperTest {
private void checkTokens(String template, List<String> expected) {
private void checkTokens(String template, List<MustacheBindingToken> expected) {
assertThat(tokenize(template)).isEqualTo(expected);
}
private void checkKeys(String template, Set<String> expected) {
private void checkKeys(String template, Set<MustacheBindingToken> expected) {
assertThat(extractMustacheKeys(template)).isEqualTo(expected);
}
private void check(String template, List<String> expectedTokens, Set<String> expectedKeys) {
private void check(String template, List<MustacheBindingToken> expectedTokens, Set<MustacheBindingToken> expectedKeys) {
if (expectedTokens != null) {
checkTokens(template, expectedTokens);
}
@ -46,7 +48,7 @@ public class MustacheHelperTest {
}
}
private AbstractCollectionAssert<?, Collection<? extends String>, String, ObjectAssert<String>>
private AbstractCollectionAssert<?, Collection<? extends MustacheBindingToken>, MustacheBindingToken, ObjectAssert<MustacheBindingToken>>
assertKeys(Object object) {
return assertThat(extractMustacheKeysFromFields(object));
}
@ -58,20 +60,20 @@ public class MustacheHelperTest {
@Test
public void justSingleMustache() {
checkTokens("{{A}}", Arrays.asList("{{A}}"));
checkKeys("{{A}}", Set.of("A"));
checkKeys("{{A + B / C}}", Set.of("A + B / C"));
checkTokens("{{A}}", Arrays.asList(new MustacheBindingToken("{{A}}", 0, true)));
checkKeys("{{A}}", Set.of(new MustacheBindingToken("A", 2, false)));
checkKeys("{{A + B / C}}", Set.of(new MustacheBindingToken("A + B / C", 2, false)));
}
@Test
public void textAndMustache() {
checkKeys("Hello {{name}}", Set.of("name"));
checkKeys("Hello {{url.hash}}", Set.of("url.hash"));
checkKeys("Hello {{name}}", Set.of(new MustacheBindingToken("name", 8, false)));
checkKeys("Hello {{url.hash}}", Set.of(new MustacheBindingToken("url.hash", 8, false)));
}
@Test
public void mustacheAndText() {
checkKeys("{{name}} is approved!", Set.of("name"));
checkKeys("{{name}} is approved!", Set.of(new MustacheBindingToken("name", 2, false)));
}
@Test
@ -79,20 +81,20 @@ public class MustacheHelperTest {
checkTokens(
"Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
Arrays.asList(
"Hello ",
"{{Customer.Name}}",
", the status for your order id ",
"{{orderId}}",
" is ",
"{{status}}"
new MustacheBindingToken("Hello ", 0, false),
new MustacheBindingToken("{{Customer.Name}}", 6, true),
new MustacheBindingToken(", the status for your order id ", 6, false),
new MustacheBindingToken("{{orderId}}", 54, true),
new MustacheBindingToken(" is ", 54, false),
new MustacheBindingToken("{{status}}", 69, true)
)
);
checkKeys(
"Hello {{Customer.Name}}, the status for your order id {{orderId}} is {{status}}",
Set.of(
"Customer.Name",
"orderId",
"status"
new MustacheBindingToken("Customer.Name", 8, false),
new MustacheBindingToken("orderId", 56, false),
new MustacheBindingToken("status", 71, false)
)
);
}
@ -101,11 +103,11 @@ public class MustacheHelperTest {
public void realWorldText2() {
checkTokens(
"{{data.map(datum => {return {id: datum}})}}",
Arrays.asList("{{data.map(datum => {return {id: datum}})}}")
Arrays.asList(new MustacheBindingToken("{{data.map(datum => {return {id: datum}})}}", 0, true))
);
checkKeys(
"{{data.map(datum => {return {id: datum}})}}",
Set.of("data.map(datum => {return {id: datum}})")
Set.of(new MustacheBindingToken("data.map(datum => {return {id: datum}})", 2, false))
);
}
@ -113,35 +115,39 @@ public class MustacheHelperTest {
public void braceDances1() {
check(
"{{}}{{}}}",
Arrays.asList("{{}}", "{{}}", "}"),
Set.of("")
Arrays.asList(
new MustacheBindingToken("{{}}", 0, true),
new MustacheBindingToken("{{}}", 4, true),
new MustacheBindingToken("}", 4, false)),
Set.of(new MustacheBindingToken("", 2, false),
new MustacheBindingToken("", 6, false))
);
check("{{{}}", Arrays.asList("{{{}}"), Set.of("{"));
check("{{{}}", Arrays.asList(new MustacheBindingToken("{{{}}", 0, false)), Set.of(new MustacheBindingToken("{", 2, false)));
check("{{ {{", Arrays.asList("{{ {{"), Set.of());
check("{{ {{", Arrays.asList(new MustacheBindingToken("{{ {{", 0, false)), Set.of());
check("}} }}", Arrays.asList("}} }}"), Set.of());
check("}} }}", Arrays.asList(new MustacheBindingToken("}} }}", 0, false)), Set.of());
check("}} {{", Arrays.asList("}} ", "{{"), Set.of());
check("}} {{", Arrays.asList(new MustacheBindingToken("}} ", 0, false), new MustacheBindingToken("{{", 3, false)), Set.of());
}
@Test
public void quotedStrings() {
check(
"{{ 'abc def'.toUpperCase() }}",
Arrays.asList("{{ 'abc def'.toUpperCase() }}"),
Set.of("'abc def'.toUpperCase()")
Arrays.asList(new MustacheBindingToken("{{ 'abc def'.toUpperCase() }}", 0, true)),
Set.of(new MustacheBindingToken(" 'abc def'.toUpperCase() ", 2, false))
);
check(
"{{ \"abc def\".toUpperCase() }}",
Arrays.asList("{{ \"abc def\".toUpperCase() }}"),
Set.of("\"abc def\".toUpperCase()")
Arrays.asList(new MustacheBindingToken("{{ \"abc def\".toUpperCase() }}", 0, true)),
Set.of(new MustacheBindingToken(" \"abc def\".toUpperCase() ", 2, false))
);
check(
"{{ `abc def`.toUpperCase() }}",
Arrays.asList("{{ `abc def`.toUpperCase() }}"),
Set.of("`abc def`.toUpperCase()")
Arrays.asList(new MustacheBindingToken("{{ `abc def`.toUpperCase() }}", 0, true)),
Set.of(new MustacheBindingToken(" `abc def`.toUpperCase() ", 2, false))
);
}
@ -149,38 +155,38 @@ public class MustacheHelperTest {
public void singleQuotedStringsWithBraces() {
check(
"{{ 'The { char is a brace' }}",
Arrays.asList("{{ 'The { char is a brace' }}"),
Set.of("'The { char is a brace'")
Arrays.asList(new MustacheBindingToken("{{ 'The { char is a brace' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'The { char is a brace' ", 2, false))
);
check(
"{{ 'I have {{ two braces' }}",
Arrays.asList("{{ 'I have {{ two braces' }}"),
Set.of("'I have {{ two braces'")
Arrays.asList(new MustacheBindingToken("{{ 'I have {{ two braces' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'I have {{ two braces' ", 2, false))
);
check(
"{{ 'I have {{{ three braces' }}",
Arrays.asList("{{ 'I have {{{ three braces' }}"),
Set.of("'I have {{{ three braces'")
Arrays.asList(new MustacheBindingToken("{{ 'I have {{{ three braces' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'I have {{{ three braces' ", 2, false))
);
check(
"{{ 'The } char is a brace' }}",
Arrays.asList("{{ 'The } char is a brace' }}"),
Set.of("'The } char is a brace'")
Arrays.asList(new MustacheBindingToken("{{ 'The } char is a brace' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'The } char is a brace' ", 2, false))
);
check(
"{{ 'I have }} two braces' }}",
Arrays.asList("{{ 'I have }} two braces' }}"),
Set.of("'I have }} two braces'")
Arrays.asList(new MustacheBindingToken("{{ 'I have }} two braces' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'I have }} two braces' ", 2, false))
);
check(
"{{ 'I have }}} three braces' }}",
Arrays.asList("{{ 'I have }}} three braces' }}"),
Set.of("'I have }}} three braces'")
Arrays.asList(new MustacheBindingToken("{{ 'I have }}} three braces' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'I have }}} three braces' ", 2, false))
);
check(
"{{ 'Interpolation uses {{ and }} delimiters' }}",
Arrays.asList("{{ 'Interpolation uses {{ and }} delimiters' }}"),
Set.of("'Interpolation uses {{ and }} delimiters'")
Arrays.asList(new MustacheBindingToken("{{ 'Interpolation uses {{ and }} delimiters' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'Interpolation uses {{ and }} delimiters' ", 2, false))
);
}
@ -188,38 +194,38 @@ public class MustacheHelperTest {
public void doubleQuotedStringsWithBraces() {
check(
"{{ \"The { char is a brace\" }}",
Arrays.asList("{{ \"The { char is a brace\" }}"),
Set.of("\"The { char is a brace\"")
Arrays.asList(new MustacheBindingToken("{{ \"The { char is a brace\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"The { char is a brace\" ", 2, false))
);
check(
"{{ \"I have {{ two braces\" }}",
Arrays.asList("{{ \"I have {{ two braces\" }}"),
Set.of("\"I have {{ two braces\"")
Arrays.asList(new MustacheBindingToken("{{ \"I have {{ two braces\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"I have {{ two braces\" ", 2, false))
);
check(
"{{ \"I have {{{ three braces\" }}",
Arrays.asList("{{ \"I have {{{ three braces\" }}"),
Set.of("\"I have {{{ three braces\"")
Arrays.asList(new MustacheBindingToken("{{ \"I have {{{ three braces\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"I have {{{ three braces\" ", 2, false))
);
check(
"{{ \"The } char is a brace\" }}",
Arrays.asList("{{ \"The } char is a brace\" }}"),
Set.of("\"The } char is a brace\"")
Arrays.asList(new MustacheBindingToken("{{ \"The } char is a brace\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"The } char is a brace\" ", 2, false))
);
check(
"{{ \"I have }} two braces\" }}",
Arrays.asList("{{ \"I have }} two braces\" }}"),
Set.of("\"I have }} two braces\"")
Arrays.asList(new MustacheBindingToken("{{ \"I have }} two braces\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"I have }} two braces\" ", 2, false))
);
check(
"{{ \"I have }}} three braces\" }}",
Arrays.asList("{{ \"I have }}} three braces\" }}"),
Set.of("\"I have }}} three braces\"")
Arrays.asList(new MustacheBindingToken("{{ \"I have }}} three braces\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"I have }}} three braces\" ", 2, false))
);
check(
"{{ \"Interpolation uses {{ and }} delimiters\" }}",
Arrays.asList("{{ \"Interpolation uses {{ and }} delimiters\" }}"),
Set.of("\"Interpolation uses {{ and }} delimiters\"")
Arrays.asList(new MustacheBindingToken("{{ \"Interpolation uses {{ and }} delimiters\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"Interpolation uses {{ and }} delimiters\" ", 2, false))
);
}
@ -227,38 +233,38 @@ public class MustacheHelperTest {
public void backQuotedStringsWithBraces() {
check(
"{{ `The { char is a brace` }}",
Arrays.asList("{{ `The { char is a brace` }}"),
Set.of("`The { char is a brace`")
Arrays.asList(new MustacheBindingToken("{{ `The { char is a brace` }}", 0, true)),
Set.of(new MustacheBindingToken(" `The { char is a brace` ", 2, false))
);
check(
"{{ `I have {{ two braces` }}",
Arrays.asList("{{ `I have {{ two braces` }}"),
Set.of("`I have {{ two braces`")
Arrays.asList(new MustacheBindingToken("{{ `I have {{ two braces` }}", 0, true)),
Set.of(new MustacheBindingToken(" `I have {{ two braces` ", 2, false))
);
check(
"{{ `I have {{{ three braces` }}",
Arrays.asList("{{ `I have {{{ three braces` }}"),
Set.of("`I have {{{ three braces`")
Arrays.asList(new MustacheBindingToken("{{ `I have {{{ three braces` }}", 0, true)),
Set.of(new MustacheBindingToken(" `I have {{{ three braces` ", 2, false))
);
check(
"{{ `The } char is a brace` }}",
Arrays.asList("{{ `The } char is a brace` }}"),
Set.of("`The } char is a brace`")
Arrays.asList(new MustacheBindingToken("{{ `The } char is a brace` }}", 0, true)),
Set.of(new MustacheBindingToken(" `The } char is a brace` ", 2, false))
);
check(
"{{ `I have }} two braces` }}",
Arrays.asList("{{ `I have }} two braces` }}"),
Set.of("`I have }} two braces`")
Arrays.asList(new MustacheBindingToken("{{ `I have }} two braces` }}", 0, true)),
Set.of(new MustacheBindingToken(" `I have }} two braces` ", 2, false))
);
check(
"{{ `I have }}} three braces` }}",
Arrays.asList("{{ `I have }}} three braces` }}"),
Set.of("`I have }}} three braces`")
Arrays.asList(new MustacheBindingToken("{{ `I have }}} three braces` }}", 0, true)),
Set.of(new MustacheBindingToken(" `I have }}} three braces` ", 2, false))
);
check(
"{{ `Interpolation uses {{ and }} delimiters` }}",
Arrays.asList("{{ `Interpolation uses {{ and }} delimiters` }}"),
Set.of("`Interpolation uses {{ and }} delimiters`")
Arrays.asList(new MustacheBindingToken("{{ `Interpolation uses {{ and }} delimiters` }}", 0, true)),
Set.of(new MustacheBindingToken(" `Interpolation uses {{ and }} delimiters` ", 2, false))
);
}
@ -266,18 +272,18 @@ public class MustacheHelperTest {
public void quotedStringsWithExtras() {
check(
"{{ 2 + ' hello ' + 3 }}",
Arrays.asList("{{ 2 + ' hello ' + 3 }}"),
Set.of("2 + ' hello ' + 3")
Arrays.asList(new MustacheBindingToken("{{ 2 + ' hello ' + 3 }}", 0, true)),
Set.of(new MustacheBindingToken(" 2 + ' hello ' + 3 ", 2, false))
);
check(
"{{ 2 + \" hello \" + 3 }}",
Arrays.asList("{{ 2 + \" hello \" + 3 }}"),
Set.of("2 + \" hello \" + 3")
Arrays.asList(new MustacheBindingToken("{{ 2 + \" hello \" + 3 }}", 0, true)),
Set.of(new MustacheBindingToken(" 2 + \" hello \" + 3 ", 2, false))
);
check(
"{{ 2 + ` hello ` + 3 }}",
Arrays.asList("{{ 2 + ` hello ` + 3 }}"),
Set.of("2 + ` hello ` + 3")
Arrays.asList(new MustacheBindingToken("{{ 2 + ` hello ` + 3 }}", 0, true)),
Set.of(new MustacheBindingToken(" 2 + ` hello ` + 3 ", 2, false))
);
}
@ -285,18 +291,18 @@ public class MustacheHelperTest {
public void quotedStringsWithEscapes() {
check(
"{{ 'Escaped \\' character' }}",
Arrays.asList("{{ 'Escaped \\' character' }}"),
Set.of("'Escaped \\' character'")
Arrays.asList(new MustacheBindingToken("{{ 'Escaped \\' character' }}", 0, true)),
Set.of(new MustacheBindingToken(" 'Escaped \\' character' ", 2, false))
);
check(
"{{ \"Escaped \\\" character\" }}",
Arrays.asList("{{ \"Escaped \\\" character\" }}"),
Set.of("\"Escaped \\\" character\"")
Arrays.asList(new MustacheBindingToken("{{ \"Escaped \\\" character\" }}", 0, true)),
Set.of(new MustacheBindingToken(" \"Escaped \\\" character\" ", 2, false))
);
check(
"{{ `Escaped \\` character` }}",
Arrays.asList("{{ `Escaped \\` character` }}"),
Set.of("`Escaped \\` character`")
Arrays.asList(new MustacheBindingToken("{{ `Escaped \\` character` }}", 0, true)),
Set.of(new MustacheBindingToken(" `Escaped \\` character` ", 2, false))
);
}
@ -304,8 +310,10 @@ public class MustacheHelperTest {
public void conditionalExpression() {
check(
"Conditional: {{ 2 + 4 ? trueVal : falseVal }}",
Arrays.asList("Conditional: ", "{{ 2 + 4 ? trueVal : falseVal }}"),
Set.of("2 + 4 ? trueVal : falseVal")
Arrays.asList(
new MustacheBindingToken("Conditional: ", 0, false),
new MustacheBindingToken("{{ 2 + 4 ? trueVal : falseVal }}", 13, true)),
Set.of(new MustacheBindingToken(" 2 + 4 ? trueVal : falseVal ", 15, false))
);
}
@ -313,8 +321,8 @@ public class MustacheHelperTest {
public void jsonInMustache() {
check(
"{{{\"foo\": \"bar\"}}}",
Arrays.asList("{{{\"foo\": \"bar\"}}}"),
Set.of("{\"foo\": \"bar\"}")
Arrays.asList(new MustacheBindingToken("{{{\"foo\": \"bar\"}}}", 0, true)),
Set.of(new MustacheBindingToken("{\"foo\": \"bar\"}", 2, false))
);
}
@ -339,11 +347,24 @@ public class MustacheHelperTest {
));
configuration.setProperties(Arrays.asList(
new Property("name1", "Hello {{ propertyValue1 }}!"),
new Property("name2", "Hello {{ propertyValue2 }}!")
new Property("name1", "{{ propertyValue1 }}!"),
new Property("name2", "{{ propertyValue2 }}!")
));
Map<String, String> context = Map.of(
" dbName ", "rendered dbName",
" url ", "rendered url",
" headerValue1 ", "rendered headerValue1",
" headerValue2 ", "rendered headerValue2",
" host1 ", "rendered host1",
" host2 ", "rendered host2",
" propertyValue1 ", "rendered propertyValue1",
" propertyValue2 ", "rendered propertyValue2"
);
assertKeys(configuration).hasSameElementsAs(context.keySet().stream().map(keys -> new MustacheBindingToken(keys, 2, false)).collect(Collectors.toSet()));
Map<String, String> context2 = Map.of(
"dbName", "rendered dbName",
"url", "rendered url",
"headerValue1", "rendered headerValue1",
@ -354,9 +375,7 @@ public class MustacheHelperTest {
"propertyValue2", "rendered propertyValue2"
);
assertKeys(configuration).hasSameElementsAs(context.keySet());
renderFieldValues(configuration, context);
renderFieldValues(configuration, context2);
assertThat(configuration.getConnection().getDefaultDatabaseName()).isEqualTo("rendered dbName");
assertThat(configuration.getUrl()).isEqualTo("rendered url");
@ -372,8 +391,8 @@ public class MustacheHelperTest {
);
assertThat(configuration.getProperties()).containsOnly(
new Property("name1", "Hello rendered propertyValue1!"),
new Property("name2", "Hello rendered propertyValue2!")
new Property("name1", "rendered propertyValue1!"),
new Property("name2", "rendered propertyValue2!")
);
}
@ -408,6 +427,30 @@ public class MustacheHelperTest {
));
final Map<String, String> context = new HashMap<>(Map.of(
" body ", "rendered body",
" path ", "rendered path",
" next ", "rendered next",
" headerValue2 ", "rendered headerValue2",
" headerValue1 ", "rendered headerValue1",
" bodyParam1 ", "rendered bodyParam1",
" bodyParam2 ", "rendered bodyParam2",
" queryParam1 ", "rendered queryParam1",
" queryParam2 ", "rendered queryParam2"
));
context.putAll(Map.of(
" pluginSpecifiedProp1 ", "rendered pluginSpecifiedProp1",
" pluginSpecifiedProp2 ", "rendered pluginSpecifiedProp2"
));
assertKeys(configuration)
.hasSameElementsAs(context
.keySet()
.stream()
.map(keys -> new MustacheBindingToken(keys, 2, false))
.collect(Collectors.toSet()));
final Map<String, String> context2 = new HashMap<>(Map.of(
"body", "rendered body",
"path", "rendered path",
"next", "rendered next",
@ -418,15 +461,11 @@ public class MustacheHelperTest {
"queryParam1", "rendered queryParam1",
"queryParam2", "rendered queryParam2"
));
context.putAll(Map.of(
context2.putAll(Map.of(
"pluginSpecifiedProp1", "rendered pluginSpecifiedProp1",
"pluginSpecifiedProp2", "rendered pluginSpecifiedProp2"
));
assertKeys(configuration).hasSameElementsAs(context.keySet());
renderFieldValues(configuration, context);
renderFieldValues(configuration, context2);
assertThat(configuration.getBody()).isEqualTo("rendered body");
assertThat(configuration.getPath()).isEqualTo("rendered path");
@ -462,7 +501,7 @@ public class MustacheHelperTest {
property.setKey("name");
property.setValue("Hello {{ \"there\" }}!");
configuration.setProperties(Arrays.asList(property));
assertKeys(configuration).isEqualTo(Set.of("\"there\""));
assertKeys(configuration).containsExactlyInAnyOrder(new MustacheBindingToken(" \"there\" ", 8, false));
}
@Test
@ -472,7 +511,7 @@ public class MustacheHelperTest {
property.setKey("name");
property.setValue("Hello {{ \"th\\\\ere\" }}!");
configuration.setProperties(Arrays.asList(property));
assertKeys(configuration).isEqualTo(Set.of("\"th\\\\ere\""));
assertKeys(configuration).containsExactlyInAnyOrder(new MustacheBindingToken(" \"th\\\\ere\" ", 8, false));
}
@Test
@ -483,14 +522,14 @@ public class MustacheHelperTest {
// The `\n` should be interpreted by Javascript, not Java. So we put an extra `\` before it.
property.setValue("Hello {{ \"line 1\" + \"\\n\" + \"line 2\" }}!");
configuration.setProperties(Arrays.asList(property));
assertKeys(configuration).isEqualTo(Set.of("\"line 1\" + \"\\n\" + \"line 2\""));
assertKeys(configuration).containsExactlyInAnyOrder(new MustacheBindingToken(" \"line 1\" + \"\\n\" + \"line 2\" ", 8, false));
}
@Test
public void bodyInMustaches() {
ActionConfiguration configuration = new ActionConfiguration();
configuration.setBody("outside {{ab}} outside");
assertKeys(configuration).isEqualTo(Set.of("ab"));
configuration.setBody("outside {{ ab }} outside");
assertKeys(configuration).containsExactlyInAnyOrder(new MustacheBindingToken(" ab ", 10, false));
renderFieldValues(configuration, Map.of("ab", "rendered"));
assertThat(configuration.getBody()).isEqualTo("outside rendered outside");
@ -500,7 +539,7 @@ public class MustacheHelperTest {
public void bodyWithNewlineInMustaches() {
ActionConfiguration configuration = new ActionConfiguration();
configuration.setBody("outside {{a\nb}} outside");
assertKeys(configuration).isEqualTo(Set.of("a\nb"));
assertKeys(configuration).isEqualTo(Set.of(new MustacheBindingToken("a\nb", 10, false)));
renderFieldValues(configuration, Map.of("a\nb", "{\"more\": \"json\"}"));
assertThat(configuration.getBody()).isEqualTo("outside {\"more\": \"json\"} outside");
@ -510,21 +549,21 @@ public class MustacheHelperTest {
public void bodyWithTabInMustaches() {
ActionConfiguration configuration = new ActionConfiguration();
configuration.setBody("outside {{a\tb}} outside");
assertKeys(configuration).isEqualTo(Set.of("a\tb"));
assertKeys(configuration).containsExactlyInAnyOrder(new MustacheBindingToken("a\tb", 10, false));
}
@Test
public void bodyWithMultilineJavascriptInMustaches() {
ActionConfiguration configuration = new ActionConfiguration();
configuration.setBody("outside {{\n\ttrue\n\t\t? \"yes\\n\"\n\t\t: \"no\\n\"\n}} outside");
assertKeys(configuration).isEqualTo(Set.of("true\n\t\t? \"yes\\n\"\n\t\t: \"no\\n\""));
assertKeys(configuration).containsExactlyInAnyOrder(new MustacheBindingToken("\n\ttrue\n\t\t? \"yes\\n\"\n\t\t: \"no\\n\"\n", 10, false));
}
@Test
public void renderBodyWithMultilineJavascriptInMustaches() {
ActionConfiguration configuration = new ActionConfiguration();
configuration.setBody("outside {{\n\ttrue\n\t\t \"yes\\n\"\n\t\t \"no\\n\"\n}} outside");
assertKeys(configuration).isEqualTo(Set.of("true\n\t\t \"yes\\n\"\n\t\t \"no\\n\""));
assertKeys(configuration).containsExactlyInAnyOrder(new MustacheBindingToken("\n\ttrue\n\t\t \"yes\\n\"\n\t\t \"no\\n\"\n", 10, false));
renderFieldValues(configuration, Map.of("true\n\t\t \"yes\\n\"\n\t\t \"no\\n\"", "{\"more\": \"json\"}"));
assertThat(configuration.getBody()).isEqualTo("outside {\"more\": \"json\"} outside");

View File

@ -31,6 +31,7 @@ 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.MustacheBindingToken;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.RequestParamDTO;
@ -419,7 +420,7 @@ public class AmazonS3Plugin extends BasePlugin {
if (TRUE.equals(smartJsonSubstitution)) {
final String body = getDataValueSafelyFromFormData(formData, BODY, STRING_TYPE, "");
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(body);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(body);
// Replace all the bindings with a placeholder
String updatedValue = MustacheHelper.replaceMustacheWithPlaceholder(body, mustacheKeysInOrder);

View File

@ -12,6 +12,7 @@ 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.MustacheBindingToken;
import com.appsmith.external.models.PaginationField;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.RequestParamDTO;
@ -147,7 +148,7 @@ public class FirestorePlugin extends BasePlugin {
if (query != null) {
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedQuery = MustacheHelper.replaceMustacheWithPlaceholder(query, mustacheKeysInOrder);

View File

@ -8,6 +8,7 @@ import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.OAuth2;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
@ -106,7 +107,7 @@ public class GoogleSheetsPlugin extends BasePlugin {
if (property != null) {
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(property);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(property);
// Replace all the bindings with a placeholder
String updatedValue = MustacheHelper.replaceMustacheWithPlaceholder(property, mustacheKeysInOrder);

View File

@ -12,6 +12,7 @@ import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.ApiContentType;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.PaginationType;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
@ -89,7 +90,7 @@ public class GraphQLPlugin extends BasePlugin {
if (TRUE.equals(smartSubstitution)) {
/* Apply smart JSON substitution logic to mustache binding values in query variables */
if (!isBlank(variables)) {
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(variables);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(variables);
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedVariables = MustacheHelper.replaceMustacheWithPlaceholder(variables, mustacheKeysInOrder);
@ -112,7 +113,7 @@ public class GraphQLPlugin extends BasePlugin {
/* Apply smart substitution logic to query body */
String query = actionConfiguration.getBody();
if (!isBlank(query)) {
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedQuery = MustacheHelper.replaceMustacheWithPlaceholder(query, mustacheKeysInOrder);
@ -238,8 +239,7 @@ public class GraphQLPlugin extends BasePlugin {
return Mono.error(e);
}
}
}
else if (HttpMethod.GET.equals(httpMethod)) {
} else if (HttpMethod.GET.equals(httpMethod)) {
/**
* When a GraphQL request is sent using GET method, the GraphQL body and variables are sent as part of
* query parameters in the URL.
@ -247,8 +247,7 @@ public class GraphQLPlugin extends BasePlugin {
*/
List<Property> additionalQueryParams = getGraphQLQueryParamsForBodyAndVariables(actionConfiguration);
uri = uriUtils.addQueryParamsToURI(uri, additionalQueryParams, encodeParamsToggle);
}
else {
} else {
/**
* Only POST and GET HTTP methods are supported by GraphQL specifications.
* Ref: https://graphql.org/learn/serving-over-http/
@ -288,8 +287,7 @@ public class GraphQLPlugin extends BasePlugin {
if (!isInputQueryBody) {
String queryVariables = (String) input;
return DataTypeStringUtils.jsonSmartReplacementPlaceholderWithValue(queryVariables, value, null, insertedParams, null, param);
}
else {
} else {
String queryBody = (String) input;
return smartlyReplaceGraphQLQueryBodyPlaceholderWithValue(queryBody, value, insertedParams);
}

View File

@ -21,6 +21,7 @@ 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.models.MustacheBindingToken;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.ParsedDataType;
import com.appsmith.external.models.Property;
@ -577,7 +578,7 @@ public class MongoPlugin extends BasePlugin {
List<Map.Entry<String, String>> parameters) throws AppsmithPluginException {
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(rawQuery);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(rawQuery);
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedQuery = MustacheHelper.replaceMustacheWithPlaceholder(rawQuery, mustacheKeysInOrder);

View File

@ -14,6 +14,7 @@ import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.DBAuth;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.Endpoint;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.PsParameterDTO;
@ -156,7 +157,7 @@ public class MssqlPlugin extends BasePlugin {
//Prepared Statement
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedQuery = MustacheHelper.replaceMustacheWithQuestionMark(query, mustacheKeysInOrder);
actionConfiguration.setBody(updatedQuery);
@ -166,7 +167,7 @@ public class MssqlPlugin extends BasePlugin {
public Mono<ActionExecutionResult> executeCommon(HikariDataSource hikariDSConnection,
ActionConfiguration actionConfiguration,
Boolean preparedStatement,
List<String> mustacheValuesInOrder,
List<MustacheBindingToken> mustacheValuesInOrder,
ExecuteActionDTO executeActionDTO) {
final Map<String, Object> requestData = new HashMap<>();

View File

@ -1,7 +1,6 @@
package com.external.plugins;
import com.appsmith.external.datatypes.AppsmithType;
import com.appsmith.external.datatypes.ClientDataType;
import com.appsmith.external.dtos.ExecuteActionDTO;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
@ -15,6 +14,7 @@ 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 com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.PsParameterDTO;
@ -214,7 +214,7 @@ public class MySqlPlugin extends BasePlugin {
//This has to be executed as Prepared Statement
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedQuery = MustacheHelper.replaceMustacheWithQuestionMark(query, mustacheKeysInOrder);
// Set the query with bindings extracted and replaced with '?' back in config
@ -225,7 +225,7 @@ public class MySqlPlugin extends BasePlugin {
public Mono<ActionExecutionResult> executeCommon(Connection connection,
ActionConfiguration actionConfiguration,
Boolean preparedStatement,
List<String> mustacheValuesInOrder,
List<MustacheBindingToken> mustacheValuesInOrder,
ExecuteActionDTO executeActionDTO,
Map<String, Object> requestData) {
@ -351,7 +351,7 @@ public class MySqlPlugin extends BasePlugin {
private Flux<Result> createAndExecuteQueryFromConnection(String query,
Connection connection,
Boolean preparedStatement,
List<String> mustacheValuesInOrder,
List<MustacheBindingToken> mustacheValuesInOrder,
ExecuteActionDTO executeActionDTO,
Map<String, Object> requestData,
Map psParams) {

View File

@ -15,6 +15,7 @@ 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 com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.PsParameterDTO;
@ -236,7 +237,7 @@ public class PostgresPlugin extends BasePlugin {
// Prepared Statement
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(query);
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedQuery = MustacheHelper.replaceMustacheWithQuestionMark(query, mustacheKeysInOrder);
List<DataType> explicitCastDataTypes = extractExplicitCasting(updatedQuery);
@ -249,7 +250,7 @@ public class PostgresPlugin extends BasePlugin {
DatasourceConfiguration datasourceConfiguration,
ActionConfiguration actionConfiguration,
Boolean preparedStatement,
List<String> mustacheValuesInOrder,
List<MustacheBindingToken> mustacheValuesInOrder,
ExecuteActionDTO executeActionDTO,
List<DataType> explicitCastDataTypes) {

View File

@ -11,6 +11,7 @@ import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.PaginationField;
import com.appsmith.external.models.PaginationType;
import com.appsmith.external.models.Param;
@ -79,7 +80,7 @@ public class RestApiPlugin extends BasePlugin {
if (actionConfiguration.getBody() != null) {
// First extract all the bindings in order
List<String> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(actionConfiguration.getBody());
List<MustacheBindingToken> mustacheKeysInOrder = MustacheHelper.extractMustacheKeysInOrder(actionConfiguration.getBody());
// Replace all the bindings with a ? as expected in a prepared statement.
String updatedBody = MustacheHelper.replaceMustacheWithPlaceholder(actionConfiguration.getBody(), mustacheKeysInOrder);

View File

@ -149,6 +149,7 @@ public enum AppsmithError {
UNKNOWN_PLUGIN_REFERENCE(400, 4052, " Unable to find the plugin. Please reach out to Appsmith customer support to resolve this.", AppsmithErrorAction.DEFAULT, null, ErrorType.CONFIGURATION_ERROR, null),
ENV_FILE_NOT_FOUND(500, 5019, "Admin Settings is unavailable. Unable to read and write to Environment file.", AppsmithErrorAction.DEFAULT, null, ErrorType.CONFIGURATION_ERROR, null),
PUBLIC_APP_NO_PERMISSION_GROUP(500, 5020, "Invalid state. Public application does not have the required roles set for public access. Please reach out to Appsmith customer support to resolve this.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
RTS_SERVER_ERROR(500, 5021, "RTS server error while processing request: {0}", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null),
;
private final Integer httpErrorCode;

View File

@ -1,12 +1,12 @@
package com.appsmith.server.helpers;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.MustacheBindingToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.HashSet;
@ -17,11 +17,10 @@ import java.util.regex.Pattern;
public class DslUtils {
public static Set<String> getMustacheValueSetFromSpecificDynamicBindingPath(JsonNode dsl, String fieldPath) {
public static Set<MustacheBindingToken> getMustacheValueSetFromSpecificDynamicBindingPath(JsonNode dsl, String fieldPath) {
DslNodeWalkResponse dslWalkResponse = getDslWalkResponse(dsl, fieldPath);
// Only extract mustache keys from leaf nodes
if (dslWalkResponse != null && dslWalkResponse.isLeafNode) {
@ -31,7 +30,7 @@ public class DslUtils {
}
// Stricter extraction of dynamic bindings
Set<String> mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(((TextNode) dslWalkResponse.currentNode).asText());
Set<MustacheBindingToken> mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(((TextNode) dslWalkResponse.currentNode).asText());
return mustacheKeysFromFields;
}
@ -39,23 +38,29 @@ public class DslUtils {
return new HashSet<>();
}
public static JsonNode replaceValuesInSpecificDynamicBindingPath(JsonNode dsl, String fieldPath, Map<String, String> replacementMap) {
public static JsonNode replaceValuesInSpecificDynamicBindingPath(JsonNode dsl, String fieldPath, Map<MustacheBindingToken, String> replacementMap) {
DslNodeWalkResponse dslWalkResponse = getDslWalkResponse(dsl, fieldPath);
if (dslWalkResponse != null && dslWalkResponse.isLeafNode) {
final String oldValue = ((TextNode) dslWalkResponse.currentNode).asText();
final StringBuilder oldValue = new StringBuilder(((TextNode) dslWalkResponse.currentNode).asText());
final String newValue = StringUtils.replaceEach(
oldValue,
replacementMap.keySet().toArray(new String[0]),
replacementMap.values().toArray(new String[0]));
for (MustacheBindingToken mustacheBindingToken : replacementMap.keySet()) {
String tokenValue = mustacheBindingToken.getValue();
int endIndex = mustacheBindingToken.getStartIndex() + tokenValue.length();
if (oldValue.length() >= endIndex && oldValue.subSequence(mustacheBindingToken.getStartIndex(), endIndex).equals(tokenValue)) {
oldValue.replace(mustacheBindingToken.getStartIndex(), endIndex, replacementMap.get(mustacheBindingToken));
}
}
((ObjectNode) dslWalkResponse.parentNode).set(dslWalkResponse.currentKey, new TextNode(newValue));
((ObjectNode) dslWalkResponse.parentNode).set(dslWalkResponse.currentKey, new TextNode(oldValue.toString()));
}
return dsl;
}
private static DslNodeWalkResponse getDslWalkResponse(JsonNode dsl, String fieldPath) {
if (dsl == null) {
return null;
}
String[] fields = fieldPath.split("[].\\[]");
// For nested fields, the parent dsl to search in would shift by one level every iteration
Object currentNode = dsl;

View File

@ -233,9 +233,9 @@ public class DatabaseChangelog {
/* Add plugin to the workspace */
Update update = new Update();
update.addToSet("plugins",new WorkspacePlugin(pluginId, WorkspacePluginStatus.FREE));
update.addToSet("plugins", new WorkspacePlugin(pluginId, WorkspacePluginStatus.FREE));
mongockTemplate.updateMulti(queryToFetchWorkspacesWOPlugin,update,Workspace.class);
mongockTemplate.updateMulti(queryToFetchWorkspacesWOPlugin, update, Workspace.class);
}
@ChangeSet(order = "001", id = "initial-plugins", author = "")
@ -755,7 +755,7 @@ public class DatabaseChangelog {
}
}
// Examples organization is no longer getting used. Commenting out the migrations which add/update the same.
// Examples organization is no longer getting used. Commenting out the migrations which add/update the same.
// @SuppressWarnings({"unchecked", "rawtypes"})
// @ChangeSet(order = "022", id = "examples-organization", author = "")
// public void examplesOrganization(MongockTemplate mongoTemplate, EncryptionService encryptionService) throws IOException {
@ -1886,7 +1886,7 @@ public class DatabaseChangelog {
}
// Only extract mustache keys from leaf nodes
if (parent != null && isLeafNode) {
Set<String> mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent);
Set<String> mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent).stream().map(token -> token.getValue()).collect(Collectors.toSet());
// We found the path. But if the path does not have any mustache bindings, remove it from the path list
if (mustacheKeysFromFields.isEmpty()) {
@ -3481,9 +3481,9 @@ public class DatabaseChangelog {
* This method holds the steps to transform data before it is migrated to UQI schema.
* Each transformation is uniquely identified by the combination of plugin name and the transformation name.
*
* @param pluginName - name of the plugin for which the transformation is intended
* @param pluginName - name of the plugin for which the transformation is intended
* @param transformationName - name of the transformation relative to the plugin
* @param value - value that needs to be transformed
* @param value - value that needs to be transformed
* @return - transformed value
*/
public Object transformData(String pluginName, String transformationName, Object value) {
@ -3664,13 +3664,13 @@ public class DatabaseChangelog {
* @param dynamicBindingPathList : old dynamicBindingPathList
* @param objectMapper
* @param action
* @param migrationMap : A mapping from `pluginSpecifiedTemplates` index to attribute path in UQI model. For
* reference, please check out the `s3MigrationMap` defined above.
* @param migrationMap : A mapping from `pluginSpecifiedTemplates` index to attribute path in UQI model. For
* reference, please check out the `s3MigrationMap` defined above.
* @return : updated dynamicBindingPathList - ported to UQI model.
*/
static List<Property> getUpdatedDynamicBindingPathList(List<Property> dynamicBindingPathList,
ObjectMapper objectMapper, NewAction action,
Map<Integer, List<String>> migrationMap) {
ObjectMapper objectMapper, NewAction action,
Map<Integer, List<String>> migrationMap) {
// Return if empty.
if (CollectionUtils.isEmpty(dynamicBindingPathList)) {
return dynamicBindingPathList;
@ -4101,7 +4101,7 @@ public class DatabaseChangelog {
.include(fieldName(QDatasource.datasource.organizationId));
List<Datasource> datasources = mongockTemplate.find(datasourceQuery, Datasource.class);
for(Datasource datasource: datasources) {
for (Datasource datasource : datasources) {
final Update update = new Update();
final String gitSyncId = datasource.getOrganizationId() + "_" + new ObjectId();
update.set(fieldName(QDatasource.datasource.gitSyncId), gitSyncId);
@ -4117,7 +4117,7 @@ public class DatabaseChangelog {
.addCriteria(where(fieldName(QApplication.application.pages)).exists(true));
List<Application> applications = mongockTemplate.find(applicationQuery, Application.class);
for(Application application: applications) {
for (Application application : applications) {
application.getPages().forEach(page -> {
page.setDefaultPageId(page.getId());
});
@ -4179,7 +4179,7 @@ public class DatabaseChangelog {
defaultResourceUpdates.set(fieldName(QNewPage.newPage.publishedPage) + "." + "layouts", page.getPublishedPage().getLayouts());
}
if (!StringUtils.isEmpty(applicationId) ) {
if (!StringUtils.isEmpty(applicationId)) {
mongockTemplate.updateFirst(
query(where(fieldName(QNewPage.newPage.id)).is(page.getId())),
defaultResourceUpdates,
@ -4622,7 +4622,7 @@ public class DatabaseChangelog {
);
// Query to get action id from all Firestore actions
Query queryToGetActionIds =query(
Query queryToGetActionIds = query(
where(fieldName(QNewAction.newAction.pluginId)).is(firestorePlugin.getId())
.and(fieldName(QNewAction.newAction.deleted)).ne(true)
);
@ -4647,7 +4647,7 @@ public class DatabaseChangelog {
ActionDTO unpublishedAction = firestoreAction.getUnpublishedAction();
// No migrations required if action configuration does not exist.
if (unpublishedAction == null || unpublishedAction.getActionConfiguration() == null ) {
if (unpublishedAction == null || unpublishedAction.getActionConfiguration() == null) {
continue;
}
@ -4733,6 +4733,7 @@ public class DatabaseChangelog {
/**
* This method sets the key formData.aggregate.limit to 101 for all Mongo plugin actions.
* It iterates over each action id one by one to avoid out of memory error.
*
* @param mongoActions
* @param mongockTemplate
*/
@ -4760,6 +4761,7 @@ public class DatabaseChangelog {
/**
* Returns true only if the action has non-null published actionConfiguration.
*
* @param action
* @return true / false
*/
@ -4783,6 +4785,7 @@ public class DatabaseChangelog {
* Mongo database - this is the same value that would have been applied to the aggregate cmd so far by the
* database. However, for any new action, this field's initial value is 10.
* Ref: https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/
*
* @param mongockTemplate
*/
@ChangeSet(order = "109", id = "add-limit-field-data-to-mongo-aggregate-cmd", author = "")
@ -4804,6 +4807,7 @@ public class DatabaseChangelog {
/**
* Returns true only if the action has non-null un-published actionConfiguration.
*
* @param action
* @return true / false
*/
@ -4818,6 +4822,7 @@ public class DatabaseChangelog {
/**
* Fetch an action using id.
*
* @param actionId
* @param mongockTemplate
* @return action
@ -4830,6 +4835,7 @@ public class DatabaseChangelog {
/**
* Generate query to fetch all non-deleted actions defined for a given plugin.
*
* @param plugin
* @return query
*/
@ -4884,7 +4890,7 @@ public class DatabaseChangelog {
query.addCriteria(Criteria.where("deleted").is(FALSE));
for (Application application : mongockTemplate.find(query, Application.class)) {
if(!Optional.ofNullable(application.getGitApplicationMetadata()).isEmpty()) {
if (!Optional.ofNullable(application.getGitApplicationMetadata()).isEmpty()) {
GitAuth gitAuth = GitDeployKeyGenerator.generateSSHKey(null);
GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata();
gitApplicationMetadata.setGitAuth(gitAuth);
@ -4988,6 +4994,7 @@ public class DatabaseChangelog {
/**
* Adding this migration again because we've added permission to themes.
* Also there are couple of changes in the system theme properties.
*
* @param mongockTemplate
* @throws IOException
*/

View File

@ -225,7 +225,8 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
/**
* Generates event name tag to analytic events
* @param event AnalyticsEvents
*
* @param event AnalyticsEvents
* @param object Analytic event resource object
* @return String
*/

View File

@ -1,5 +1,6 @@
package com.appsmith.server.services.ce;
import com.appsmith.external.models.MustacheBindingToken;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
@ -23,5 +24,5 @@ public interface AstServiceCE {
*/
Flux<Tuple2<String, Set<String>>> getPossibleReferencesFromDynamicBinding(List<String> bindingValues, int evalVersion);
Mono<Map<String, String>> refactorNameInDynamicBindings(Set<String> bindingValues, String oldName, String newName, int evalVersion);
Mono<Map<MustacheBindingToken, String>> refactorNameInDynamicBindings(Set<MustacheBindingToken> bindingValues, String oldName, String newName, int evalVersion, boolean isJSObject);
}

View File

@ -1,8 +1,11 @@
package com.appsmith.server.services.ce;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.InstanceConfig;
import com.appsmith.server.exceptions.AppsmithError;
import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.util.WebClientUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -10,6 +13,7 @@ import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
@ -24,6 +28,7 @@ import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@ -76,7 +81,7 @@ public class AstServiceCEImpl implements AstServiceCE {
.retrieve()
.bodyToMono(GetIdentifiersResponseBulk.class)
.retryWhen(Retry.max(3))
.flatMapIterable(getIdentifiersResponseDetails -> getIdentifiersResponseDetails.data)
.flatMapIterable(getIdentifiersResponse -> getIdentifiersResponse.data)
.index()
.flatMap(tuple2 -> {
long currentIndex = tuple2.getT1();
@ -87,20 +92,27 @@ public class AstServiceCEImpl implements AstServiceCE {
}
@Override
public Mono<Map<String, String>> refactorNameInDynamicBindings(Set<String> bindingValues, String oldName, String newName, int evalVersion) {
public Mono<Map<MustacheBindingToken, String>> refactorNameInDynamicBindings(Set<MustacheBindingToken> bindingValues, String oldName, String newName, int evalVersion, boolean isJSObject) {
if (bindingValues == null || bindingValues.isEmpty()) {
return Mono.empty();
}
return Flux.fromIterable(bindingValues)
.flatMap(bindingValue -> {
EntityRefactorRequest entityRefactorRequest = new EntityRefactorRequest(bindingValue.getValue(), oldName, newName, evalVersion, isJSObject);
return webClient
.post()
.uri(commonConfig.getRtsBaseDomain() + "/rts-api/v1/ast/entity-refactor")
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(new EntityRefactorRequest(bindingValue, oldName, newName, evalVersion)))
.body(BodyInserters.fromValue(entityRefactorRequest))
.retrieve()
.bodyToMono(EntityRefactorResponse.class)
.toEntity(EntityRefactorResponse.class)
.flatMap(entityRefactorResponseResponseEntity -> {
if (HttpStatus.OK.equals(entityRefactorResponseResponseEntity.getStatusCode())) {
return Mono.just(Objects.requireNonNull(entityRefactorResponseResponseEntity.getBody()));
}
return Mono.error(new AppsmithException(AppsmithError.RTS_SERVER_ERROR, entityRefactorResponseResponseEntity.getStatusCodeValue()));
})
.elapsed()
.map(tuple -> {
log.debug("Time elapsed since AST refactor call: {} ms", tuple.getT1());
@ -189,6 +201,7 @@ public class AstServiceCEImpl implements AstServiceCE {
String oldName;
String newName;
int evalVersion;
Boolean isJSObject;
}
@NoArgsConstructor

View File

@ -3,6 +3,7 @@ package com.appsmith.server.services.ce;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.services.CrudService;
import reactor.core.publisher.Flux;
@ -21,7 +22,7 @@ public interface DatasourceServiceCE extends CrudService<Datasource, String> {
Mono<Datasource> findById(String id);
Set<String> extractKeysFromDatasource(Datasource datasource);
Set<MustacheBindingToken> extractKeysFromDatasource(Datasource datasource);
Mono<Datasource> validateDatasource(Datasource datasource);

View File

@ -7,6 +7,7 @@ import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.DatasourceTestResult;
import com.appsmith.external.models.Endpoint;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.Policy;
import com.appsmith.external.models.QDatasource;
import com.appsmith.external.plugins.PluginExecutor;
@ -400,7 +401,7 @@ public class DatasourceServiceCEImpl extends BaseService<DatasourceRepository, D
}
@Override
public Set<String> extractKeysFromDatasource(Datasource datasource) {
public Set<MustacheBindingToken> extractKeysFromDatasource(Datasource datasource) {
if (datasource == null || datasource.getDatasourceConfiguration() == null) {
return new HashSet<>();
}

View File

@ -62,6 +62,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.appsmith.server.services.ce.ApplicationPageServiceCEImpl.EVALUATION_VERSION;
import static java.lang.Boolean.FALSE;
@ -341,7 +342,7 @@ public class LayoutActionServiceCEImpl implements LayoutActionServiceCE {
}
// Stricter extraction of dynamic bindings
Set<String> mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent);
Set<String> mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent).stream().map(token -> token.getValue()).collect(Collectors.toSet());
String completePath = widgetName + "." + fieldPath;
if (widgetDynamicBindingsMap.containsKey(completePath)) {

View File

@ -1,6 +1,7 @@
package com.appsmith.server.services.ce;
import com.appsmith.external.helpers.AppsmithBeanUtils;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.DefaultResources;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ActionCollection;
@ -9,7 +10,6 @@ import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.dtos.ActionCollectionDTO;
import com.appsmith.server.dtos.ActionCollectionMoveDTO;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.server.dtos.LayoutDTO;
import com.appsmith.server.dtos.RefactorActionCollectionNameDTO;
import com.appsmith.server.dtos.RefactorActionNameDTO;
@ -38,15 +38,13 @@ import reactor.core.publisher.Mono;
import java.time.Instant;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Map;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNewFieldValuesIntoOldObject;
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
@ -319,9 +317,14 @@ public class LayoutCollectionServiceCEImpl implements LayoutCollectionServiceCE
return actionUpdatesFlux
.then(actionCollectionService.update(branchedActionCollection.getId(), branchedActionCollection))
.then(branchedPageIdMono)
.flatMap(branchedPageId -> refactoringSolution.refactorName(branchedPageId, layoutId, oldName, newName));
.flatMap(branchedPageId -> refactoringSolution.refactorActionCollectionName(
branchedActionCollection.getApplicationId(),
branchedPageId,
layoutId,
oldName,
newName));
})
.map(responseUtils::updateLayoutDTOWithDefaultResources);
.map(layoutDTO -> responseUtils.updateLayoutDTOWithDefaultResources(layoutDTO));
}
@Override

View File

@ -2,6 +2,7 @@ package com.appsmith.server.services.ce;
import com.appsmith.external.dtos.ExecuteActionDTO;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
@ -38,7 +39,7 @@ public interface NewActionServiceCE extends CrudService<NewAction, String> {
Mono<ActionExecutionResult> executeAction(ExecuteActionDTO executeActionDTO);
Mono<ActionExecutionResult> executeAction(Flux<Part> partsFlux, String branchName);
Mono<ActionDTO> getValidActionForExecution(ExecuteActionDTO executeActionDTO, String actionId, NewAction newAction);
<T> T variableSubstitution(T configuration, Map<String, String> replaceParamsMap);
@ -85,7 +86,7 @@ public interface NewActionServiceCE extends CrudService<NewAction, String> {
Mono<List<NewAction>> archiveActionsByApplicationId(String applicationId, AclPermission permission);
List<String> extractMustacheKeysInOrder(String query);
List<MustacheBindingToken> extractMustacheKeysInOrder(String query);
String replaceMustacheWithQuestionMark(String query, List<String> mustacheBindings);

View File

@ -10,12 +10,16 @@ import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException
import com.appsmith.external.exceptions.pluginExceptions.StaleConnectionException;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.ActionExecutionRequest;
import com.appsmith.external.models.ActionExecutionResult;
import com.appsmith.external.models.ActionProvider;
import com.appsmith.external.models.Datasource;
import com.appsmith.external.models.DatasourceConfiguration;
import com.appsmith.external.models.DefaultResources;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.Param;
import com.appsmith.external.models.PluginType;
import com.appsmith.external.models.Policy;
import com.appsmith.external.models.Property;
import com.appsmith.external.models.Provider;
@ -25,7 +29,6 @@ import com.appsmith.server.acl.AclPermission;
import com.appsmith.server.acl.PolicyGenerator;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Action;
import com.appsmith.external.models.ActionProvider;
import com.appsmith.server.domains.Application;
import com.appsmith.server.domains.ApplicationMode;
import com.appsmith.server.domains.DatasourceContext;
@ -33,9 +36,7 @@ import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.Page;
import com.appsmith.server.domains.Plugin;
import com.appsmith.external.models.PluginType;
import com.appsmith.server.domains.User;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.server.dtos.ActionViewDTO;
import com.appsmith.server.dtos.LayoutActionUpdateDTO;
import com.appsmith.server.exceptions.AppsmithError;
@ -106,11 +107,7 @@ import java.util.stream.Collectors;
import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNewFieldValuesIntoOldObject;
import static com.appsmith.external.helpers.DataTypeStringUtils.getDisplayDataTypes;
import static com.appsmith.external.helpers.PluginUtils.setValueSafelyInFormData;
import static com.appsmith.server.acl.AclPermission.EXECUTE_ACTIONS;
import static com.appsmith.server.acl.AclPermission.EXECUTE_DATASOURCES;
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
import static com.appsmith.server.acl.AclPermission.READ_ACTIONS;
import static com.appsmith.server.acl.AclPermission.READ_PAGES;
import static com.appsmith.server.helpers.WidgetSuggestionHelper.getSuggestedWidgets;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
@ -447,7 +444,7 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
* @param actionDTO
* @return
*/
private Set<String> extractKeysFromAction(ActionDTO actionDTO) {
private Set<MustacheBindingToken> extractKeysFromAction(ActionDTO actionDTO) {
if (actionDTO == null) {
return new HashSet<>();
}
@ -457,11 +454,11 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
return new HashSet<>();
}
Set<String> keys = MustacheHelper.extractMustacheKeysFromFields(actionConfiguration);
Set<MustacheBindingToken> keys = MustacheHelper.extractMustacheKeysFromFields(actionConfiguration);
// Add JS function body to jsonPathKeys field.
if (PluginType.JS.equals(actionDTO.getPluginType()) && actionConfiguration.getBody() != null) {
keys.add(actionConfiguration.getBody());
keys.add(new MustacheBindingToken(actionConfiguration.getBody(), 0, false));
// Since this is a JS function, we should also set the dynamic binding path list if absent
List<Property> dynamicBindingPathList = actionDTO.getDynamicBindingPathList();
@ -485,8 +482,8 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
@Override
public NewAction extractAndSetJsonPathKeys(NewAction newAction) {
ActionDTO action = newAction.getUnpublishedAction();
Set<String> actionKeys = extractKeysFromAction(action);
Set<String> datasourceKeys = datasourceService.extractKeysFromDatasource(action.getDatasource());
Set<String> actionKeys = extractKeysFromAction(action).stream().map(token -> token.getValue()).collect(Collectors.toSet());
Set<String> datasourceKeys = datasourceService.extractKeysFromDatasource(action.getDatasource()).stream().map(token -> token.getValue()).collect(Collectors.toSet());
Set<String> keys = new HashSet<>() {{
addAll(actionKeys);
addAll(datasourceKeys);
@ -1054,6 +1051,7 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
/**
* Since we're loading the application and other details from DB *only* for analytics, we check if analytics is
* active before making the call to DB.
*
* @return
*/
public Boolean isSendExecuteAnalyticsEvent() {
@ -1167,7 +1165,7 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
if (paramsList == null) {
paramsList = new ArrayList<>();
}
List<String> executionParams = paramsList.stream().map(param -> param.getValue()).collect(Collectors.toList());
List<String> executionParams = paramsList.stream().map(param -> param.getValue()).collect(Collectors.toList());
data.putAll(Map.of(
"request", request,
@ -1913,7 +1911,7 @@ public class NewActionServiceCEImpl extends BaseService<NewActionRepository, New
.collectList();
}
public List<String> extractMustacheKeysInOrder(String query) {
public List<MustacheBindingToken> extractMustacheKeysInOrder(String query) {
return MustacheHelper.extractMustacheKeysInOrder(query);
}

View File

@ -1,13 +1,15 @@
package com.appsmith.server.solutions;
package com.appsmith.server.solutions;
import com.appsmith.server.configurations.InstanceConfig;
import com.appsmith.server.helpers.ResponseUtils;
import com.appsmith.server.services.ActionCollectionService;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.AstService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.services.NewActionService;
import com.appsmith.server.services.NewPageService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.ce.RefactoringSolutionCEImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
@ -26,6 +28,8 @@ public class RefactoringSolutionImpl extends RefactoringSolutionCEImpl implement
ApplicationService applicationService,
AstService astService,
InstanceConfig instanceConfig,
AnalyticsService analyticsService,
SessionUserService sessionUserService,
PagePermission pagePermission,
ActionPermission actionPermission) {
super(objectMapper,
@ -37,6 +41,8 @@ public class RefactoringSolutionImpl extends RefactoringSolutionCEImpl implement
applicationService,
astService,
instanceConfig,
analyticsService,
sessionUserService,
pagePermission,
actionPermission);
}

View File

@ -41,7 +41,6 @@ import java.util.stream.Collectors;
import static com.appsmith.external.helpers.MustacheHelper.ACTION_ENTITY_REFERENCES;
import static com.appsmith.external.helpers.MustacheHelper.WIDGET_ENTITY_REFERENCES;
import static com.appsmith.external.helpers.MustacheHelper.getPossibleParents;
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
@ -934,7 +933,7 @@ public class PageLoadActionsUtilCEImpl implements PageLoadActionsUtilCE {
Set<String> mustacheKeysFromFields;
// Stricter extraction of dynamic bindings
if (isBindingPresentInString) {
mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent);
mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent).stream().map(token -> token.getValue()).collect(Collectors.toSet());
} else {
// this must be a JS function. No need to extract mustache. The entire string is JS body
mustacheKeysFromFields = Set.of((String) parent);

View File

@ -4,6 +4,9 @@ import com.appsmith.server.dtos.LayoutDTO;
import com.appsmith.server.dtos.RefactorActionNameDTO;
import com.appsmith.server.dtos.RefactorNameDTO;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.Set;
public interface RefactoringSolutionCE {
@ -15,5 +18,16 @@ public interface RefactoringSolutionCE {
Mono<LayoutDTO> refactorActionName(RefactorActionNameDTO refactorActionNameDTO, String branchName);
Mono<LayoutDTO> refactorName(String pageId, String layoutId, String oldName, String newName);
Mono<LayoutDTO> refactorActionCollectionName(String appId, String pageId, String layoutId, String oldName, String newName);
/**
* This method is responsible for the core logic of refactoring a valid name inside an Appsmith page.
* This includes refactoring inside the DSL, in actions, and JS objects.
* @param pageId The page where the refactor needs to happen
* @param layoutId The layout where the refactor needs to happen
* @param oldName The valid name to convert from. For JS functions, this would be the FQN
* @param newName The new name to convert into. For JS functions, this would be FQN
* @return A tuple of the updated layout after refactoring and a set of all the paths in the page that ended up getting refactored
*/
Mono<Tuple2<LayoutDTO, Set<String>>> refactorName(String pageId, String layoutId, String oldName, String newName);
}

View File

@ -1,7 +1,9 @@
package com.appsmith.server.solutions.ce;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.models.ActionConfiguration;
import com.appsmith.external.models.ActionDTO;
import com.appsmith.external.models.MustacheBindingToken;
import com.appsmith.external.models.PluginType;
import com.appsmith.server.configurations.InstanceConfig;
import com.appsmith.server.constants.FieldName;
@ -17,11 +19,13 @@ import com.appsmith.server.exceptions.AppsmithException;
import com.appsmith.server.helpers.DslUtils;
import com.appsmith.server.helpers.ResponseUtils;
import com.appsmith.server.services.ActionCollectionService;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.AstService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.services.NewActionService;
import com.appsmith.server.services.NewPageService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.PagePermission;
import com.fasterxml.jackson.databind.JsonNode;
@ -38,6 +42,7 @@ import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@ -49,8 +54,6 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS;
import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES;
import static com.appsmith.server.services.ce.ApplicationPageServiceCEImpl.EVALUATION_VERSION;
import static java.util.stream.Collectors.toSet;
@ -67,11 +70,8 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
private final PagePermission pagePermission;
private final ActionPermission actionPermission;
private final InstanceConfig instanceConfig;
private final Boolean isRtsAccessible;
private static final Pattern actionCollectionBodyPattern = Pattern.compile("export default(.*)", Pattern.DOTALL);
private static final String EXPORT_DEFAULT_STRING = "export default";
private final AnalyticsService analyticsService;
private final SessionUserService sessionUserService;
/*
* To replace fetchUsers in `{{JSON.stringify(fetchUsers)}}` with getUsers, the following regex is required :
* `\\b(fetchUsers)\\b`. To achieve this the following strings preWord and postWord are declared here to be used
@ -89,6 +89,8 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
ApplicationService applicationService,
AstService astService,
InstanceConfig instanceConfig,
AnalyticsService analyticsService,
SessionUserService sessionUserService,
PagePermission pagePermission,
ActionPermission actionPermission) {
this.objectMapper = objectMapper;
@ -100,26 +102,30 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
this.applicationService = applicationService;
this.astService = astService;
this.instanceConfig = instanceConfig;
this.analyticsService = analyticsService;
this.sessionUserService = sessionUserService;
this.pagePermission = pagePermission;
this.actionPermission = actionPermission;
// TODO Remove this variable and access the field directly when RTS API is ready
this.isRtsAccessible = false;
}
@Override
public Mono<LayoutDTO> refactorWidgetName(RefactorNameDTO refactorNameDTO) {
final Map<String, String> analyticsProperties = new HashMap<>();
String pageId = refactorNameDTO.getPageId();
String layoutId = refactorNameDTO.getLayoutId();
String oldName = refactorNameDTO.getOldName();
String newName = refactorNameDTO.getNewName();
return layoutActionService.isNameAllowed(pageId, layoutId, newName)
.flatMap(allowed -> {
if (!allowed) {
.zipWith(newPageService.getById(pageId))
.flatMap(tuple -> {
analyticsProperties.put(FieldName.APPLICATION_ID, tuple.getT2().getApplicationId());
analyticsProperties.put(FieldName.PAGE_ID, pageId);
if (!tuple.getT1()) {
return Mono.error(new AppsmithException(AppsmithError.NAME_CLASH_NOT_ALLOWED_IN_REFACTOR, oldName, newName));
}
return this.refactorName(pageId, layoutId, oldName, newName);
return this.refactorName(pageId, layoutId, oldName, newName)
.flatMap(tuple2 -> this.sendRefactorAnalytics(AnalyticsEvents.REFACTOR_WIDGET.getEventName(), analyticsProperties, tuple2.getT2())
.thenReturn(tuple2.getT1()));
});
}
@ -139,6 +145,7 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
@Override
public Mono<LayoutDTO> refactorActionName(RefactorActionNameDTO refactorActionNameDTO) {
final Map<String, String> analyticsProperties = new HashMap<>();
String pageId = refactorActionNameDTO.getPageId();
String layoutId = refactorActionNameDTO.getLayoutId();
String oldName = refactorActionNameDTO.getOldName();
@ -165,13 +172,23 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
.findActionDTObyIdAndViewMode(actionId, false, actionPermission.getEditPermission());
})
.flatMap(action -> {
analyticsProperties.put(FieldName.APPLICATION_ID, action.getApplicationId());
analyticsProperties.put(FieldName.PAGE_ID, pageId);
action.setName(newName);
if (StringUtils.hasLength(refactorActionNameDTO.getCollectionName())) {
action.setFullyQualifiedName(newFullyQualifiedName);
}
return newActionService.updateUnpublishedAction(actionId, action);
})
.then(this.refactorName(pageId, layoutId, oldFullyQualifiedName, newFullyQualifiedName));
.then(this.refactorName(pageId, layoutId, oldFullyQualifiedName, newFullyQualifiedName)
.flatMap(tuple -> {
String eventName = AnalyticsEvents.REFACTOR_ACTION.getEventName();
if (StringUtils.hasLength(refactorActionNameDTO.getCollectionName())) {
eventName = AnalyticsEvents.REFACTOR_JSACTION.getEventName();
}
return this.sendRefactorAnalytics(eventName, analyticsProperties, tuple.getT2())
.thenReturn(tuple.getT1());
}));
}
@Override
@ -187,6 +204,16 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
.map(responseUtils::updateLayoutDTOWithDefaultResources);
}
@Override
public Mono<LayoutDTO> refactorActionCollectionName(String appId, String pageId, String layoutId, String oldName, String newName) {
final Map<String, String> analyticsProperties = new HashMap<>();
analyticsProperties.put(FieldName.APPLICATION_ID, appId);
analyticsProperties.put(FieldName.PAGE_ID, pageId);
return this.refactorName(pageId, layoutId, oldName, newName)
.flatMap(tuple -> this.sendRefactorAnalytics(AnalyticsEvents.REFACTOR_JSOBJECT.getEventName(), analyticsProperties, tuple.getT2())
.thenReturn(tuple.getT1()));
}
/**
* Assumption here is that the refactoring name provided is indeed unique and is fit to be replaced everywhere.
* <p>
@ -199,9 +226,10 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
* @return : The DSL after refactor updates
*/
@Override
public Mono<LayoutDTO> refactorName(String pageId, String layoutId, String oldName, String newName) {
public Mono<Tuple2<LayoutDTO, Set<String>>> refactorName(String pageId, String layoutId, String oldName, String newName) {
String regexPattern = preWord + oldName + postWord;
Pattern oldNamePattern = Pattern.compile(regexPattern);
final Set<String> updatedBindingPaths = new HashSet<>();
Mono<PageDTO> pageMono = newPageService
// fetch the unpublished page
@ -239,11 +267,12 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
final JsonNode dslNode = objectMapper.convertValue(layout.getDsl(), JsonNode.class);
Mono<PageDTO> refactorNameInDslMono = this.refactorNameInDsl(dslNode, oldName, newName, evalVersion, oldNamePattern)
.then(Mono.fromCallable(() -> {
.flatMap(dslBindingPaths -> {
updatedBindingPaths.addAll(dslBindingPaths);
layout.setDsl(objectMapper.convertValue(dslNode, JSONObject.class));
page.setLayouts(layouts);
return page;
}));
return Mono.just(page);
});
// Since the page has most probably changed, save the page and return.
return refactorNameInDslMono
@ -284,6 +313,7 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
if (updates.isEmpty()) {
return Mono.just(newAction);
}
updatedBindingPaths.addAll(updates);
if (StringUtils.hasLength(action.getCollectionId())) {
updatableCollectionIds.add(action.getCollectionId());
}
@ -305,29 +335,23 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
.flatMap(actionCollection -> {
final ActionCollectionDTO unpublishedCollection = actionCollection.getUnpublishedCollection();
Matcher matcher = actionCollectionBodyPattern.matcher(unpublishedCollection.getBody());
if (matcher.find()) {
String parsableBody = matcher.group(1);
return this.replaceValueInMustacheKeys(
new HashSet<>(Collections.singletonList(parsableBody)),
oldName,
newName,
evalVersion,
oldNamePattern)
.flatMap(replacedMap -> {
Optional<String> replacedValue = replacedMap.values().stream().findFirst();
// This value should always be there
if (replacedValue.isPresent()) {
final String replacedBody = EXPORT_DEFAULT_STRING + replacedValue.get();
unpublishedCollection.setBody(replacedBody);
return actionCollectionService.save(actionCollection);
}
return Mono.just(actionCollection);
});
} else {
// TODO make this error more informative, users should never edit JS objects to this state
return Mono.error(new AppsmithException(AppsmithError.INTERNAL_SERVER_ERROR));
}
return this.replaceValueInMustacheKeys(
new HashSet<>(Collections.singletonList(new MustacheBindingToken(unpublishedCollection.getBody(), 0, true))),
oldName,
newName,
evalVersion,
oldNamePattern,
true)
.flatMap(replacedMap -> {
Optional<String> replacedValue = replacedMap.values().stream().findFirst();
// This value should always be there
if (replacedValue.isPresent()) {
unpublishedCollection.setBody(replacedValue.get());
return actionCollectionService.save(actionCollection);
}
return Mono.just(actionCollection);
});
})
.collectList()
.thenReturn(updatedActions);
@ -342,7 +366,8 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
for (Layout layout : layouts) {
if (layoutId.equals(layout.getId())) {
layout.setDsl(layoutActionService.unescapeMongoSpecialCharacters(layout));
return layoutActionService.updateLayout(page.getId(), page.getApplicationId(), layout.getId(), layout);
return layoutActionService.updateLayout(page.getId(), page.getApplicationId(), layout.getId(), layout)
.zipWith(Mono.just(updatedBindingPaths));
}
}
return Mono.empty();
@ -489,7 +514,7 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
((ObjectNode) bindingPath).set(FieldName.KEY, new TextNode(key));
}
// Find values inside mustache bindings in this path
Set<String> mustacheValues = DslUtils.getMustacheValueSetFromSpecificDynamicBindingPath(widgetDsl, key);
Set<MustacheBindingToken> mustacheValues = DslUtils.getMustacheValueSetFromSpecificDynamicBindingPath(widgetDsl, key);
final String finalKey = key;
// Perform refactor for each mustache value
return this.replaceValueInMustacheKeys(mustacheValues, oldName, newName, evalVersion, oldNamePattern)
@ -514,7 +539,7 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
// If we're going the fallback route (without AST), we can first filter actions to be refactored
// By performing a check on whether json path keys had a reference
// This is not needed in the AST way since it would be costlier to make double the number of API calls
if (Boolean.FALSE.equals(this.isRtsAccessible)) {
if (Boolean.FALSE.equals(this.instanceConfig.getIsRtsAccessible())) {
Set<String> jsonPathKeys = actionDTO.getJsonPathKeys();
boolean isReferenceFound = false;
@ -546,9 +571,9 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
refactorDynamicBindingsMono = Flux.fromIterable(actionDTO.getDynamicBindingPathList())
.flatMap(dynamicBindingPath -> {
String key = dynamicBindingPath.getKey();
Set<String> mustacheValues = new HashSet<>();
Set<MustacheBindingToken> mustacheValues = new HashSet<>();
if (PluginType.JS.equals(actionDTO.getPluginType()) && "body".equals(key)) {
mustacheValues.add(actionConfiguration.getBody());
mustacheValues.add(new MustacheBindingToken(actionConfiguration.getBody(), 0, false));
} else {
mustacheValues = DslUtils.getMustacheValueSetFromSpecificDynamicBindingPath(actionConfigurationNode, key);
@ -573,19 +598,24 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
return refactorDynamicBindingsMono;
}
Mono<Map<String, String>> replaceValueInMustacheKeys(Set<String> mustacheKeySet, String oldName, String
Mono<Map<MustacheBindingToken, String>> replaceValueInMustacheKeys(Set<MustacheBindingToken> mustacheKeySet, String oldName, String
newName, int evalVersion, Pattern oldNamePattern) {
if (Boolean.TRUE.equals(this.isRtsAccessible)) {
return astService.refactorNameInDynamicBindings(mustacheKeySet, oldName, newName, evalVersion);
return this.replaceValueInMustacheKeys(mustacheKeySet, oldName, newName, evalVersion, oldNamePattern, false);
}
Mono<Map<MustacheBindingToken, String>> replaceValueInMustacheKeys(Set<MustacheBindingToken> mustacheKeySet, String oldName, String
newName, int evalVersion, Pattern oldNamePattern, boolean isJSObject) {
if (Boolean.TRUE.equals(this.instanceConfig.getIsRtsAccessible())) {
return astService.refactorNameInDynamicBindings(mustacheKeySet, oldName, newName, evalVersion, isJSObject);
}
return this.replaceValueInMustacheKeys(mustacheKeySet, oldNamePattern, newName);
}
Mono<Map<String, String>> replaceValueInMustacheKeys(Set<String> mustacheKeySet, Pattern
Mono<Map<MustacheBindingToken, String>> replaceValueInMustacheKeys(Set<MustacheBindingToken> mustacheKeySet, Pattern
oldNamePattern, String newName) {
return Flux.fromIterable(mustacheKeySet)
.flatMap(mustacheKey -> {
Matcher matcher = oldNamePattern.matcher(mustacheKey);
Matcher matcher = oldNamePattern.matcher(mustacheKey.getValue());
if (matcher.find()) {
return Mono.zip(Mono.just(mustacheKey), Mono.just(matcher.replaceAll(newName)));
}
@ -593,4 +623,16 @@ public class RefactoringSolutionCEImpl implements RefactoringSolutionCE {
})
.collectMap(Tuple2::getT1, Tuple2::getT2);
}
Mono<Void> sendRefactorAnalytics(String event, Map<String, String> properties, Set<String> updatedPaths) {
return sessionUserService.getCurrentUser()
.map(user -> {
final Map<String, String> analyticsProperties = new HashMap<>(properties);
analyticsProperties.put("updatedPaths", updatedPaths.toString());
analyticsProperties.put("userId", user.getUsername());
analyticsService.sendEvent(event, user.getUsername(), analyticsProperties);
return true;
})
.then();
}
}

View File

@ -0,0 +1,94 @@
package com.appsmith.server.helpers;
import com.appsmith.external.models.MustacheBindingToken;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Set;
class DslUtilsTest {
@Test
void getMustacheValueSetFromSpecificDynamicBindingPath_withNullOrEmptyDsl_returnsEmptySet() {
Set<MustacheBindingToken> tokensInNullDsl = DslUtils.getMustacheValueSetFromSpecificDynamicBindingPath(null, "irrelevantPath");
Set<MustacheBindingToken> tokensInEmptyDsl = DslUtils.getMustacheValueSetFromSpecificDynamicBindingPath(new TextNode(""), "irrelevantPath");
Assertions.assertThat(tokensInNullDsl).isEmpty();
Assertions.assertThat(tokensInEmptyDsl).isEmpty();
}
@Test
void getMustacheValueSetFromSpecificDynamicBindingPath_withComplicatedPathAndMultipleBindings_parsesDslCorrectly() throws JsonProcessingException {
String fieldPath = "root.field.list[0].childField.anotherList.0.multidimensionalList[0][0]";
String jsonString = "{ " +
"\"root\": { " +
" \"field\": { " +
" \"list\": [ " +
" { " +
" \"childField\": { " +
" \"anotherList\": [ " +
" { " +
" \"multidimensionalList\" : [ " +
" [\"{{ retrievedBinding1.text }} {{ retrievedBinding2.text }}\"]" +
" ] " +
" } " +
" ] " +
" } " +
" } " +
" ] " +
" } " +
" } " +
"}";
ObjectMapper mapper = new ObjectMapper();
JsonNode dsl = mapper.readTree(jsonString);
Set<MustacheBindingToken> tokens = DslUtils.getMustacheValueSetFromSpecificDynamicBindingPath(dsl, fieldPath);
Assertions.assertThat(tokens).containsExactlyInAnyOrder(
new MustacheBindingToken(" retrievedBinding1.text ", 2, false),
new MustacheBindingToken(" retrievedBinding2.text ", 31, false));
}
@Test
void replaceValuesInSpecificDynamicBindingPath_whenFieldPathNotFound() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode dsl = mapper.createObjectNode();
dsl.put("fieldKey", "fieldValue");
JsonNode replacedDsl = DslUtils.replaceValuesInSpecificDynamicBindingPath(dsl, "nonExistentPath", new HashMap<>());
Assertions.assertThat(replacedDsl).isEqualTo(dsl);
}
@Test
void replaceValuesInSpecificDynamicBindingPath_whenReplacementKeyNotFound() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode dsl = mapper.createObjectNode();
dsl.put("existingPath", "fieldValue");
HashMap<MustacheBindingToken, String> replacementMap = new HashMap<>();
replacementMap.put(new MustacheBindingToken("nonExistentBinding", 0, false), "newNonExistentBinding");
JsonNode replacedDsl = DslUtils.replaceValuesInSpecificDynamicBindingPath(dsl, "existingPath", replacementMap);
ObjectNode newDsl = mapper.createObjectNode();
newDsl.put("existingPath", "fieldValue");
Assertions.assertThat(replacedDsl).isEqualTo(newDsl);
}
@Test
void replaceValuesInSpecificDynamicBindingPath_withSuccessfulMultipleReplacements() {
ObjectMapper mapper = new ObjectMapper();
ObjectNode dsl = mapper.createObjectNode();
dsl.put("existingPath", "oldFieldValue1 oldFieldValue2");
HashMap<MustacheBindingToken, String> replacementMap = new HashMap<>();
replacementMap.put(new MustacheBindingToken("oldFieldValue1", 0, false), "newFieldValue1");
replacementMap.put(new MustacheBindingToken("oldFieldValue2", 15, false), "newFieldValue2");
JsonNode replacedDsl = DslUtils.replaceValuesInSpecificDynamicBindingPath(dsl, "existingPath", replacementMap);
ObjectNode newDsl = mapper.createObjectNode();
newDsl.put("existingPath", "newFieldValue1 newFieldValue2");
Assertions.assertThat(replacedDsl).isEqualTo(dsl);
}
}

View File

@ -280,7 +280,7 @@ public class ActionCollectionServiceImplTest {
Mockito
.when(layoutActionService.updatePageLayoutsByPageId(Mockito.anyString()))
.thenAnswer(invocationOnMock -> {
return Mono.just(actionCollectionDTO.getPageId());
return Mono.just(actionCollectionDTO.getPageId());
});
Mockito
@ -525,7 +525,7 @@ public class ActionCollectionServiceImplTest {
Mockito
.when(layoutActionService.updatePageLayoutsByPageId(Mockito.anyString()))
.thenAnswer(invocationOnMock -> {
return Mono.just(actionCollection.getUnpublishedCollection().getPageId());
return Mono.just(actionCollection.getUnpublishedCollection().getPageId());
});
@ -799,7 +799,7 @@ public class ActionCollectionServiceImplTest {
jsonObject.put("key", "value");
layout.setDsl(jsonObject);
Mockito
.when(refactoringSolution.refactorName(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
.when(refactoringSolution.refactorActionCollectionName(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(Mono.just(layout));
Mockito
@ -877,7 +877,7 @@ public class ActionCollectionServiceImplTest {
layout.setActionUpdates(new ArrayList<>());
layout.setLayoutOnLoadActions(new ArrayList<>());
Mockito
.when(refactoringSolution.refactorName(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()))
.when(refactoringSolution.refactorActionCollectionName(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(Mono.just(layout));
Mockito

View File

@ -937,7 +937,7 @@ public class LayoutServiceTest {
Mockito.when(astService.getPossibleReferencesFromDynamicBinding(List.of("\"anIgnoredAction.data:\" + aGetAction.data"), EVALUATION_VERSION))
.thenReturn(Flux.just(Tuples.of("\"anIgnoredAction.data:\" + aGetAction.data", new HashSet<>(Set.of("aGetAction.data")))));
String bindingValue = "(function(ignoredAction1){\n" +
String bindingValue = "\n(function(ignoredAction1){\n" +
"\tlet a = ignoredAction1.data\n" +
"\tlet ignoredAction2 = { data: \"nothing\" }\n" +
"\tlet b = ignoredAction2.data\n" +
@ -952,8 +952,8 @@ public class LayoutServiceTest {
.thenReturn(Flux.just(Tuples.of("aPostActionWithAutoExec.data", new HashSet<>(Set.of("aPostActionWithAutoExec.data")))));
Mockito.when(astService.getPossibleReferencesFromDynamicBinding(List.of("aDBAction.data[0].irrelevant"), EVALUATION_VERSION))
.thenReturn(Flux.just(Tuples.of("aDBAction.data[0].irrelevant", new HashSet<>(Set.of("aDBAction.data[0].irrelevant")))));
Mockito.when(astService.getPossibleReferencesFromDynamicBinding(List.of("anotherDBAction.data.optional"), EVALUATION_VERSION))
.thenReturn(Flux.just(Tuples.of("anotherDBAction.data.optional", new HashSet<>(Set.of("anotherDBAction.data.optional")))));
Mockito.when(astService.getPossibleReferencesFromDynamicBinding(List.of(" anotherDBAction.data.optional "), EVALUATION_VERSION))
.thenReturn(Flux.just(Tuples.of(" anotherDBAction.data.optional ", new HashSet<>(Set.of("anotherDBAction.data.optional")))));
Mockito.when(astService.getPossibleReferencesFromDynamicBinding(List.of("aTableAction.data.child"), EVALUATION_VERSION))
.thenReturn(Flux.just(Tuples.of("aTableAction.data.child", new HashSet<>(Set.of("aTableAction.data.child")))));
Mockito.when(astService.getPossibleReferencesFromDynamicBinding(List.of("Collection.anAsyncCollectionActionWithoutCall.data"), EVALUATION_VERSION))

View File

@ -3,11 +3,13 @@ package com.appsmith.server.solutions.ce;
import com.appsmith.server.configurations.InstanceConfig;
import com.appsmith.server.helpers.ResponseUtils;
import com.appsmith.server.services.ActionCollectionService;
import com.appsmith.server.services.AnalyticsService;
import com.appsmith.server.services.ApplicationService;
import com.appsmith.server.services.AstService;
import com.appsmith.server.services.LayoutActionService;
import com.appsmith.server.services.NewActionService;
import com.appsmith.server.services.NewPageService;
import com.appsmith.server.services.SessionUserService;
import com.appsmith.server.solutions.ActionPermission;
import com.appsmith.server.solutions.ActionPermissionImpl;
import com.appsmith.server.solutions.PagePermission;
@ -52,6 +54,10 @@ class RefactoringSolutionCEImplTest {
private AstService astService;
@MockBean
private InstanceConfig instanceConfig;
@MockBean
private AnalyticsService analyticsService;
@MockBean
private SessionUserService sessionUserService;
PagePermission pagePermission;
ActionPermission actionPermission;
@ -73,7 +79,9 @@ class RefactoringSolutionCEImplTest {
layoutActionService,
applicationService,
astService,
instanceConfig,
instanceConfig,
analyticsService,
sessionUserService,
pagePermission,
actionPermission);
}

View File

@ -41,6 +41,13 @@ interface IdentifierNode extends Node {
name: string;
}
//Using this to handle the Variable property refactor
interface RefactorIdentifierNode extends Node {
type: NodeTypes.Identifier;
name: string;
property?: IdentifierNode;
}
// doc: https://github.com/estree/estree/blob/master/es5.md#variabledeclarator
interface VariableDeclaratorNode extends Node {
type: NodeTypes.VariableDeclarator;
@ -270,50 +277,81 @@ export const entityRefactorFromCode = (
evaluationVersion: number,
invalidIdentifiers?: Record<string, unknown>
): EntityRefactorResponse => {
//Sanitizing leads to removal of special charater.
//Hence we are not sanatizing the script. Fix(#18492)
//If script is a JSObject then replace export default to decalartion.
if (isJSObject) script = jsObjectToCode(script);
let ast: Node = { end: 0, start: 0, type: "" };
//Copy of script to refactor
let refactorScript = script;
//Difference in length of oldName and newName
let nameLengthDiff: number = newName.length - oldName.length;
const nameLengthDiff: number = newName.length - oldName.length;
//Offset index used for deciding location of oldName.
let refactorOffset: number = 0;
//Count of refactors on the script
let refactorCount: number = 0;
try {
const sanitizedScript = sanitizeScript(script, evaluationVersion);
ast = getAST(sanitizedScript);
ast = getAST(script);
let {
references,
functionalParams,
variableDeclarations,
identifierList,
}: NodeList = ancestorWalk(ast);
let identifierArray = Array.from(identifierList) as Array<IdentifierNode>;
Array.from(references).forEach((reference, index) => {
const identifierArray = Array.from(
identifierList
) as Array<RefactorIdentifierNode>;
//To handle if oldName has property ("JSObject.myfunc")
const oldNameArr = oldName.split(".");
const referencesArr = Array.from(references).filter((reference) => {
// To remove references derived from declared variables and function params,
// We extract the topLevelIdentifier Eg. Api1.name => Api1
const topLevelIdentifier = toPath(reference)[0];
let shouldUpdateNode = !(
return !(
functionalParams.has(topLevelIdentifier) ||
variableDeclarations.has(topLevelIdentifier) ||
has(invalidIdentifiers, topLevelIdentifier)
);
//check if node should be updated
if (shouldUpdateNode && identifierArray[index].name === oldName) {
//Replace the oldName by newName
//Get start index from node and get subarray from index 0 till start
//Append above with new name
//Append substring from end index from the node till end of string
//Offset variable is used to alter the position based on `refactorOffset`
refactorScript =
refactorScript.substring(
0,
identifierArray[index].start + refactorOffset
) +
newName +
refactorScript.substring(identifierArray[index].end + refactorOffset);
refactorOffset += nameLengthDiff;
++refactorCount;
});
//Traverse through all identifiers in the script
identifierArray.forEach((identifier) => {
if (identifier.name === oldNameArr[0]) {
let index = 0;
while (index < referencesArr.length) {
if (identifier.name === referencesArr[index].split(".")[0]) {
//Replace the oldName by newName
//Get start index from node and get subarray from index 0 till start
//Append above with new name
//Append substring from end index from the node till end of string
//Offset variable is used to alter the position based on `refactorOffset`
//In case of nested JS action get end postion fro the property.
///Default end index
let endIndex = identifier.end;
const propertyNode = identifier.property;
//Flag variable : true if property should be updated
//false if property should not be updated
let propertyCondFlag =
oldNameArr.length > 1 &&
propertyNode &&
oldNameArr[1] === propertyNode.name;
//Condition to validate if Identifier || Property should be updated??
if (oldNameArr.length === 1 || propertyCondFlag) {
//Condition to extend end index in case of property match
if (propertyCondFlag && propertyNode) {
endIndex = propertyNode.end;
}
refactorScript =
refactorScript.substring(0, identifier.start + refactorOffset) +
newName +
refactorScript.substring(endIndex + refactorOffset);
refactorOffset += nameLengthDiff;
++refactorCount;
//We are only looking for one match in refrence for the identifier name.
break;
}
}
index++;
}
}
});
//If script is a JSObject then revert decalartion to export default.
@ -507,8 +545,8 @@ export const extractInvalidTopLevelMemberExpressionsFromCode = (
};
const ancestorWalk = (ast: Node): NodeList => {
//List of all Identifier nodes
const identifierList = new Array<IdentifierNode>();
//List of all Identifier nodes with their property(if exists).
const identifierList = new Array<RefactorIdentifierNode>();
// List of all references found
const references = new Set<string>();
// List of variables declared within the script. All identifiers and member expressions derived from declared variables will be removed
@ -554,7 +592,15 @@ const ancestorWalk = (ast: Node): NodeList => {
break;
}
}
identifierList.push(node as IdentifierNode);
//If parent is a Member expression then attach property to the Node.
//else push Identifier Node.
const parentNode = ancestors[ancestors.length - 2];
if (isMemberExpressionNode(parentNode)) {
identifierList.push({
...(node as IdentifierNode),
property: parentNode.property as IdentifierNode,
});
} else identifierList.push(node as RefactorIdentifierNode);
if (isIdentifierNode(candidateTopLevelNode)) {
// If the node is an Identifier, just save that
references.add(candidateTopLevelNode.name);