Skip to content
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

WSTEAM1-1514: Add click tracking at top level #12360

Open
wants to merge 8 commits into
base: latest
Choose a base branch
from

Conversation

shayneahchoon
Copy link
Contributor

Resolves JIRA WSTEAM1-1514

Overall changes

Implements changes from #12332

Code changes

  • Uses immediately invoked function expression instead of a string to outline script.
  • Adds unit tests.
  • Adds tracking script at top level of document.
  • Adds lite tracking on Most Read links only.

Testing

  1. List the steps used to test this PR.

Helpful Links

Add Links to useful resources related to this PR if applicable.

Coding Standards

Repository use guidelines

src/app/hooks/useClickTrackerHandler/index.jsx Outdated Show resolved Hide resolved

return (
<div css={getItemCss({ dir, size })} dir={dir}>
<a
css={[styles.link, size === 'default' && styles.defaultLink]}
href={href}
onClick={clickTrackerHandler}
{...(isLite && { [LITE_TRACKER_PARAM]: liteUrl })}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that in order to add tracking on lite, we would need to add this line to every single component? Is there a way of doing this for all components? I wondered if that should be part of the intercepting of the click event...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be really hard to do without duplicating a lot of the atiTracking logic in vanilla js.

src/server/utilities/clickTrackingScript/clickTracking.js Outdated Show resolved Hide resolved
@@ -9,6 +10,7 @@ import { ServiceContext } from '../../contexts/ServiceContext';
import { isValidClick } from './clickTypes';

const EVENT_TYPE = 'click';
export const LITE_TRACKER_PARAM = 'data-ati-tracking';
Copy link
Contributor

@karinathomasbbc karinathomasbbc Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Naming things is 🧑‍🔬 (ignore this for the spike - perhaps when we productionise it, we can rename)

Suggested change
export const LITE_TRACKER_PARAM = 'data-ati-tracking';
export const LITE_ATI_TRACKING = 'data-lite-ati-tracking';

@@ -0,0 +1,92 @@
export default function trackingScript() {
Copy link
Contributor

@karinathomasbbc karinathomasbbc Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there duplication between this and the canonical ATI code, and is there any way of sharing the code / implementing it once in one place? I would be keen to keep this script as small as possible 🤞

e.g. can we use https://github.com/bbc/simorgh/blob/c326deb0c8e9ca4469c471a784e12f6eb0eaac51/src/app/lib/analyticsUtils/index.js and have even more branching logic for platform === 'lite'? Or is that just a maintenance nightmare, since we already have amp & canonical code in here?

}
}

if (!atUserIdValue && crypto.randomUUID) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to worry about Opera Mini + Lite, or does this work? Or is the point of Opera Mini that all users have the same ID, so it would be impossible for us to detect unique users?

Comment on lines +144 to +148
const componentName = props?.componentName;
const url = props?.url;
const advertiserID = props?.advertiserID;
const format = props?.format;
const detailedPlacement = props?.detailedPlacement;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use destructuring here?

@@ -1,5 +1,6 @@
/* eslint-disable react/no-danger */
import React, { ReactElement, PropsWithChildren } from 'react';
import trackingScript from '#src/server/utilities/clickTrackingScript/clickTracking';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we rename the folder / file, let's also rename it here

Comment on lines 55 to 61
const clickTrackerHandler = isLite
? {
[LITE_TRACKER_PARAM]: useConstructLiteSiteUrl(eventTrackingData),
}
: {
onClick: useClickTrackerHandler(eventTrackingData),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this logic be moved into the useClickTrackerHandler code, meaning that the only changes we would need to make to the other components which have click tracking would be what you've done in this file on line 68?


trackingScript();

document.cookie =

Check warning

Code scanning / CodeQL

Clear text transmission of sensitive cookie Medium

Sensitive cookie sent without enforcing SSL encryption.

Copilot Autofix AI about 23 hours ago

To fix the problem, we need to ensure that the cookie is transmitted using SSL by setting the secure attribute on the cookie. This can be done by modifying the document.cookie assignment to include the secure attribute. This change should be made in the beforeEach block where the cookie is being set.

Suggested changeset 1
src/server/utilities/liteATIClickTracking/index.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/server/utilities/liteATIClickTracking/index.test.ts b/src/server/utilities/liteATIClickTracking/index.test.ts
--- a/src/server/utilities/liteATIClickTracking/index.test.ts
+++ b/src/server/utilities/liteATIClickTracking/index.test.ts
@@ -21,3 +21,3 @@
     document.cookie =
-      'atuserid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
+      'atuserid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; secure;';
 
@@ -53,3 +53,3 @@
 
-    expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}');
+    expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}; secure');
   });
