-
Notifications
You must be signed in to change notification settings - Fork 1
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
Changes from 11 commits
84f78e6
2a9247d
5e11a5d
8f5317d
fa8305f
2585a45
b20f7f4
3c5b94f
6208808
39db226
3833d08
75c28d7
0b327a4
b7f9f96
1234462
7025d74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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; | ||
|
@@ -27,6 +23,7 @@ interface Props { | |
isEnabled?: boolean; | ||
newsroom: Newsroom; | ||
story: Story | undefined; | ||
plugins?: Plugin[]; | ||
} | ||
|
||
export const AnalyticsContext = createContext<Context | undefined>(undefined); | ||
|
@@ -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
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. 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). |
||
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') { | ||
|
@@ -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> | ||
); | ||
|
This file was deleted.
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'; | ||
|
@@ -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); | ||
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. All of the changes to this file were basically about replacing |
||
const [deferredIdentity, setDeferredIdentity, removeDeferredIdentity] = | ||
useLocalStorage<DeferredIdentity>(DEFERRED_IDENTITY_STORAGE_KEY); | ||
const { | ||
|
@@ -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) { | ||
riffbyte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
analytics.identify(userId, traits, buildOptions(), callback); | ||
} | ||
}); | ||
}, | ||
[addToQueue, buildOptions, consent, setDeferredIdentity, trackingPolicy], | ||
[analytics, addToQueue, buildOptions, consent, setDeferredIdentity, trackingPolicy], | ||
); | ||
|
||
function alias(userId: string, previousId: string) { | ||
|
@@ -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()); | ||
} | ||
}); | ||
} | ||
|
@@ -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); | ||
} | ||
}); | ||
} | ||
|
@@ -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 | ||
|
@@ -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) { | ||
|
@@ -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 { | ||
|
@@ -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, | ||
|
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export function getApiUrl(): string { | ||
riffbyte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (process.env.NODE_ENV !== 'production') { | ||
return 'http://analytics.prezly.test'; | ||
} | ||
|
||
return 'https://analytics.prezly.com'; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { injectPrezlyMetaPlugin } from './injectPrezlyMeta'; | ||
export { sendEventToPrezlyPlugin } from './sendToPrezly'; |
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.
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.