Skip to content

Commit

Permalink
Merge pull request #69 from companieshouse/feature/IDVA6-1914-Matomo-…
Browse files Browse the repository at this point in the history
…tracker-client

IDVA6-1914 Matomo tracker client
  • Loading branch information
griffithsjd authored Dec 12, 2024
2 parents 983c831 + de3aa49 commit 38a3640
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 24 deletions.
94 changes: 75 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,25 +71,6 @@ messageSource.setBasenames("locales/messages", "locales/common-messages");

The node.js equivalent is ```ch-node-utils``` but this is still to be fully implemented

## More Info

### chsBaseLayout.html

"Standard" layout for CHS services.

This layout expects properties/environment variables to have been set accordingly.

| Property | Environment | Description |
|--------------|---------------|----------------------------------------------------|
| cdn.url | CDN_HOST | Global environment variable for CDN |
| chs.url | CHS_URL | Global environment variable for main CHS home page |
| piwik.url | PIWIK_URL | Relevant Piwik/Matomo url for service |
| piwik.siteId | PIWIK_SITE_ID | Relevant Piwik/Matomo id for service |

Requires ```serviceName``` variable to be set to the name of the service using the template as described above.

The following fragments are used by this baseLayout depending on the setting of variables described in each fragment.

## Common Interceptors

