-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[JN-1600] customer-specific mixpanel export #1443
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package bio.terra.pearl.core.service.logging; | ||
|
||
import bio.terra.pearl.core.model.portal.PortalEnvironmentConfig; | ||
import bio.terra.pearl.core.service.portal.PortalEnvironmentConfigService; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.cache.annotation.CacheEvict; | ||
import org.springframework.cache.annotation.Cacheable; | ||
import org.springframework.scheduling.annotation.Scheduled; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* we don't want to have to go to the database every time an event is logged in our system to get the configs, | ||
* and they change very rarely. So we cache them here | ||
*/ | ||
@Service | ||
@Slf4j | ||
public class LoggingConfigCache { | ||
private final PortalEnvironmentConfigService portalEnvironmentConfigService; | ||
|
||
public static final String CONFIGS_WITH_DOMAIN_CACHE_KEY = "portalEnvironmentConfigsWithDomain"; | ||
|
||
public LoggingConfigCache(PortalEnvironmentConfigService portalEnvironmentConfigService) { | ||
this.portalEnvironmentConfigService = portalEnvironmentConfigService; | ||
} | ||
|
||
@Cacheable(value = CONFIGS_WITH_DOMAIN_CACHE_KEY) | ||
public Map<String, PortalEnvironmentConfig> getConfigsWithDomain() { | ||
return portalEnvironmentConfigService.findAllMappedByCustomDomain(); | ||
} | ||
|
||
/** since we run multiple instances of the app, we can't rely on cache invalidation on writes, | ||
* so instead we just clear the cache every 10mins */ | ||
@CacheEvict(allEntries = true, value = CONFIGS_WITH_DOMAIN_CACHE_KEY) | ||
@Scheduled(fixedDelay = 10 * 60 * 1000, initialDelay = 10 * 60 * 1000) | ||
public void configCacheEvict() { | ||
log.info("Evicting cache for {}", CONFIGS_WITH_DOMAIN_CACHE_KEY); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package bio.terra.pearl.core.service.logging; | ||
|
||
import bio.terra.pearl.core.model.portal.PortalEnvironmentConfig; | ||
import com.mixpanel.mixpanelapi.ClientDelivery; | ||
import com.mixpanel.mixpanelapi.MessageBuilder; | ||
import com.mixpanel.mixpanelapi.MixpanelAPI; | ||
|
@@ -13,16 +14,21 @@ | |
import org.springframework.stereotype.Service; | ||
|
||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@Service | ||
@Slf4j | ||
public class MixpanelService { | ||
private final Environment env; | ||
|
||
public MixpanelService(Environment env) { | ||
this.env = env; | ||
private static final String MIXPANEL_TOKEN_ENV_VAR = "env.mixpanel.token"; | ||
private static final String MIXPANEL_ENABLED_ENV_VAR = "env.mixpanel.enabled"; | ||
private final MixpanelConfig mixpanelConfig; | ||
private final LoggingConfigCache loggingConfigCache; | ||
|
||
public MixpanelService(MixpanelConfig mixpanelConfig, LoggingConfigCache loggingConfigCache) { | ||
this.mixpanelConfig = mixpanelConfig; | ||
this.loggingConfigCache = loggingConfigCache; | ||
} | ||
|
||
private Map<String, String> getRedactionPatterns() { | ||
|
@@ -47,59 +53,100 @@ public String filterEventData(String data) { | |
} | ||
|
||
public void logEvent(String data) { | ||
if(!Boolean.parseBoolean(env.getProperty("env.mixpanel.enabled"))) { | ||
if(!mixpanelConfig.enabled) { | ||
return; | ||
} | ||
|
||
// Filter all the incoming events in one pass, so we don't have | ||
// to unpack the JSONObject and repack it for each individual event | ||
String filteredData = filterEventData(data); | ||
|
||
//Mixpanel sends event data as urlencoded form data, so we need to parse the event data as a JSON array | ||
JSONArray events = new JSONArray(filteredData); | ||
|
||
ClientDelivery delivery = new ClientDelivery(); | ||
ClientDelivery customDelivery = null; | ||
|
||
for (int i = 0; i < events.length(); i++) { | ||
JSONObject mixpanelEvent = buildEvent(events.getJSONObject(i)); | ||
JSONObject event = events.getJSONObject(i); | ||
JSONObject mixpanelEvent = buildEvent(event, mixpanelConfig.token); | ||
delivery.addMessage(mixpanelEvent); | ||
String eventDomain = getEventCurrentDomain(event); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not send the portal shortcode when tracking to avoid domain matching? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suppose we could send portal shortcode and environment (we'd need the environment to not log sandbox events to their mixpanel) |
||
|
||
/** check if the event comes from a domain with a dedicated mixpanel token, if so, use that token to send the | ||
* event to that token in addition to the global mixpanel domain */ | ||
if (eventDomain != null) { | ||
customDelivery = customDelivery == null ? new ClientDelivery() : customDelivery; | ||
Map<String, PortalEnvironmentConfig> configMap = loggingConfigCache.getConfigsWithDomain(); | ||
PortalEnvironmentConfig matchedConfig = configMap.get(eventDomain); | ||
if (matchedConfig != null && matchedConfig.getMixpanelToken() != null) { | ||
JSONObject domainEvent = buildEvent(event, matchedConfig.getMixpanelToken()); | ||
customDelivery.addMessage(domainEvent); | ||
} | ||
} | ||
} | ||
|
||
deliverEvents(delivery); | ||
if (customDelivery != null) { | ||
deliverEvents(customDelivery); | ||
} | ||
} | ||
|
||
protected JSONObject buildEvent(JSONObject event) { | ||
String MIXPANEL_TOKEN_ENV_VAR = "env.mixpanel.token"; | ||
MessageBuilder messageBuilder = new MessageBuilder(env.getProperty(MIXPANEL_TOKEN_ENV_VAR)); | ||
protected JSONObject buildEvent(JSONObject event, String apiToken) { | ||
|
||
MessageBuilder messageBuilder = new MessageBuilder(apiToken); | ||
|
||
return messageBuilder.event( | ||
null, | ||
event.getString("event"), | ||
event.getJSONObject("properties") | ||
.put("token", env.getProperty(MIXPANEL_TOKEN_ENV_VAR)) | ||
.put("token", apiToken) | ||
); | ||
} | ||
|
||
protected void deliverEvents(ClientDelivery delivery) { | ||
MixpanelAPI mixpanel = new MixpanelAPI(); | ||
|
||
try { | ||
mixpanel.deliver(delivery); | ||
} catch (IOException e) { | ||
log.info("Failed to deliver event to Mixpanel: {}", e.getMessage()); | ||
} | ||
} | ||
|
||
protected String getEventCurrentDomain(JSONObject event) { | ||
String domain = null; | ||
if (event.has("properties")) { | ||
JSONObject properties = event.getJSONObject("properties"); | ||
if (properties.has("$current_url")) { | ||
String url = properties.getString("$current_url"); | ||
return getDomainName(url); | ||
} | ||
} | ||
return domain; | ||
} | ||
|
||
public static String getDomainName(String url) { | ||
try { | ||
URI uri = new URI(url); | ||
String domain = uri.getHost(); | ||
return domain.startsWith("www.") ? domain.substring(4) : domain; | ||
} catch (Exception e) { | ||
return null; | ||
} | ||
} | ||
|
||
@Component | ||
@Getter @Setter | ||
public static class MixpanelConfig { | ||
private String token; | ||
private String enabled; | ||
private Boolean enabled; | ||
|
||
public MixpanelConfig(Environment environment) { | ||
this.token = environment.getProperty("env.mixpanel.token"); | ||
this.enabled = environment.getProperty("env.mixpanel.enabled"); | ||
this.enabled = Boolean.parseBoolean(environment.getProperty("env.mixpanel.enabled")); | ||
} | ||
} | ||
|
||
|
||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
databaseChangeLog: | ||
- changeSet: | ||
id: "custom_mixpanel_config" | ||
author: dbush | ||
changes: | ||
- addColumn: | ||
tableName: portal_environment_config | ||
columns: | ||
- column: { name: mixpanel_token, type: text } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great decision to cache this