Skip to content

Commit

Permalink
feat: [ZIF-1008] add Matomo feature usage tracking for smart link (#474)
Browse files Browse the repository at this point in the history
* feat(tracking): add initial layout for tracking an event with Matomo

- add test on matomo success + match url

* feat(tracking): add failure when Matomo answers != 204

- add test on matomo failure/status code

* feat(tracking): add tracking of smartlink event to CreateSmartLinks

- small refactors + test on tracking called when creating smart links

* test: add test to check equality for TrackingUtil.anonymize

* test: inline few local vars

* test: make generateRequest return a request instead of a url

* chore: make sendEvent ignore failures

* chore: apply sonarqube suggestions

* feat: remove unnecessary userId hashing

---------

Co-authored-by: Giovanni De Facci <giovanni.defacci@zextras.com>
Co-authored-by: Alessio Coser <18616553+AlessioCoser@users.noreply.github.com>
  • Loading branch information
3 people authored Apr 8, 2024
1 parent f0481ba commit f352b31
Show file tree
Hide file tree
Showing 11 changed files with 541 additions and 248 deletions.
30 changes: 30 additions & 0 deletions store/src/main/java/com/zextras/mailbox/tracking/Event.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
//
// SPDX-License-Identifier: AGPL-3.0-only

package com.zextras.mailbox.tracking;

public class Event {

public String getUserId() {
return userId;
}

public String getCategory() {
return category;
}

public String getAction() {
return action;
}

private final String userId;
private final String category;
private final String action;

public Event(String userId, String category, String action) {
this.userId = userId;
this.category = category;
this.action = action;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
//
// SPDX-License-Identifier: AGPL-3.0-only

package com.zextras.mailbox.tracking;

import java.net.URI;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

public class MatomoTracking implements Tracking {

private final String matomoEndpoint;

public MatomoTracking(String matomoEndpoint) {
this.matomoEndpoint = matomoEndpoint;
}

@Override
public void sendEventIgnoringFailure(Event event) {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
client.execute(generateRequest(event));
} catch (Exception ignored) {
}
}

private HttpGet generateRequest(Event event) {
final HttpGet httpGet = new HttpGet();
String url = matomoEndpoint + "/matomo.php?idsite=7&rec=1&send_image=0&apiv=1"
+ "&e_c=" + event.getCategory() +
"&e_a=" + event.getAction() +
"&uid=" + event.getUserId();
httpGet.setURI(URI.create(url));
return httpGet;
}

}
16 changes: 16 additions & 0 deletions store/src/main/java/com/zextras/mailbox/tracking/Tracking.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
//
// SPDX-License-Identifier: AGPL-3.0-only

package com.zextras.mailbox.tracking;

/**
* Tracks events/actions performed by the user to use as metrics/insights on Carbonio usages.
* Tracking of data is anonymous and not linkable to user information/personal data.
*
*/
public interface Tracking {

void sendEventIgnoringFailure(Event event);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.zextras.mailbox.AuthenticationInfo;
import com.zextras.mailbox.smartlinks.Attachment;
import com.zextras.mailbox.smartlinks.SmartLinksGenerator;
import com.zextras.mailbox.tracking.Event;
import com.zextras.mailbox.tracking.Tracking;
import com.zimbra.common.service.ServiceException;
import com.zimbra.common.soap.Element;
import com.zimbra.cs.account.Account;
Expand All @@ -20,9 +22,11 @@
public class CreateSmartLinks extends MailDocumentHandler {

private final SmartLinksGenerator smartLinksGenerator;
private final Tracking tracking;

public CreateSmartLinks(SmartLinksGenerator smartLinksGenerator) {
public CreateSmartLinks(SmartLinksGenerator smartLinksGenerator, Tracking tracking) {
this.smartLinksGenerator = smartLinksGenerator;
this.tracking = tracking;
}

@Override
Expand All @@ -45,6 +49,10 @@ private CreateSmartLinksResponse handle(
}
final List<Attachment> attachments = toAttachments(req.getAttachments());
final List<SmartLink> smartLinks = generateSmartLinks(authenticationInfo, attachments);

final String uid = authenticationInfo.getAuthenticatedAccount().getId();
tracking.sendEventIgnoringFailure(new Event(uid, "Mail", "SendEmailWithSmartLink"));

return new CreateSmartLinksResponse(smartLinks);
}

Expand All @@ -56,7 +64,7 @@ private List<SmartLink> generateSmartLinks(
.smartLinksFrom(attachments, authenticationInfo)
.stream()
.map(smartLink -> new SmartLink(smartLink.getPublicUrl())
).collect(Collectors.toList());
).collect(Collectors.toList());
}

private List<Attachment> toAttachments(List<AttachmentToConvert> reqAttachments) {
Expand Down
10 changes: 8 additions & 2 deletions store/src/main/java/com/zimbra/cs/service/mail/MailService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.zextras.carbonio.files.FilesClient;
import com.zextras.files.client.GraphQLFilesClient;
import com.zextras.mailbox.smartlinks.FilesSmartLinksGenerator;
import com.zextras.mailbox.tracking.MatomoTracking;
import com.zextras.mailbox.tracking.Tracking;
import com.zimbra.common.soap.MailConstants;
import com.zimbra.cs.service.MailboxAttachmentService;
import com.zimbra.soap.DocumentDispatcher;
Expand Down Expand Up @@ -237,12 +239,16 @@ public void registerHandlers(DocumentDispatcher dispatcher) {
dispatcher.registerHandler(
QName.get("CreateSmartLinksRequest", MailConstants.NAMESPACE),
new CreateSmartLinks(new FilesSmartLinksGenerator(
new GraphQLFilesClient(getFilesClient(), new ObjectMapper()),
filesCopyHandler)
new GraphQLFilesClient(getFilesClient(), new ObjectMapper()), filesCopyHandler),
getTracking()
)
);
}

protected Tracking getTracking() {
return new MatomoTracking("https://analytics.zextras.tools");
}

protected FilesClient getFilesClient() {
return FilesClient.atURL("http://127.78.0.7:20002");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
package com.zextras.mailbox.soap;

import com.zextras.mailbox.util.SoapClient;
import org.apache.http.HttpResponse;
import org.junit.jupiter.api.extension.RegisterExtension;

/**
Expand All @@ -18,15 +17,11 @@ public class SoapTestSuite {
static SoapExtension soapExtension = new SoapExtension.Builder()
.addEngineHandler("com.zimbra.cs.service.admin.AdminService")
.addEngineHandler("com.zimbra.cs.service.account.AccountService")
.addEngineHandler("com.zimbra.cs.service.mail.MailService")
.addEngineHandler("com.zimbra.cs.service.mail.MailServiceWithoutTracking")
.create();

public SoapClient getSoapClient() {
return soapExtension.getSoapClient();
}

public String getResponse(HttpResponse response) throws Exception {
return new String (response.getEntity().getContent().readAllBytes());
}

}
14 changes: 14 additions & 0 deletions store/src/test/java/com/zextras/mailbox/soap/SoapUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
//
// SPDX-License-Identifier: AGPL-3.0-only

package com.zextras.mailbox.soap;

import org.apache.http.HttpResponse;

public class SoapUtils {

public static String getResponse(HttpResponse response) throws Exception {
return new String (response.getEntity().getContent().readAllBytes());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2024 Zextras <https://www.zextras.com>
//
// SPDX-License-Identifier: AGPL-3.0-only

package com.zextras.mailbox.tracking;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockserver.integration.ClientAndServer.startClientAndServer;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;

import java.io.IOException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockserver.integration.ClientAndServer;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.Parameter;
import org.mockserver.verify.VerificationTimes;

class MatomoTrackingTest {

private ClientAndServer matomo;
private static final int MATOMO_PORT = 5000;

@BeforeEach
public void startUp() throws IOException {
matomo = startClientAndServer(MATOMO_PORT);
}

@AfterEach
public void tearDown() throws IOException {
matomo.stop();
}

@Test
void shouldSendEventToMatomo() {
final Event event = new Event("UserId", "TestCategory", "TestAction");
final HttpRequest matomoRequest = createMatomoRequest("UserId", "TestCategory", "TestAction");
mockSuccessMatomoResponse(matomoRequest);

assertDoesNotThrow(() -> new MatomoTracking("http://localhost:" + MATOMO_PORT).sendEventIgnoringFailure(event));
matomo.verify(matomoRequest, VerificationTimes.exactly(1));
}

@Test
void shouldFailWhenMatomoFails() {
final Event event = new Event("UserId", "TestCategory", "TestAction");
final HttpRequest matomoRequest = createMatomoRequest("UserId", "TestCategory", "TestAction");
mockFailureMatomoResponse(matomoRequest);

assertDoesNotThrow(() -> new MatomoTracking("http://localhost:" + MATOMO_PORT).sendEventIgnoringFailure(event));
matomo.verify(matomoRequest, VerificationTimes.exactly(1));
}

private void mockFailureMatomoResponse(HttpRequest request) {
matomo
.when(request)
.respond(response().withStatusCode(500));
}

private void mockSuccessMatomoResponse(HttpRequest request) {
matomo
.when(request)
.respond(response().withStatusCode(204));
}

private HttpRequest createMatomoRequest(String uid, String category, String action) {
return request()
.withMethod("GET")
.withPath("/matomo.php")
.withQueryStringParameters(
Parameter.param("idsite", "7"),
Parameter.param("rec", "1"),
Parameter.param("send_image", "0"),
Parameter.param("apiv", "1"),
Parameter.param("e_c", category),
Parameter.param("e_a", action),
Parameter.param("uid", uid)
);
}
}
Loading

0 comments on commit f352b31

Please sign in to comment.