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

Refactor #397

Draft
wants to merge 55 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e565364
Upgrade @react-hookz/web
yuriyyakym Jan 8, 2025
77e8452
Use `useQueue` from `@react-hookz/web` & drop custom implementation
yuriyyakym Jan 8, 2025
a44847d
Upgrade `@segment/analytics-next` to `v1.76.0`
yuriyyakym Jan 9, 2025
30762a7
Remove UI components
yuriyyakym Jan 9, 2025
576df1a
Update test file
yuriyyakym Jan 9, 2025
aeae927
Drop onetrust
yuriyyakym Jan 9, 2025
ff1d870
Drop cookieState helper && uninstall js-cookie
yuriyyakym Jan 9, 2025
105deba
Temporarily mock getConsentCookie in tests
yuriyyakym Jan 9, 2025
b450991
Remove Plausible-related tests
yuriyyakym Jan 9, 2025
c096b85
Remove ignoreConsent param
yuriyyakym Jan 9, 2025
55cfa7f
Remove plausible-related notes from Readme
yuriyyakym Jan 9, 2025
0a1e6de
Get rid of Plausible
yuriyyakym Jan 9, 2025
c32181d
Implement logToConsole plugin
yuriyyakym Jan 9, 2025
9438f61
Replace inline logging with logToConsole plugin
yuriyyakym Jan 9, 2025
4ec4b2a
Calculate isTrackingCookieAllowed && update analytics Context
yuriyyakym Jan 10, 2025
f3811d5
Disable google tracking if tracking is not allowed
yuriyyakym Jan 10, 2025
762b000
Introduce ConsentCategory type
yuriyyakym Jan 10, 2025
398a0b9
Add injectPrezlyMetaFromPropsPlugin
yuriyyakym Jan 13, 2025
99ebebf
Update PrezlyMeta type
yuriyyakym Jan 13, 2025
e6ef647
Extract injecting prezly meta to event into a separate plugin
yuriyyakym Jan 13, 2025
7739d69
Refactor redundant condition
yuriyyakym Jan 13, 2025
47bce58
Add useLatest hook
yuriyyakym Jan 13, 2025
eb71a31
Refactor useAnalytics hook to use useLatest instead of ref mutation
yuriyyakym Jan 13, 2025
11e0b93
Get rid of redundant useEffect for isTrackingCookieAllowed check
yuriyyakym Jan 13, 2025
c252772
Add TrackingGroups type
yuriyyakym Jan 13, 2025
93fbc20
Add getTrackingGroups helper
yuriyyakym Jan 13, 2025
1ec1a43
Update useAnalytics identification function
yuriyyakym Jan 13, 2025
7f1d2a9
Refactor AnalyticsProvider
yuriyyakym Jan 13, 2025
2fdff30
Refactor useAnalytics - remove `isTrackingCookieAllowed` condition
yuriyyakym Jan 13, 2025
a88a616
Replace TrackingGroup type with TrackingPermissions
yuriyyakym Jan 14, 2025
0428dbc
Replace getTrackingGroups with getTrackingPermissions
yuriyyakym Jan 14, 2025
2455bba
Refactor injectPrezlyMeta to work based on props and not meta tags
yuriyyakym Jan 14, 2025
7941ef2
Remove redundant normalizePrezlyMetaPlugin
yuriyyakym Jan 14, 2025
1837a2b
Update TrackingPolicy type (copy from SDK PR for now)
yuriyyakym Jan 14, 2025
06323ff
Update Google Analytics disabling effect
yuriyyakym Jan 14, 2025
efa995f
De-register segment analytics if another registration is requested
yuriyyakym Jan 14, 2025
bdbcecf
Update Consent type
yuriyyakym Jan 14, 2025
a237330
Update useAnalytics hook
yuriyyakym Jan 14, 2025
5aefe95
Update default tracking_policy value
yuriyyakym Jan 14, 2025
75abd53
Fix tests
yuriyyakym Jan 15, 2025
866d1ae
Update AnalyticsProvider
yuriyyakym Jan 15, 2025
81ef361
Update AnalyticsProvider tests
yuriyyakym Jan 15, 2025
2d746f7
Update sendToPrezly plugin and its tests
yuriyyakym Jan 15, 2025
ee5b7a0
Update useAnalytics to be aware of integrations
yuriyyakym Jan 15, 2025
97a389e
Refactor getTrackingPermissions
yuriyyakym Jan 15, 2025
ea976f7
Update AnalyticsProvider
yuriyyakym Jan 15, 2025
570558b
Update sendToPrezly plugin type to be `destination`
yuriyyakym Jan 15, 2025
4bc7f1b
Update `useAnalytics` hook
yuriyyakym Jan 15, 2025
5556191
Remove unused code
yuriyyakym Jan 15, 2025
b58d013
Add test checking if Prezly metadata is injected to event
yuriyyakym Jan 15, 2025
0081474
Install `plausible-tracker`
yuriyyakym Jan 15, 2025
bf80c3e
Add sendEventToPlausiblePlugin
yuriyyakym Jan 15, 2025
c679d2c
Revert PickedNewsroomProperties type
yuriyyakym Jan 15, 2025
1b4d0eb
Update tsconfig - do not exclude injectPrezlyMeta
yuriyyakym Jan 15, 2025
01f6a58
Load plausible plugin
yuriyyakym Jan 15, 2025
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
23,770 changes: 9,928 additions & 13,842 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@
"devDependencies": {
"@prezly/eslint-config": "5.4.5",
"@prezly/sdk": "21.2.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "12.1.5",
"@testing-library/react": "^16.1.0",
"@testing-library/react-hooks": "8.0.1",
"@types/jest": "28.1.8",
"@types/react": "18.3.18",
Expand Down
18 changes: 6 additions & 12 deletions packages/analytics-nextjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ This library is an easy plug-and-play solution to enable Prezly Analytics on you
- 🤖 Automatically handles Segment and Google Analytics integrations on your Prezly Newsroom
- 🔒 GDPR-compliant tracking
- 🍪 Ready-made components to handle Cookie Consent
- 🧪 Experimental: [Plausible] integration
- 🚀 Coming soon: 1st party domain tracking support

# Adding the library to your Next.js application
Expand All @@ -31,14 +30,15 @@ If you're starting from scratch, use [create-next-app] to quick-start the projec
To keep things fresh, we require at least Next.js 12 and React 17.

You can also install the dependencies manually

```Shell
npm install --save next react react-dom
npm install --save-dev @types/react @types/react-dom
```

## Install into your Next.js application

### /pages/_app.tsx
### /pages/\_app.tsx

In order for the library to work, you need to install it's context provider close to the top of your component tree. Ideal place for that would be the custom `_app` component.

Expand All @@ -52,10 +52,7 @@ function App({ Component, pageProps }: AppProps) {
/* Code that extracts the `newsroom` and `currentStory` props */

return (
<AnalyticsContextProvider
newsroom={newsroom}
story={currentStory}
>
<AnalyticsContextProvider newsroom={newsroom} story={currentStory}>
<Component {...pageProps} />
</AnalyticsContextProvider>
);
Expand All @@ -81,20 +78,18 @@ import type { PropsWithChildren } from 'react';
interface Props {}

function Layout({ children }: PropsWithChildren<Props>) {

return (
<>
<Analytics />
<main className="customLayout">
{children}
</main>
<main className="customLayout">{children}</main>
</>
);
}
export default Layout;
```

Here's what this component does for you:

- Automatic page visit tracking
- Detecting Campaign recipients and firing `Campaign Click` events
- Auto-clicking assets linked from a Campaign when used with [Prezly Content React Renderer]
Expand Down Expand Up @@ -135,7 +130,7 @@ You can find more examples of tracking calls in the [Prezly Bea Theme] repo.

### Using Segment tracking without Prezly Tracking

If you want to use a single solution to also track pages unrelated to Prezly, you can omit `newsroom` and `story` props on pages that don't need it.
If you want to use a single solution to also track pages unrelated to Prezly, you can omit `newsroom` and `story` props on pages that don't need it.
Instead, you would pass `segmentWriteKey` prop to `AnalyticsContextProvider`. This will disable sending events to PrezlyAnalytics and will only send events to Segment.
Note that you need to pass either `segmentWriteKey` or `newsroom` to make the tracking library work.

Expand All @@ -149,4 +144,3 @@ Please refer to [analytics-next] and [Segment docs](https://segment.com/docs/con
[Next.js]: https://nextjs.org
[Prezly Bea Theme]: https://github.com/prezly/theme-nextjs-bea
[Prezly Content React Renderer]: https://www.npmjs.com/package/@prezly/content-renderer-react-js
[Plausible]: https://plausible.io
8 changes: 3 additions & 5 deletions packages/analytics-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,12 @@
"react-dom": "^17.x || ^18.x"
},
"dependencies": {
"@react-hookz/web": "^14.2.2",
"@segment/analytics-next": "^1.66.0",
"js-cookie": "^3.0.1",
"next-plausible": "^3.12.0"
"@react-hookz/web": "^25.0.1",
"@segment/analytics-next": "^1.76.0",
"plausible-tracker": "^0.3.9"
},
"devDependencies": {
"@prezly/sdk": "21.2.0",
"@types/js-cookie": "3.0.6",
"@types/node": "22.10.5",
"@types/react": "18.3.18",
"@types/react-dom": "18.3.5"
Expand Down
191 changes: 41 additions & 150 deletions packages/analytics-nextjs/src/AnalyticsProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -1,149 +1,38 @@
import { prettyDOM } from '@testing-library/dom';
import React from 'react';
import { AnalyticsBrowser } from '@segment/analytics-next';
import { render, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';

import { DEFAULT_NEWSROOM } from './__mocks__/newsroom';

import { AnalyticsProvider, useAnalyticsContext } from './AnalyticsProvider';
import { getConsentCookie } from './lib';
import { Newsroom } from '@prezly/sdk';
import { AnalyticsProvider } from './AnalyticsProvider';
import { useEffect } from 'react';
import { useAnalytics } from './hooks';

jest.mock('./lib');
function TestingComponent() {
const analytics = useAnalytics();

const getConsentCookieMock = getConsentCookie as jest.MockedFunction<typeof getConsentCookie>;
useEffect(() => {
analytics.track('abc');
}, []);

function TestingComponent() {
const { isEnabled, consent } = useAnalyticsContext();

return (
<div>
<p>Analytics {isEnabled ? 'enabled' : 'disabled'}</p>
<p>Consent is: {`${consent}`}</p>
</div>
);
return <div></div>;
}

describe('AnalyticsProvider', () => {
it('renders into document', async () => {
getConsentCookieMock.mockReturnValue(true);
const analyticsSpy = jest.spyOn(AnalyticsBrowser, 'load');

render(<AnalyticsProvider newsroom={DEFAULT_NEWSROOM} />);

expect(getConsentCookieMock).toHaveBeenCalledTimes(1);
await waitFor(() => expect(analyticsSpy).toHaveBeenCalledTimes(1));
});

it('passes the `isEnabled` prop into the context', async () => {
getConsentCookieMock.mockReturnValue(true);

const { getByText } = render(
<AnalyticsProvider newsroom={DEFAULT_NEWSROOM} isEnabled={false}>
<TestingComponent />
</AnalyticsProvider>,
);

expect(getByText(/analytics/i)).toHaveTextContent('disabled');
});

it('bypasses consent checks when `ignorConsent` is set to `true`', async () => {
getConsentCookieMock.mockReturnValue(false);

const { getByText } = render(
<AnalyticsProvider newsroom={DEFAULT_NEWSROOM} ignoreConsent>
<TestingComponent />
</AnalyticsProvider>,
);

expect(getByText(/analytics/i)).toHaveTextContent('enabled');
expect(getByText(/consent/i)).toHaveTextContent('true');
});

it('Loads Plausible integration when newsroom has it enabled', async () => {
getConsentCookieMock.mockReturnValue(true);

const { getByTestId } = render(
<AnalyticsProvider
newsroom={{ ...DEFAULT_NEWSROOM, is_plausible_enabled: true }}
/>,
);

await waitFor(() => expect(getByTestId('plausible-debug-enabled')).toBeInTheDocument());
});

it('Loads Plausible integration with custom domain', async () => {
getConsentCookieMock.mockReturnValue(true);

const { getByTestId } = render(
<AnalyticsProvider
newsroom={{ ...DEFAULT_NEWSROOM, is_plausible_enabled: true }}
plausibleDomain="newsroom.prezly.test"
/>,
);

await waitFor(() => expect(getByTestId('plausible-debug-enabled')).toBeInTheDocument());
});

it('Does not load Plausible integration when it is disabled', async () => {
getConsentCookieMock.mockReturnValue(true);

const { queryByTestId } = render(
<AnalyticsProvider
newsroom={{ ...DEFAULT_NEWSROOM, is_plausible_enabled: false }}
/>,
);

await waitFor(() =>
expect(queryByTestId('plausible-debug-enabled')).not.toBeInTheDocument(),
);
});

it('Does not load Plausible integration when newsroom tracking policy is set to "disabled"', async () => {
getConsentCookieMock.mockReturnValue(true);

const { queryByTestId } = render(
<AnalyticsProvider
newsroom={{
...DEFAULT_NEWSROOM,
tracking_policy: Newsroom.TrackingPolicy.DISABLED,
is_plausible_enabled: true,
}}
/>,
);

await waitFor(() =>
expect(queryByTestId('plausible-debug-enabled')).not.toBeInTheDocument(),
);
});

it('Does not load Plausible integration when Analytics are disabled entirely', async () => {
getConsentCookieMock.mockReturnValue(true);

const { queryByTestId } = render(
<AnalyticsProvider
newsroom={{ ...DEFAULT_NEWSROOM, is_plausible_enabled: true }}
isEnabled={false}
/>,
);

await waitFor(() =>
expect(queryByTestId('plausible-debug-enabled')).not.toBeInTheDocument(),
);
});

it('Works without Newsroom provided and shows a warning without segment write key', async () => {
getConsentCookieMock.mockReturnValue(true);
const consoleSpy = jest.spyOn(console, 'warn');

const { getByText } = render(
<AnalyticsProvider>
<TestingComponent />
</AnalyticsProvider>,
);
render(<AnalyticsProvider></AnalyticsProvider>);

expect(getByText(/analytics/i)).toHaveTextContent('enabled');
await waitFor(() =>
expect(consoleSpy).toHaveBeenCalledWith(
'Warning: You have not provided neither `newsroom`, nor `segmentWriteKey`. The library will not send any events.',
Expand All @@ -152,7 +41,6 @@ describe('AnalyticsProvider', () => {
});

it('Logs an error to console and fails gracefully when analytics fail to load', async () => {
getConsentCookieMock.mockReturnValue(true);
const consoleSpy = jest.spyOn(console, 'error');
const analyticsSpy = jest.spyOn(AnalyticsBrowser, 'load');

Expand All @@ -162,44 +50,47 @@ describe('AnalyticsProvider', () => {
throw error;
});

const { getByText } = render(
<AnalyticsProvider>
<TestingComponent />
</AnalyticsProvider>,
);
render(<AnalyticsProvider>test</AnalyticsProvider>);

expect(getByText(/analytics/i)).toHaveTextContent('enabled');
await waitFor(() =>
expect(consoleSpy).toHaveBeenCalledWith('Error while loading Analytics', error),
);
});
});

describe('useAnalyticsContext', () => {
it('should throw an error when not inside the AnalyticsContext provider', () => {
let caughtError: any = null;

try {
render(<TestingComponent />);
} catch (error) {
// TODO: This error is still logged to console, despite the `catch` block
caughtError = error;
}

expect(caughtError).toEqual(
Error('No `AnalyticsProvider` found when calling `useAnalyticsContext`'),
);
});

it('should return context value when inside AnalyticsContext provider', async () => {
getConsentCookieMock.mockReturnValue(true);
it('injects Prezly metadata to an event', async () => {
const track = jest.fn();

const { getByText } = render(
<AnalyticsProvider newsroom={DEFAULT_NEWSROOM}>
render(
<AnalyticsProvider
newsroom={DEFAULT_NEWSROOM}
story={{ uuid: 'story_uuid' }}
gallery={{ uuid: 'gallery_uuid' }}
plugins={[
{
name: '_',
type: 'after',
isLoaded: () => true,
load: () => Promise.resolve(),
track(ctx) {
track(ctx);
return ctx;
},
},
]}
>
<TestingComponent />
</AnalyticsProvider>,
);

expect(getByText(/analytics/i)).toHaveTextContent('enabled');
await waitFor(() => expect(track).toBeCalled());
const context = track.mock.lastCall[0];
expect(context).toHaveProperty('event');
expect(context.event).toHaveProperty('prezly');
expect(context.event.prezly).toEqual({
newsroom: DEFAULT_NEWSROOM.uuid,
story: 'story_uuid',
gallery: 'gallery_uuid',
tracking_policy: DEFAULT_NEWSROOM.tracking_policy,
});
});
});
Loading
Loading