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

[TF-633] Feature: migrate to @segment/analytics-next package #1

Merged
merged 16 commits into from
Apr 15, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17,502 changes: 10,666 additions & 6,836 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@
"url": "https://github.com/prezly/analytics/issues"
},
"homepage": "https://github.com/prezly/analytics#readme",
"workspaces": [
"packages/*"
],
"workspaces": ["packages/*"],
"engines": {
"node": ">= 14.x",
"npm": ">= 7.x"
Expand All @@ -51,6 +49,7 @@
"typescript": "^4.6.3"
},
"dependencies": {
"@prezly/sdk": "^6.21.0",
"next": "^12.1.4",
"react": "^17.0.2",
"react-dom": "^17.0.2"
Expand Down
3 changes: 2 additions & 1 deletion packages/analytics-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
},
"homepage": "https://github.com/prezly/analytics#readme",
"peerDependencies": {
"@prezly/sdk": "^6.21.0",
"next": "^12.x",
"react": "^17.x",
"react-dom": "^17.x"
},
"dependencies": {
"@prezly/sdk": "^6.17.0",
"@segment/analytics-next": "^1.35.0",
"js-cookie": "^3.0.1",
"react-use": "^17.3.2"
},
Expand Down
80 changes: 54 additions & 26 deletions packages/analytics-nextjs/src/context.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import type { Newsroom, Story } from '@prezly/sdk';
import { TrackingPolicy } from '@prezly/sdk';
import type { Analytics, Plugin } from '@segment/analytics-next';
import { AnalyticsBrowser } from '@segment/analytics-next';
import Head from 'next/head';
import Script from 'next/script';
import type { PropsWithChildren } from 'react';
import { createContext, useContext, useEffect, useState } from 'react';

import {
createAnalyticsStub,
getAnalyticsJsUrl,
getConsentCookie,
isPrezlyTrackingAllowed,
setConsentCookie,
} from './lib';
import { getConsentCookie, isPrezlyTrackingAllowed, setConsentCookie } from './lib';
import { injectPrezlyMetaPlugin, sendEventToPrezlyPlugin } from './plugins';

