chore: Add version to analytics events (#20295)

Currently, analytics events don't include Appsmith version information.
This PR adds version, and edition information to all events.
This commit is contained in:
Shrikant Sharat Kandula 2023-02-03 10:45:39 +05:30 committed by GitHub
parent ad5d236a05
commit 04f6208f86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 30 deletions

View File

@ -16,6 +16,8 @@ import org.springframework.context.annotation.PropertySource;
@Getter
public class ProjectProperties {
public static final String EDITION = "CE";
@Value("${version:UNKNOWN}")
private String version;

View File

@ -15,4 +15,7 @@ public interface CustomUserDataRepositoryCE extends AppsmithRepository<UserData>
Mono<UpdateResult> removeIdFromRecentlyUsedList(String userId, String workspaceId, List<String> applicationIds);
Flux<UserData> findPhotoAssetsByUserIds(Iterable<String> userId);
Mono<String> fetchMostRecentlyUsedWorkspaceId(String userId);
}

View File

@ -9,6 +9,7 @@ import com.mongodb.client.result.UpdateResult;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
@ -66,4 +67,17 @@ public class CustomUserDataRepositoryCEImpl extends BaseAppsmithRepositoryImpl<U
return queryAll(List.of(criteria), Optional.of(fieldsToInclude), Optional.empty(), Optional.empty());
}
@Override
public Mono<String> fetchMostRecentlyUsedWorkspaceId(String userId) {
final Query query = query(where(fieldName(QUserData.userData.userId)).is(userId));
query.fields().include(fieldName(QUserData.userData.recentlyUsedWorkspaceIds));
return mongoOperations.findOne(query, UserData.class)
.map(userData -> {
final List<String> recentlyUsedWorkspaceIds = userData.getRecentlyUsedWorkspaceIds();
return CollectionUtils.isEmpty(recentlyUsedWorkspaceIds) ? "" : recentlyUsedWorkspaceIds.get(0);
});
}
}

View File

@ -1,8 +1,10 @@
package com.appsmith.server.services;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.ProjectProperties;
import com.appsmith.server.helpers.PolicyUtils;
import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.repositories.UserDataRepository;
import com.appsmith.server.services.ce.AnalyticsServiceCEImpl;
import com.google.gson.Gson;
import com.segment.analytics.Analytics;
@ -19,12 +21,10 @@ public class AnalyticsServiceImpl extends AnalyticsServiceCEImpl implements Anal
SessionUserService sessionUserService,
CommonConfig commonConfig,
ConfigService configService,
PolicyUtils policyUtils,
UserUtils userUtils,
Gson gson) {
super(analytics, sessionUserService, commonConfig, configService, policyUtils, userUtils, gson);
ProjectProperties projectProperties,
UserDataRepository userDataRepository) {
super(analytics, sessionUserService, commonConfig, configService, userUtils, projectProperties, userDataRepository);
}
}

View File