### TemplateNameInterceptor
Expand All @@ -108,6 +89,81 @@ public void addInterceptors(@NonNull InterceptorRegistry registry) {
...
}
```
## Common Services

### MatomoClient

Spring component to allow the sending of Matomo tracking events or goals

requires _pk_id.xxx cookie in order to obtain visitorID.

This service expects properties/environment variables to have been set accordingly.

| Property | Environment | Description |
|--------------|---------------|---------------------------------------|
| piwik.url | PIWIK_URL | Relevant Piwik/Matomo url for service |
| piwik.siteId | PIWIK_SITE_ID | Relevant Piwik/Matomo id for service |
| service.name | n/a | Name of service/application |

#### Configuration file
```
package uk.gov.companieshouse.authentication-service.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import uk.gov.companieshouse.common.web.service.MatomoClient;
@Configuration
public class MatomoClientConfig {
@Bean
MatomoClient getMatomoClient() {
return new MatomoClient();
}
}
```
#### Example usage
```
...
import uk.gov.companieshouse.common.web.service.MatomoClient;
...
// declare autowired component and add to constructor
private final MatomoClient matomoClient;
@Autowired
public constructor(
...
MatomoClient matomoClient) {
...
this.matomoClient = matomoClient;
}
// Send a goal (int)
matomoClient.sendGoal(httpServletRequest.getCookies(), goalNumber);
// Send an event - String page is name of template event is being raised for
sendEvent(httpServletRequest.getCookies(), "page", "event-action")
```

## chsBaseLayout.html

"Standard" layout for CHS services.

This layout expects properties/environment variables to have been set accordingly.

| Property | Environment | Description |
|--------------|---------------|----------------------------------------------------|
| cdn.url | CDN_HOST | Global environment variable for CDN |
| chs.url | CHS_URL | Global environment variable for main CHS home page |
| piwik.url | PIWIK_URL | Relevant Piwik/Matomo url for service |
| piwik.siteId | PIWIK_SITE_ID | Relevant Piwik/Matomo id for service |

Requires ```serviceName``` variable to be set to the name of the service using the template as described above.

The following fragments are used by this baseLayout depending on the setting of variables described in each fragment.



## Fragments

Expand Down
12 changes: 9 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
<parent>
<groupId>uk.gov.companieshouse</groupId>
<artifactId>companies-house-parent</artifactId>
<version>2.1.6</version>
<version>2.1.9</version>
</parent>

<properties>
<java.version>21</java.version>
<spring-boot-dependencies.version>3.3.3</spring-boot-dependencies.version>
<spring-boot-starter-thymeleaf.version>3.3.3</spring-boot-starter-thymeleaf.version>
<spring-boot-dependencies.version>3.3.5</spring-boot-dependencies.version>
<spring-boot-starter-thymeleaf.version>3.3.5</spring-boot-starter-thymeleaf.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<junit-platform-surefire-provider.version>1.3.2</junit-platform-surefire-provider.version>
Expand Down Expand Up @@ -80,6 +80,12 @@
<version>${sonar-maven-plugin.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.piwik.java.tracking</groupId>
<artifactId>matomo-java-tracker</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package uk.gov.companieshouse.common.web.service;

import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.Cookie;
import org.matomo.java.tracking.MatomoRequest;
import org.matomo.java.tracking.MatomoRequests;
import org.matomo.java.tracking.MatomoTracker;
import org.matomo.java.tracking.TrackerConfiguration;
import org.matomo.java.tracking.parameters.VisitorId;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.Arrays;
import java.util.Optional;

@Component
public class MatomoClient {
@Value("${piwik.url}")
private String PIWIK_URL;
@Value("${piwik.siteid}")
private int PIWIK_SITE_ID;
@Value("${service.name}")
private String SERVICE_NAME;

private MatomoTracker tracker;

@PostConstruct
public void init() {
// Create the tracker configuration
var configuration = TrackerConfiguration.builder()
.apiEndpoint(URI.create(PIWIK_URL + "/matomo.php"))
.defaultSiteId(PIWIK_SITE_ID)
.build();
// Prepare the tracker (stateless - can be used for multiple requests)
tracker = new MatomoTracker(configuration);
}

public void sendEvent(Cookie[] cookies, String page, String eventAction) {
track(cookies, 0, page, eventAction);
}

public void sendGoal(Cookie[] cookies, int goal) {
track(cookies, goal, null, null);
}

private void track(Cookie[] cookies, int goal, String page, String eventAction ) {

// Look for the piwik ID cookie
Optional<Cookie> cookie = Arrays.stream(cookies)
.filter(c ->c.getName().startsWith("_pk_id"))
.findFirst();

if (cookie.isPresent()) {
// Only send matomo tracking event if cookie present

MatomoRequest matomoRequest;
if (goal > 0) {
matomoRequest = MatomoRequests
.goal(goal, null)
.build();
} else {
// Create the event request - (category, action, name, value)
// Category naming is "service page"
// Action is equivalent of data-event-id in html
matomoRequest = MatomoRequests
.event( SERVICE_NAME + " - " + page, eventAction, null, null)
.build();
}

// Get the visitor ID from the cookie
var cookieContents = cookie.get().getValue().split("\\.");
matomoRequest.setVisitorId(VisitorId.fromHex(cookieContents[0]));

// Send the request asynchronously (non-blocking) as an HTTP POST request (payload is JSON and contains the
// tracking parameters)
tracker.sendRequestAsync(matomoRequest);
}
}
}
5 changes: 3 additions & 2 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
cdn.url=//${CDN_HOST}
piwik.url=${PIWIK_URL}
piwik.siteId=${PIWIK_SITE_ID}
piwik.url=${PIWIK_URL:https://matomo.platform.aws.chdev.org}
piwik.siteId=${PIWIK_SITE_ID:24}
service.name=common-web-java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package uk.gov.companieshouse.common.web.unit.service;

import jakarta.servlet.http.Cookie;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.matomo.java.tracking.MatomoRequest;
import org.matomo.java.tracking.MatomoTracker;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.util.ReflectionTestUtils;
import uk.gov.companieshouse.common.web.service.MatomoClient;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class MatomoClientTest {

private static final MatomoClient matomoClient=new MatomoClient();

private MockHttpServletRequest servletRequest;

@Mock
MatomoTracker mockTracker;
@Mock
Cookie cookie;
@Captor
private ArgumentCaptor<MatomoRequest> matomoRequestCaptor;

@BeforeAll
public static void init() {
matomoClient.init();
}
@BeforeEach
public void setUp() {
servletRequest = new MockHttpServletRequest();
Cookie[] cookies = new Cookie[]{ cookie };
servletRequest.setCookies(cookies);
ReflectionTestUtils.setField(matomoClient, "PIWIK_URL", "https://matomo.platform.aws.chdev.org");
ReflectionTestUtils.setField(matomoClient, "PIWIK_SITE_ID", 24);
ReflectionTestUtils.setField(matomoClient, "SERVICE_NAME", "common-web-java");
ReflectionTestUtils.setField(matomoClient, "tracker", mockTracker);
}

@Test
@DisplayName("send goal")
void sendGoalSuccess() {
ReflectionTestUtils.setField(matomoClient, "tracker", mockTracker);
when(cookie.getName()).thenReturn("_pk_id.24.ca6b");
when(cookie.getValue()).thenReturn("d98f63f0c6f700cd.1732096477.");

matomoClient.sendGoal(servletRequest.getCookies(), 42);

verify(mockTracker).sendRequestAsync(matomoRequestCaptor.capture());
var matomoRequest = matomoRequestCaptor.getValue();
assertEquals(42, matomoRequest.getGoalId());
assertEquals("d98f63f0c6f700cd", matomoRequest.getVisitorId().toString());
}

@Test
@DisplayName("send event")
void sendEventSuccess() {
when(cookie.getName()).thenReturn("_pk_id.24.ca6b");
when(cookie.getValue()).thenReturn("d98f63f0c6f700cd.1732096477.");

matomoClient.sendEvent(servletRequest.getCookies(), "page", "event-action");

verify(mockTracker).sendRequestAsync(matomoRequestCaptor.capture());
var matomoRequest = matomoRequestCaptor.getValue();
assertEquals("event-action", matomoRequest.getEventAction());
assertEquals("common-web-java - page", matomoRequest.getEventCategory());
assertEquals("d98f63f0c6f700cd", matomoRequest.getVisitorId().toString());
}

@Test
@DisplayName("send goal")
void noCookie() {
ReflectionTestUtils.setField(matomoClient, "tracker", mockTracker);
when(cookie.getName()).thenReturn("other-cookie");

matomoClient.sendGoal(servletRequest.getCookies(), 42);

verify(mockTracker, times(0)).sendRequestAsync(any());
}
}

0 comments on commit 38a3640

Please sign in to comment.