interface Context {
analytics: Analytics | undefined;
consent: boolean | null;
isAnalyticsReady: boolean;
isEnabled: boolean;
isTrackingAllowed: boolean | null;
newsroom: Newsroom;
Expand All @@ -27,6 +23,7 @@ interface Props {
isEnabled?: boolean;
newsroom: Newsroom;
story: Story | undefined;
plugins?: Plugin[];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought it would be a nice feature to allow the package consumers to pass their own plugins, that way they would be able to extend the features without the need to fork the library.

}

export const AnalyticsContext = createContext<Context | undefined>(undefined);
Expand All @@ -45,16 +42,54 @@ export function AnalyticsContextProvider({
isEnabled = true,
newsroom,
story,
plugins,
}: PropsWithChildren<Props>) {
const { uuid, tracking_policy: trackingPolicy } = newsroom;
const [isAnalyticsReady, setAnalyticsReady] = useState(false);
const {
tracking_policy: trackingPolicy,
segment_analytics_id: segmentWriteKey,
uuid,
} = newsroom;
const [consent, setConsent] = useState(getConsentCookie());
const isTrackingAllowed = isEnabled && isPrezlyTrackingAllowed(consent, newsroom);

const [analytics, setAnalytics] = useState<Analytics | undefined>(undefined);

useEffect(() => {
if (trackingPolicy === TrackingPolicy.DISABLED) {
window.analytics = createAnalyticsStub();
async function loadAnalytics(writeKey: string) {
const [response] = await AnalyticsBrowser.load(
{
writeKey,
// If no Segment Write Key is provided, we initialize the library settings manually
...(!writeKey && {
cdnSettings: {
integrations: {},
},
}),
Comment on lines +62 to +67
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This code allows the library to initialize without a working Segment Write Key (it is used to fetch the source configuration from Segment, which we don't need to Prezly-only tracking).
Somehow the tracking calls to Segment API still go through even without authorization. This is handled later in the code.

plugins: [
injectPrezlyMetaPlugin(),
sendEventToPrezlyPlugin(uuid),
...(plugins || []),
],
},
{
// By default, the analytics.js library plants its cookies on the top-level domain.
// We need to completely isolate tracking between any Prezly newsroom hosted on a .prezly.com subdomain.
cookie: {
domain: document.location.host,
},
// If no Segment Write Key is provided, we initialize the library settings manually
...(!writeKey && {
integrations: { 'Segment.io': false },
}),
},
);
setAnalytics(response);
}

if (isTrackingAllowed) {
loadAnalytics(segmentWriteKey || '');
}
});
}, [segmentWriteKey, isTrackingAllowed, uuid, plugins]);

useEffect(() => {
if (typeof consent === 'boolean') {
Expand All @@ -65,29 +100,22 @@ export function AnalyticsContextProvider({
return (
<AnalyticsContext.Provider
value={{
analytics,
consent,
isAnalyticsReady,
isEnabled,
isTrackingAllowed: isEnabled && isPrezlyTrackingAllowed(consent, newsroom),
isTrackingAllowed,
newsroom,
setConsent,
trackingPolicy: newsroom.tracking_policy,
trackingPolicy,
}}
>
<Head>
<meta name="prezly:newsroom" content={newsroom.uuid} />
{story && <meta name="prezly:story" content={story.uuid} />}
{newsroom.tracking_policy !== TrackingPolicy.DEFAULT && (
<meta name="prezly:tracking_policy" content={newsroom.tracking_policy} />
{trackingPolicy !== TrackingPolicy.DEFAULT && (
<meta name="prezly:tracking_policy" content={trackingPolicy} />
)}
</Head>
{trackingPolicy !== TrackingPolicy.DISABLED && (
<Script
key="prezly-analytics"
onLoad={() => setAnalyticsReady(true)}
src={getAnalyticsJsUrl(uuid)}
/>
)}
{children}
</AnalyticsContext.Provider>
);
Expand Down
7 changes: 0 additions & 7 deletions packages/analytics-nextjs/src/global.d.ts

This file was deleted.

39 changes: 20 additions & 19 deletions packages/analytics-nextjs/src/hooks/useAnalytics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TrackingPolicy } from '@prezly/sdk';
import { useCallback, useEffect } from 'react';
import { useLocalStorage, useQueue } from 'react-use';
import { useLatest, useLocalStorage, useQueue } from 'react-use';

import { useAnalyticsContext } from '../context';
import { stringify } from '../lib';
Expand All @@ -9,8 +9,8 @@ import type { DeferredIdentity } from '../types';
const DEFERRED_IDENTITY_STORAGE_KEY = 'prezly_ajs_deferred_identity';

export function useAnalytics() {
const { consent, isAnalyticsReady, isEnabled, newsroom, trackingPolicy } =
useAnalyticsContext();
const { analytics, consent, isEnabled, newsroom, trackingPolicy } = useAnalyticsContext();
const analyticsRef = useLatest(analytics);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

All of the changes to this file were basically about replacing window.analytics with the analytics object received from analytics-next.

const [deferredIdentity, setDeferredIdentity, removeDeferredIdentity] =
useLocalStorage<DeferredIdentity>(DEFERRED_IDENTITY_STORAGE_KEY);
const {
Expand Down Expand Up @@ -53,12 +53,12 @@ export function useAnalytics() {
}

addToQueue(() => {
if (window.analytics && window.analytics.identify) {
window.analytics.identify(userId, traits, buildOptions(), callback);
if (analytics && analytics.identify) {
analytics.identify(userId, traits, buildOptions(), callback);
}
});
},
[addToQueue, buildOptions, consent, setDeferredIdentity, trackingPolicy],
[analytics, addToQueue, buildOptions, consent, setDeferredIdentity, trackingPolicy],
);

function alias(userId: string, previousId: string) {
Expand All @@ -68,8 +68,8 @@ export function useAnalytics() {
}

addToQueue(() => {
if (window.analytics && window.analytics.alias) {
window.analytics.alias(userId, previousId, buildOptions());
if (analytics && analytics.alias) {
analytics.alias(userId, previousId, buildOptions());
}
});
}
Expand All @@ -86,8 +86,8 @@ export function useAnalytics() {
}

addToQueue(() => {
if (window.analytics && window.analytics.page) {
window.analytics.page(category, name, properties, buildOptions(), callback);
if (analyticsRef.current && analyticsRef.current.page) {
analyticsRef.current.page(category, name, properties, buildOptions(), callback);
}
});
}
Expand All @@ -99,15 +99,15 @@ export function useAnalytics() {
}

addToQueue(() => {
if (window.analytics && window.analytics.track) {
window.analytics.track(event, properties, buildOptions(), callback);
if (analyticsRef.current && analyticsRef.current.track) {
analyticsRef.current.track(event, properties, buildOptions(), callback);
}
});
}

function user() {
if (window.analytics && window.analytics.user) {
return window.analytics.user();
const user = useCallback(() => {
if (analytics && analytics.user) {
return analytics.user();
}

// Return fake user API to keep code working even without analytics.js loaded
Expand All @@ -116,16 +116,16 @@ export function useAnalytics() {
return null;
},
};
}
}, [analytics]);

useEffect(() => {
// We are using simple queue to trigger tracking calls
// that might have been created before analytics.js was loaded.
if (isAnalyticsReady && firstInQueue) {
if (analytics && firstInQueue) {
firstInQueue();
removeFromQueue();
}
}, [firstInQueue, isAnalyticsReady, removeFromQueue]);
}, [firstInQueue, analytics, removeFromQueue]);

useEffect(() => {
if (consent) {
Expand All @@ -142,7 +142,7 @@ export function useAnalytics() {

user().id(null); // erase user ID
}
}, [consent, deferredIdentity, identify, removeDeferredIdentity, setDeferredIdentity]);
}, [consent, deferredIdentity, identify, user, removeDeferredIdentity, setDeferredIdentity]);

if (!isEnabled) {
return {
Expand All @@ -154,6 +154,7 @@ export function useAnalytics() {
};
}

// TODO: Expose all methods of analytics-next (might not be needed, since we already provide the `analytics` object)
return {
alias,
identify,
Expand Down
1 change: 0 additions & 1 deletion packages/analytics-nextjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from './components';
export { AnalyticsContextProvider } from './context';
export * from './events';
export { useAnalytics } from './hooks';
export type { AnalyticsJS } from './types';
38 changes: 0 additions & 38 deletions packages/analytics-nextjs/src/lib/createAnalyticsStub.ts

This file was deleted.

8 changes: 0 additions & 8 deletions packages/analytics-nextjs/src/lib/getAnalyticsJsUrl.ts

This file was deleted.

7 changes: 7 additions & 0 deletions packages/analytics-nextjs/src/lib/getApiUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function getApiUrl(): string {
if (process.env.NODE_ENV !== 'production') {
return 'http://analytics.prezly.test';
}

return 'https://analytics.prezly.com';
}
8 changes: 1 addition & 7 deletions packages/analytics-nextjs/src/lib/getRecipientInfo.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import type { RecipientInfo } from '../types';

function getApiUrl(): string {
if (process.env.NODE_ENV !== 'production') {
return 'http://analytics.prezly.test';
}

return 'https://analytics.prezly.com';
}
import { getApiUrl } from './getApiUrl';

export function isRecipientIdFormat(id?: string | null): id is string {
if (!id) {
Expand Down
3 changes: 1 addition & 2 deletions packages/analytics-nextjs/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export * from './cookieState';
export * from './createAnalyticsStub';
export * from './getAnalyticsJsUrl';
export * from './getApiUrl';
export * from './getAssetClickEvent';
export * from './getRecipientInfo';
export * from './isPrezlyTrackingAllowed';
Expand Down
2 changes: 2 additions & 0 deletions packages/analytics-nextjs/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { injectPrezlyMetaPlugin } from './injectPrezlyMeta';
export { sendEventToPrezlyPlugin } from './sendToPrezly';
Loading