diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java index db791faf53..6d1226b32f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/AuthenticationSuccessHandler.java @@ -60,7 +60,7 @@ public class AuthenticationSuccessHandler implements ServerAuthenticationSuccess .flatMap(user -> { final boolean isFromInvite = user.getInviteToken() != null; return Mono.whenDelayError( - analyticsService.sendEvent(AnalyticsEvents.FIRST_LOGIN, user, Map.of("isFromInvite", isFromInvite)), + analyticsService.sendObjectEvent(AnalyticsEvents.FIRST_LOGIN, user, Map.of("isFromInvite", isFromInvite)), examplesOrganizationCloner.cloneExamplesOrganization() ); }) 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 8e9668b0e7..62454b4ceb 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 @@ -1,13 +1,26 @@ package com.appsmith.server.constants; +import java.util.Locale; + public enum AnalyticsEvents { CREATE, UPDATE, DELETE, FIRST_LOGIN, + EXECUTE_ACTION("execute_ACTION_TRIGGERED"), ; - public String lowerName() { - return name().toLowerCase(); + private final String eventName; + + AnalyticsEvents() { + this.eventName = name().toLowerCase(Locale.ROOT); + } + + AnalyticsEvents(String eventName) { + this.eventName = eventName; + } + + public String getEventName() { + return eventName; } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java index 173e6b16b0..90b58d8ce6 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsService.java @@ -9,6 +9,7 @@ import com.segment.analytics.messages.TrackMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import reactor.core.publisher.Mono; import java.util.HashMap; @@ -27,8 +28,12 @@ public class AnalyticsService { this.sessionUserService = sessionUserService; } + public boolean isActive() { + return analytics != null; + } + public Mono trackNewUser(User user) { - if (analytics == null) { + if (!isActive()) { return Mono.just(user); } @@ -51,16 +56,30 @@ public class AnalyticsService { }); } - public Mono sendEvent(AnalyticsEvents event, T object) { - return sendEvent(event, object, null); + public void sendEvent(String event, String userId) { + sendEvent(event, userId, null); } - public Mono sendEvent(AnalyticsEvents event, T object, Map extraProperties) { - if (analytics == null) { + public void sendEvent(String event, String userId, Map properties) { + if (!isActive()) { + return; + } + + TrackMessage.Builder messageBuilder = TrackMessage.builder(event).userId(userId); + + if (!CollectionUtils.isEmpty(properties)) { + messageBuilder = messageBuilder.properties(properties); + } + + analytics.enqueue(messageBuilder); + } + + public Mono sendObjectEvent(AnalyticsEvents event, T object, Map extraProperties) { + if (!isActive()) { return Mono.just(object); } - final String eventTag = event.lowerName() + "_" + object.getClass().getSimpleName().toUpperCase(); + final String eventTag = event.getEventName() + "_" + object.getClass().getSimpleName().toUpperCase(); // We will create an anonymous user object for event tracking if no user is present // Without this, a lot of flows meant for anonymous users will error out @@ -84,18 +103,13 @@ public class AnalyticsService { analyticsProperties.putAll(extraProperties); } - analytics.enqueue( - TrackMessage.builder(eventTag) - .userId(username) - .properties(analyticsProperties) - ); - + sendEvent(eventTag, username, analyticsProperties); return object; }); } public Mono sendCreateEvent(T object, Map extraProperties) { - return sendEvent(AnalyticsEvents.CREATE, object, extraProperties); + return sendObjectEvent(AnalyticsEvents.CREATE, object, extraProperties); } public Mono sendCreateEvent(T object) { @@ -103,7 +117,7 @@ public class AnalyticsService { } public Mono sendUpdateEvent(T object, Map extraProperties) { - return sendEvent(AnalyticsEvents.UPDATE, object, extraProperties); + return sendObjectEvent(AnalyticsEvents.UPDATE, object, extraProperties); } public Mono sendUpdateEvent(T object) { @@ -111,10 +125,11 @@ public class AnalyticsService { } public Mono sendDeleteEvent(T object, Map extraProperties) { - return sendEvent(AnalyticsEvents.DELETE, object, extraProperties); + return sendObjectEvent(AnalyticsEvents.DELETE, object, extraProperties); } public Mono sendDeleteEvent(T object) { return sendDeleteEvent(object, null); } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java index 053990150c..c309e5b969 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java @@ -15,15 +15,18 @@ import com.appsmith.external.pluginExceptions.StaleConnectionException; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.PolicyGenerator; +import com.appsmith.server.constants.AnalyticsEvents; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.ActionProvider; +import com.appsmith.server.domains.Application; import com.appsmith.server.domains.Datasource; 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.server.domains.PluginType; +import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ActionDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ExecuteActionDTO; @@ -82,6 +85,8 @@ public class NewActionServiceImpl extends BaseService updatedActionMono = repository.findById(id, MANAGE_ACTIONS) @@ -456,8 +465,11 @@ public class NewActionServiceImpl extends BaseService actionMono = repository.findById(actionId, EXECUTE_ACTIONS) + Mono actionMono = repository.findById(actionId, EXECUTE_ACTIONS) .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.ACTION, actionId))) + .cache(); + + Mono actionDTOMono = actionMono .flatMap(dbAction -> { ActionDTO action; if (TRUE.equals(executeActionDTO.getViewMode())) { @@ -491,7 +503,7 @@ public class NewActionServiceImpl extends BaseService datasourceMono = actionMono + Mono datasourceMono = actionDTOMono .flatMap(action -> { // Global datasource requires us to fetch the datasource from DB. if (action.getDatasource() != null && action.getDatasource().getId() != null) { @@ -533,7 +545,7 @@ public class NewActionServiceImpl extends BaseService actionExecutionResultMono = Mono .zip( - actionMono, + actionDTOMono, datasourceMono, pluginExecutorMono ) @@ -568,7 +580,9 @@ public class NewActionServiceImpl extends BaseService getAnalyticsMono(tuple1.getT1(), tuple1.getT2(), executeActionDTO)) + .then(executionMono) .onErrorResume(StaleConnectionException.class, error -> { log.info("Looks like the connection is stale. Retrying with a fresh context."); return datasourceContextService @@ -625,6 +639,38 @@ public class NewActionServiceImpl extends BaseService getAnalyticsMono(NewAction action, ActionDTO actionDTO, ExecuteActionDTO executeActionDTO) { + // Since we're loading the application from DB *only* for analytics, we check if analytics is + // active before making the call to DB. + if (!analyticsService.isActive()) { + return Mono.empty(); + } + + return Mono.justOrEmpty(action.getApplicationId()) + .flatMap(applicationService::findById) + .defaultIfEmpty(new Application()) + .zipWith(sessionUserService.getCurrentUser()) + .map(tuple -> { + final Application application = tuple.getT1(); + final User user = tuple.getT2(); + analyticsService.sendEvent( + AnalyticsEvents.EXECUTE_ACTION.getEventName(), + user.getUsername(), + Map.of( + "type", action.getPluginType(), + "name", actionDTO.getName(), + "pageId", actionDTO.getPageId(), + "appId", action.getApplicationId(), + "appMode", Boolean.TRUE.equals(executeActionDTO.getViewMode()) ? "view" : "edit", + "appName", application.getName(), + "isExampleApp", application.isAppIsExample() + ) + ); + return user; + }) + .then(); + } + private void prepareConfigurationsForExecution(ActionDTO action, Datasource datasource, ExecuteActionDTO executeActionDTO,