@ -14,6 +14,8 @@ public interface AnalyticsServiceCE {
Mono<User> identifyUser(User user, UserData userData);
Mono<User> identifyUser(User user, UserData userData, String recentlyUsedWorkspaceId);
void identifyInstance(String instanceId, String role, String useCase);
Mono<Void> sendEvent(String event, String userId, Map<String, ?> properties);

View File

@ -3,17 +3,17 @@ package com.appsmith.server.services.ce;
import com.appsmith.external.constants.AnalyticsEvents;
import com.appsmith.external.models.BaseDomain;
import com.appsmith.server.configurations.CommonConfig;
import com.appsmith.server.configurations.ProjectProperties;
import com.appsmith.server.constants.FieldName;
import com.appsmith.server.domains.NewAction;
import com.appsmith.server.domains.NewPage;
import com.appsmith.server.domains.User;
import com.appsmith.server.domains.UserData;
import com.appsmith.server.helpers.ExchangeUtils;
import com.appsmith.server.helpers.PolicyUtils;
import com.appsmith.server.helpers.UserUtils;
import com.appsmith.server.repositories.UserDataRepository;
import com.appsmith.server.services.ConfigService;
import com.appsmith.server.services.SessionUserService;
import com.google.gson.Gson;
import com.segment.analytics.Analytics;
import com.segment.analytics.messages.IdentifyMessage;
import com.segment.analytics.messages.TrackMessage;
@ -39,22 +39,25 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
private final UserUtils userUtils;
private final Gson gson;
private final ProjectProperties projectProperties;
private final UserDataRepository userDataRepository;
@Autowired
public AnalyticsServiceCEImpl(@Autowired(required = false) Analytics analytics,
SessionUserService sessionUserService,
CommonConfig commonConfig,
ConfigService configService,
PolicyUtils policyUtils,
UserUtils userUtils,
Gson gson) {
ProjectProperties projectProperties,
UserDataRepository userDataRepository) {
this.analytics = analytics;
this.sessionUserService = sessionUserService;
this.commonConfig = commonConfig;
this.configService = configService;
this.userUtils = userUtils;
this.gson = gson;
this.projectProperties = projectProperties;
this.userDataRepository = userDataRepository;
}
public boolean isActive() {
@ -65,18 +68,34 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
return value == null ? "" : DigestUtils.sha256Hex(value);
}
@Override
public Mono<User> identifyUser(User user, UserData userData) {
return identifyUser(user, userData, null);
}
@Override
public Mono<User> identifyUser(User user, UserData userData, String recentlyUsedWorkspaceId) {
if (!isActive()) {
return Mono.just(user);
}
Mono<Boolean> isSuperUserMono = userUtils.isSuperUser(user);
return Mono.just(user)
.zipWith(isSuperUserMono)
final Mono<String> recentlyUsedWorkspaceIdMono = StringUtils.isEmpty(recentlyUsedWorkspaceId)
? userDataRepository.fetchMostRecentlyUsedWorkspaceId(user.getId()).defaultIfEmpty("")
: Mono.just(recentlyUsedWorkspaceId);
return Mono.zip(
Mono.just(user),
isSuperUserMono,
configService.getInstanceId()
.defaultIfEmpty("unknown-instance-id"),
recentlyUsedWorkspaceIdMono
)
.map(tuple -> {
User savedUser = tuple.getT1();
final Boolean isSuperUser = tuple.getT2();
final User savedUser = tuple.getT1();
final boolean isSuperUser = tuple.getT2();
final String instanceId = tuple.getT3();
String username = savedUser.getUsername();
String name = savedUser.getName();
@ -92,7 +111,9 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
.traits(Map.of(
"name", ObjectUtils.defaultIfNull(name, ""),
"email", ObjectUtils.defaultIfNull(email, ""),
"isSuperUser", isSuperUser != null && isSuperUser,
"isSuperUser", isSuperUser,
"instanceId", instanceId,
"mostRecentlyUsedWorkspaceId", tuple.getT4(),
"role", ObjectUtils.defaultIfNull(userData.getRole(), ""),
"goal", ObjectUtils.defaultIfNull(userData.getUseCase(), "")
))
@ -174,6 +195,7 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
TrackMessage.Builder messageBuilder = TrackMessage.builder(event).userId(userIdToSend);
analyticsProperties.put("originService", "appsmith-server");
analyticsProperties.put("instanceId", instanceId);
analyticsProperties.put("version", projectProperties.getVersion());
messageBuilder = messageBuilder.properties(analyticsProperties);
analytics.enqueue(messageBuilder);
return instanceId;
@ -226,7 +248,7 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE {
return Mono.just(object);
}
final String username = (object instanceof User ? (User) object : user).getUsername();
final String username = (object instanceof User objectAsUser ? objectAsUser : user).getUsername();
HashMap<String, Object> analyticsProperties = new HashMap<>();
analyticsProperties.put("id", id);

View File

@ -50,5 +50,4 @@ public interface UserDataServiceCE {
Mono<UserData> setCommentState(CommentOnboardingState commentOnboardingState);
Mono<UpdateResult> removeRecentWorkspaceAndApps(String userId, String workspaceId);
}

View File

@ -22,6 +22,7 @@ import com.appsmith.server.solutions.ReleaseNotesService;
import com.appsmith.server.solutions.UserChangedHandler;
import com.mongodb.DBObject;
import com.mongodb.client.result.UpdateResult;
import org.apache.commons.collections.map.Flat3Map;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
@ -36,6 +37,8 @@ import reactor.core.publisher.Mono;
import reactor.core.scheduler.Scheduler;
import jakarta.validation.Validator;
import reactor.util.function.Tuple2;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -261,17 +264,25 @@ public class UserDataServiceCEImpl extends BaseService<UserDataRepository, UserD
*/
@Override
public Mono<UserData> updateLastUsedAppAndWorkspaceList(Application application) {
return this.getForCurrentUser().flatMap(userData -> {
// set recently used workspace ids
userData.setRecentlyUsedWorkspaceIds(
addIdToRecentList(userData.getRecentlyUsedWorkspaceIds(), application.getWorkspaceId(), 10)
);
// set recently used application ids
userData.setRecentlyUsedAppIds(
addIdToRecentList(userData.getRecentlyUsedAppIds(), application.getId(), 20)
);
return repository.save(userData);
});
return sessionUserService.getCurrentUser()
.zipWhen(this::getForUser)
.flatMap(tuple -> {
final User user = tuple.getT1();
final UserData userData = tuple.getT2();
// set recently used workspace ids
userData.setRecentlyUsedWorkspaceIds(
addIdToRecentList(userData.getRecentlyUsedWorkspaceIds(), application.getWorkspaceId(), 10)
);
// set recently used application ids
userData.setRecentlyUsedAppIds(
addIdToRecentList(userData.getRecentlyUsedAppIds(), application.getId(), 20)
);
return Mono.zip(
analyticsService.identifyUser(user, userData, application.getWorkspaceId()),
repository.save(userData)
);
})
.map(Tuple2::getT2);
}
@Override
@ -330,4 +341,5 @@ public class UserDataServiceCEImpl extends BaseService<UserDataRepository, UserD
repository.removeIdFromRecentlyUsedList(userId, workspaceId, appIdsList)
);
}
}

View File

@ -15,9 +15,12 @@ import org.springframework.core.ParameterizedTypeReference;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriBuilder;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
@ -64,7 +67,9 @@ public class ReleaseNotesServiceCEImpl implements ReleaseNotesServiceCE {
// isCloudHosted should be true only for our cloud instance,
// For docker images that burn the segment key with the image, the CE key will be present
"&isSourceInstall=" + (commonConfig.isCloudHosting() || StringUtils.isEmpty(segmentConfig.getCeKey())) +
(StringUtils.isEmpty(commonConfig.getRepo()) ? "" : ("&repo=" + commonConfig.getRepo()))
(StringUtils.isEmpty(commonConfig.getRepo()) ? "" : ("&repo=" + commonConfig.getRepo())) +
"&version=" + projectProperties.getVersion() +
"&edition=" + ProjectProperties.EDITION
)
.get()
.exchange()