EOF
@@ -21,3 +21,3 @@
document.cookie =
'atuserid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
'atuserid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; secure;';

@@ -53,3 +53,3 @@

expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}');
expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}; secure');
});
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
'https://logws1363.ati-host.net/?',
);

document.cookie = 'atuserid={"val":"oldCookieId"}';

Check warning

Code scanning / CodeQL

Clear text transmission of sensitive cookie Medium

Sensitive cookie sent without enforcing SSL encryption.

Copilot Autofix AI about 24 hours ago

To fix the problem, we need to ensure that the cookie is transmitted using SSL by setting the secure attribute on the cookie. This can be done by appending ; secure to the cookie string when setting it. This change should be made in the test file where the cookie is being set.

Suggested changeset 1
src/server/utilities/liteATIClickTracking/index.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/server/utilities/liteATIClickTracking/index.test.ts b/src/server/utilities/liteATIClickTracking/index.test.ts
--- a/src/server/utilities/liteATIClickTracking/index.test.ts
+++ b/src/server/utilities/liteATIClickTracking/index.test.ts
@@ -53,3 +53,3 @@
 
-    expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}');
+    expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}; secure');
   });
@@ -63,3 +63,3 @@
 
-    document.cookie = 'atuserid={"val":"oldCookieId"}';
+    document.cookie = 'atuserid={"val":"oldCookieId"}; secure';
     randomUUIDMock.mockReturnValueOnce('newCookieId');
EOF
@@ -53,3 +53,3 @@

expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}');
expect(document.cookie).toBe('atuserid={"val":"randomUniqueId"}; secure');
});
@@ -63,3 +63,3 @@

document.cookie = 'atuserid={"val":"oldCookieId"}';
document.cookie = 'atuserid={"val":"oldCookieId"}; secure';
randomUUIDMock.mockReturnValueOnce('newCookieId');
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
Comment on lines +24 to +25
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be replaced with

Suggested change
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// @ts-expect-error Explain why here

const randomUUIDMock = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers().setSystemTime(new Date(1731515402000));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Could we use the ISO Date, so that we don't need to calculate what date/time this represents?

Suggested change
jest.useFakeTimers().setSystemTime(new Date(1731515402000));
jest.useFakeTimers().setSystemTime(new Date("2024-11-13T16:30:02.000Z"));

trackingScript();

document.cookie =
'atuserid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a more realistic expiry date here?

Suggested change
'atuserid=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
'atuserid=; expires=Wed, 01 Jan 2025 00:00:00 UTC; path=/;';

Comment on lines +53 to +61
const { isLite } = useContext(RequestContext);

const clickTrackerHandler = isLite
? {
[LITE_TRACKER_PARAM]: useConstructLiteSiteATIEventTrackUrl(eventTrackingData),
}
: {
onClick: useClickTrackerHandler(eventTrackingData),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for the spike, but I would like to see this extracted into a utility - perhaps somewhere adjacent to, or within the useClickTrackerHandler - which we can reuse for all other components we would like to add click tracking to.

@@ -205,6 +205,77 @@ exports[`Document Component should render LITE version correctly 1`] = `
<style>
.css-7prgni-StyledLink{display:inline-block;}
</style>
<script>
(function trackingScript() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't expecting this, but was there an existing test which already snapshots the Document component?

Comment on lines +35 to +39
Object.defineProperty(global, 'crypto', {
value: {
randomUUID: randomUUIDMock,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for spike but we should probably move this to the src/testHelpers/jest-setup.js file so that we don't have to keep mocking it in other places

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants