- {headerGroups.map((headerGroup: any, index: number) => (
-
- {headerGroup.headers.map((column: any, columnIndex: number) => (
-
-
- {column.render("Header")}
-
-
- ))}
-
- ))}
+
+ {headerGroups.map((headerGroup: any, index: number) => (
+
+ {headerGroup.headers.map(
+ (column: any, columnIndex: number) => (
+
+
+ {column.render("Header")}
+
+
+ ),
+ )}
+
+ ))}
+
{
isLoading,
} = props;
+ // set state for checking number of users invited
+ const [numberOfUsersInvited, updateNumberOfUsersInvited] = useState(0);
const currentOrg = useSelector(getCurrentAppOrg);
const userOrgPermissions = currentOrg?.userPermissions ?? [];
@@ -275,6 +278,9 @@ const OrgInviteUsersForm = (props: any) => {
onSubmit={handleSubmit((values: any, dispatch: any) => {
validateFormValues(values);
AnalyticsUtil.logEvent("INVITE_USER", values);
+ const usersAsStringsArray = values.users.split(",");
+ // update state to show success message correctly
+ updateNumberOfUsersInvited(usersAsStringsArray.length);
return inviteUsersToOrg({ ...values, orgId: props.orgId }, dispatch);
})}
>
@@ -362,7 +368,11 @@ const OrgInviteUsersForm = (props: any) => {
1
+ ? INVITE_USERS_SUBMIT_SUCCESS()
+ : INVITE_USER_SUBMIT_SUCCESS()
+ }
/>
)}
{((submitFailed && error) || emailError) && (
diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx
index 26578af83f..fa37f695cd 100644
--- a/app/client/src/sagas/PageSagas.tsx
+++ b/app/client/src/sagas/PageSagas.tsx
@@ -306,7 +306,6 @@ function* savePageSaga(action: ReduxAction<{ isRetry?: boolean }>) {
pageId: savePageRequest.pageId,
},
);
- AnalyticsUtil.logEvent("PAGE_SAVE", JSON.stringify(savePageRequest));
try {
// Store the updated DSL in the pageDSLs reducer
yield put({
diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/AuthenticationResponse.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/AuthenticationResponse.java
index d5582a981c..a35d0392bb 100644
--- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/AuthenticationResponse.java
+++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/AuthenticationResponse.java
@@ -1,13 +1,16 @@
package com.appsmith.external.models;
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
import lombok.Getter;
+import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
+@NoArgsConstructor
+@AllArgsConstructor
public class AuthenticationResponse {
String token;
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AnalyticsEvents.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AnalyticsEvents.java
index 62454b4ceb..a22833b88d 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AnalyticsEvents.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/AnalyticsEvents.java
@@ -8,6 +8,7 @@ public enum AnalyticsEvents {
DELETE,
FIRST_LOGIN,
EXECUTE_ACTION("execute_ACTION_TRIGGERED"),
+ UPDATE_LAYOUT,
;
private final String eventName;
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java
index 2a2e6c8e0c..5e46dc1d5f 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java
@@ -37,7 +37,8 @@ public enum AppsmithError {
" \"widgetName\" : \"{1}\"," +
" \"widgetId\" : \"{2}\"," +
" \"pageId\" : \"{4}\"," +
- " \"layoutId\" : \"{5}\"",
+ " \"layoutId\" : \"{5}\"," +
+ " \"dynamicBinding\" : \"{6}\"",
AppsmithErrorAction.LOG_EXTERNALLY),
USER_ALREADY_EXISTS_IN_ORGANIZATION(400, 4021, "The user {0} has already been added to the organization with role {1}. To change the role, please navigate to `Manage Users` page.", AppsmithErrorAction.DEFAULT),
UNAUTHORIZED_DOMAIN(401, 4019, "Invalid email domain {0} used for sign in/sign up. Please contact the administrator to configure this domain if this is unexpected.", AppsmithErrorAction.DEFAULT),
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java
index 5a64884605..f01231fc4a 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/LayoutActionServiceImpl.java
@@ -2,9 +2,12 @@ package com.appsmith.server.services;
import com.appsmith.external.helpers.MustacheHelper;
import com.appsmith.external.models.ActionConfiguration;
+import com.appsmith.server.constants.AnalyticsEvents;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.ActionDependencyEdge;
import com.appsmith.server.domains.Layout;
+import com.appsmith.server.domains.NewPage;
+import com.appsmith.server.domains.User;
import com.appsmith.server.dtos.ActionDTO;
import com.appsmith.server.dtos.ActionMoveDTO;
import com.appsmith.server.dtos.DslActionDTO;
@@ -31,6 +34,7 @@ import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -53,6 +57,8 @@ public class LayoutActionServiceImpl implements LayoutActionService {
private final NewPageService newPageService;
private final NewActionService newActionService;
private final PageLoadActionsUtil pageLoadActionsUtil;
+ private final SessionUserService sessionUserService;
+
/*
* To replace fetchUsers in `{{JSON.stringify(fetchUsers)}}` with getUsers, the following regex is required :
@@ -65,12 +71,15 @@ public class LayoutActionServiceImpl implements LayoutActionService {
public LayoutActionServiceImpl(ObjectMapper objectMapper,
AnalyticsService analyticsService,
NewPageService newPageService,
- NewActionService newActionService, PageLoadActionsUtil pageLoadActionsUtil) {
+ NewActionService newActionService,
+ PageLoadActionsUtil pageLoadActionsUtil,
+ SessionUserService sessionUserService) {
this.objectMapper = objectMapper;
this.analyticsService = analyticsService;
this.newPageService = newPageService;
this.newActionService = newActionService;
this.pageLoadActionsUtil = pageLoadActionsUtil;
+ this.sessionUserService = sessionUserService;
}
@Override
@@ -310,7 +319,7 @@ public class LayoutActionServiceImpl implements LayoutActionService {
// For nested fields, the parent dsl to search in would shift by one level every iteration
Object parent = dsl;
Iterator fieldsIterator = Arrays.stream(fields).filter(fieldToken -> !fieldToken.isBlank()).iterator();
- Boolean isLeafNode = false;
+ boolean isLeafNode = false;
// This loop will end at either a leaf node, or the last identified JSON field (by throwing an exception)
// Valid forms of the fieldPath for this search could be:
// root.field.list[index].childField.anotherList.indexWithDotOperator.multidimensionalList[index1][index2]
@@ -327,30 +336,30 @@ public class LayoutActionServiceImpl implements LayoutActionService {
} catch (IndexOutOfBoundsException e) {
// The index being referred does not exist. Hence the path would not exist.
throw new AppsmithException(AppsmithError.INVALID_DYNAMIC_BINDING_REFERENCE, widgetType,
- widgetName, widgetId, fieldPath, pageId, layoutId);
+ widgetName, widgetId, fieldPath, pageId, layoutId, null);
}
} else {
throw new AppsmithException(AppsmithError.INVALID_DYNAMIC_BINDING_REFERENCE, widgetType,
- widgetName, widgetId, fieldPath, pageId, layoutId);
+ widgetName, widgetId, fieldPath, pageId, layoutId, null);
}
}
// After updating the parent, check for the types
if (parent == null) {
throw new AppsmithException(AppsmithError.INVALID_DYNAMIC_BINDING_REFERENCE, widgetType,
- widgetName, widgetId, fieldPath, pageId, layoutId);
+ widgetName, widgetId, fieldPath, pageId, layoutId, null);
} else if (parent instanceof String) {
// If we get String value, then this is a leaf node
isLeafNode = true;
}
}
// Only extract mustache keys from leaf nodes
- if (parent != null && isLeafNode) {
+ if (isLeafNode) {
Set mustacheKeysFromFields = MustacheHelper.extractMustacheKeysFromFields(parent);
// We found the path. But if the path does not have any mustache bindings, throw the error
if (mustacheKeysFromFields.isEmpty()) {
throw new AppsmithException(AppsmithError.INVALID_DYNAMIC_BINDING_REFERENCE, widgetType,
- widgetName, widgetId, fieldPath, pageId, layoutId);
+ widgetName, widgetId, fieldPath, pageId, layoutId, parent);
}
dynamicBindings.addAll(mustacheKeysFromFields);
@@ -499,9 +508,38 @@ public class LayoutActionServiceImpl implements LayoutActionService {
.then(Mono.just(pageId));
}
+ private Mono sendUpdateLayoutAnalyticsEvent(String pageId, String layoutId, JSONObject dsl, boolean isSuccess, Throwable error) {
+ return Mono.zip(
+ sessionUserService.getCurrentUser(),
+ newPageService.getById(pageId)
+ )
+ .flatMap(tuple -> {
+ User t1 = tuple.getT1();
+ NewPage t2 = tuple.getT2();
+
+ final Map data = Map.of(
+ "username", t1.getUsername(),
+ "appId", t2.getApplicationId(),
+ "pageId", pageId,
+ "layoutId", layoutId,
+ "dsl", dsl.toJSONString(),
+ "isSuccessfulExecution", isSuccess,
+ "error", error == null ? "" : error.getMessage()
+ );
+
+ analyticsService.sendEvent(AnalyticsEvents.UPDATE_LAYOUT.getEventName(), t1.getUsername(), data);
+ return Mono.just(isSuccess);
+ })
+ .onErrorResume(e -> {
+ log.warn("Error sending action execution data point", e);
+ return Mono.just(isSuccess);
+ });
+
+ }
+
@Override
public Mono updateLayout(String pageId, String layoutId, Layout layout) {
- JSONObject dsl = layout.getDsl();
+ final JSONObject dsl = layout.getDsl();
if (dsl == null) {
// There is no DSL here. No need to process anything. Return as is.
return Mono.just(generateResponseDTO(layout));
@@ -512,7 +550,8 @@ public class LayoutActionServiceImpl implements LayoutActionService {
try {
extractAllWidgetNamesAndDynamicBindingsFromDSL(dsl, widgetNames, jsSnippetsInDynamicBindings, pageId, layoutId);
} catch (Throwable t) {
- return Mono.error(t);
+ return sendUpdateLayoutAnalyticsEvent(pageId, layoutId, dsl, false, t)
+ .then(Mono.error(t));
}
layout.setWidgetNames(widgetNames);
@@ -581,11 +620,13 @@ public class LayoutActionServiceImpl implements LayoutActionService {
}
return Mono.empty();
})
- .map(savedLayout -> {
+ .flatMap(savedLayout -> {
LayoutDTO layoutDTO = generateResponseDTO(savedLayout);
layoutDTO.setActionUpdates(actionUpdates);
layoutDTO.setMessages(messages);
- return layoutDTO;
+
+ return sendUpdateLayoutAnalyticsEvent(pageId, layoutId, dsl, true, null)
+ .thenReturn(layoutDTO);
});
}
diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java
index c41a34fdd3..5eac88d0fe 100644
--- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java
+++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ExamplesOrganizationCloner.java
@@ -1,5 +1,6 @@
package com.appsmith.server.solutions;
+import com.appsmith.external.models.AuthenticationDTO;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.Application;
@@ -361,7 +362,23 @@ public class ExamplesOrganizationCloner {
final Datasource templateDatasource = tuple.getT1();
final List existingDatasources = tuple.getT2();
+ final AuthenticationDTO authentication = templateDatasource.getDatasourceConfiguration() == null
+ ? null : templateDatasource.getDatasourceConfiguration().getAuthentication();
+ if (authentication != null) {
+ authentication.setIsAuthorized(null);
+ authentication.setAuthenticationResponse(null);
+ }
+
return Flux.fromIterable(existingDatasources)
+ .map(ds -> {
+ final AuthenticationDTO auth = ds.getDatasourceConfiguration() == null
+ ? null : ds.getDatasourceConfiguration().getAuthentication();
+ if (auth != null) {
+ auth.setIsAuthorized(null);
+ auth.setAuthenticationResponse(null);
+ }
+ return ds;
+ })
.filter(templateDatasource::softEquals)
.next() // Get the first matching datasource, we don't need more than one here.
.switchIfEmpty(Mono.defer(() -> {
@@ -369,8 +386,8 @@ public class ExamplesOrganizationCloner {
makePristine(templateDatasource);
templateDatasource.setOrganizationId(toOrganizationId);
- if (templateDatasource.getDatasourceConfiguration() != null) {
- datasourceContextService.decryptSensitiveFields(templateDatasource.getDatasourceConfiguration().getAuthentication());
+ if (authentication != null) {
+ datasourceContextService.decryptSensitiveFields(authentication);
}
return createSuffixedDatasource(templateDatasource);
diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java
index ebbe67750e..16f9ef7d2e 100644
--- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java
+++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ExamplesOrganizationClonerTests.java
@@ -1,6 +1,7 @@
package com.appsmith.server.solutions;
import com.appsmith.external.models.ActionConfiguration;
+import com.appsmith.external.models.AuthenticationResponse;
import com.appsmith.external.models.Connection;
import com.appsmith.external.models.DBAuth;
import com.appsmith.external.models.DatasourceConfiguration;
@@ -59,6 +60,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -762,6 +764,8 @@ public class ExamplesOrganizationClonerTests {
new Property("custom auth param 1", "custom auth param value 1"),
new Property("custom auth param 2", "custom auth param value 2")
));
+ auth.setIsAuthorized(true);
+ auth.setAuthenticationResponse(new AuthenticationResponse("token", "refreshToken", Instant.now(), Instant.now(), null));
dc.setAuthentication(auth);
final Datasource ds2 = new Datasource();
@@ -898,6 +902,14 @@ public class ExamplesOrganizationClonerTests {
"datasource 2"
);
+ final Datasource ds1 = data.datasources.stream().filter(ds -> ds.getName().equals("datasource 1")).findFirst().get();
+ assertThat(ds1.getDatasourceConfiguration().getAuthentication().getIsAuthorized()).isNull();
+ assertThat(ds1.getDatasourceConfiguration().getAuthentication().getAuthenticationResponse()).isNull();
+
+ final Datasource ds2 = data.datasources.stream().filter(ds -> ds.getName().equals("datasource 2")).findFirst().get();
+ assertThat(ds2.getDatasourceConfiguration().getAuthentication().getIsAuthorized()).isNull();
+ assertThat(ds2.getDatasourceConfiguration().getAuthentication().getAuthenticationResponse()).isNull();
+
assertThat(getUnpublishedActionName(data.actions)).containsExactlyInAnyOrder(
"action1",
"action2",