Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/tiny-beans-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scaleway/use-analytics": patch
---

Create Analytics providers with rudderstack, add consent management
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@commitlint/cli": "catalog:",
"@commitlint/config-conventional": "catalog:",
"@eslint/eslintrc": "catalog:",
"@rudderstack/analytics-js": "catalog:",
"@scaleway/eslint-config-react": "workspace:*",
"@scaleway/tsconfig": "workspace:*",
"@testing-library/jest-dom": "catalog:",
Expand Down Expand Up @@ -72,7 +73,12 @@
"react-dom": "18 || 19",
"@types/react": "18 || 19"
}
}
},
"onlyBuiltDependencies": [
"@biomejs/biome",
"esbuild",
"unrs-resolver"
]
},
"commitlint": {
"extends": [
Expand Down
5 changes: 5 additions & 0 deletions packages/use-analytics/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/
coverage/
node_modules
.reports/
coverage/
10 changes: 10 additions & 0 deletions packages/use-analytics/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { join } = require('path')

module.exports = {
rules: {
'import/no-extraneous-dependencies': [
'error',
{ packageDir: [__dirname, join(__dirname, '../../')] },
],
},
}
5 changes: 5 additions & 0 deletions packages/use-analytics/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/__tests__/**
examples/
src
.eslintrc.cjs
!.npmignore
1 change: 1 addition & 0 deletions packages/use-analytics/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Change Log
96 changes: 96 additions & 0 deletions packages/use-analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# `@scaleway/use-analytics`

## A tiny hooks to handle analytics events

## Install

```bash
$ pnpm add @scaleway/use-analytics
```

## Usage

### Event directory

Create an events directory with all you specific events.

```
events
┣ pageTypes
┃ ┗ index.ts
┣ 📂loginEvent
┃ ┗ index.ts
┃ index.ts ( export all you functions )

```

Each event will have a format like this:

```typescript
const pageVisited =
(analytics?: Analytics) =>
async (args: Args): Promise<void> => {
// here do what you have to do with analytics
await analytics?.page(args)
}

export default pageVisited
```

```typescript
import pageTypes from './pageTypes'
import testEvents from './testEvents'

export default {
pageTypes,
testEvents,
}
```

### Context Load

Inside you global app you have to use our Analytics Provider to allow loading of analytics from your settting app.
This will trigger a load and return analitycs function inside you provider.

```javascript
import { AnalyticsProvider } from '@scaleway/use-analytics'
import { captureMessage } from '@sentry/browser'
import events from './events'

const App = () => (
<AnalyticsProvider
settings={{ cdn: 'https://cdn.url', writeKey: 'WRITE_KEY' }}
events={events}
onError={e => captureMessage(`Error on Analytics: ${e.message}`)}
>
<App />
</AnalyticsProvider>
)
```

### Hook utility

Now you maybe want to use your events inside your app .
If you are using typescript, you may

```typescript
import { useAnalytics } from '@scaleway/use-analytics'
import type { Events } from 'types/events'
import { Form, Submit } from '@scaleway/form'

const Login = () => {
const { events } = useAnalytics<Events>()

const onSubmit = async args => {
// make you api calls
await events.login()
}

return (
<Form onSubmit={onSubmit}>
// others fields
<Submit />
</Form>
)
}
```
64 changes: 64 additions & 0 deletions packages/use-analytics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "@scaleway/use-analytics",
"version": "0.0.0",
"description": "A small hook to handle events analytics",
"engines": {
"node": ">=20.x"
},
"main": "./dist/index.cjs",
"sideEffects": false,
"type": "module",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/analytics/index.d.ts",
"require": "./dist/analytics/index.cjs",
"default": "./dist/analytics/index.js"
},
"./cookies-consent": {
"types": "./dist/cookies-consent/index.d.ts",
"require": "./dist/cookies-consent/index.cjs",
"default": "./dist/cookies-consent/index.js"
}
},
"publishConfig": {
"access": "public"
},
"files": [
"dist/*"
],
"scripts": {
"prebuild": "shx rm -rf dist",
"typecheck": "tsc --noEmit",
"type:generate": "tsc --declaration -p tsconfig.build.json",
"build": "vite build --config vite.config.ts && pnpm run type:generate",
"build:profile": "npx vite-bundle-visualizer -c vite.config.ts",
"lint": "eslint --report-unused-disable-directives --cache --cache-strategy content --ext ts,tsx ."
},
"repository": {
"type": "git",
"url": "https://github.com/scaleway/scaleway-lib",
"directory": "packages/use-analytics"
},
"license": "MIT",
"keywords": [
"react",
"reactjs",
"hooks",
"segment",
"rudderstack"
],
"dependencies": {
"@rudderstack/analytics-js":"catalog:",
"@segment/analytics-next": "catalog:",
"cookie": "catalog:",
"use-deep-compare-effect": "catalog:"
},
"devDependencies": {
"react": "catalog:"
},
"peerDependencies": {
"react": "18.x || 19.x"
}
}
160 changes: 160 additions & 0 deletions packages/use-analytics/src/__tests__/AnalyticsProvider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// import { AnalyticsBrowser } from '@segment/analytics-next'
// import type { Context } from '@segment/analytics-next'
// import { RudderAnalytics } from '@rudderstack/analytics-js'
// import { render, screen, waitFor } from '@testing-library/react'
// import { describe, expect, it, vi } from 'vitest'
// import {AnalyticsProvider} from './'
// import type { Analytics } from './index'

// const TestChildren = () => <div data-testid="test">children</div>

// const defaultAnalytics = {} as Analytics

// describe('AnalyticsProvider', () => {
// it('Provider should render children when shouldRenderOnlyWhenReady is false', async () => {
// const mock = vi
// .spyOn(RudderAnalytics, 'load')
// .mockResolvedValue([defaultAnalytics, {} as Context])

// const settings = { writeKey: 'helloworld', cdnURL: '', timeout: 300 }

// render(
// <AnalyticsProvider
// settings={settings}
// initOptions={{}}
// areOptionsLoaded={false}
// events={{
// event: () => () => Promise.resolve(),
// }}
// >
// <TestChildren />
// </AnalyticsProvider>,
// )

// await waitFor(() => {
// expect(mock).toHaveBeenCalledTimes(0)
// })

// expect(screen.getByTestId('test')).toBeTruthy()
// })

// it('Provider should not render children when options are not loaded ', async () => {
// const mock = vi
// .spyOn(AnalyticsBrowser, 'load')
// .mockResolvedValue([{} as Analytics, {} as Context])

// const settings = { writeKey: 'helloworld' }

// render(
// <AnalyticsProvider
// settings={settings}
// initOptions={{}}
// areOptionsLoaded={false}
// shouldRenderOnlyWhenReady
// events={{
// event: () => () => Promise.resolve(),
// }}
// >
// <TestChildren />
// </AnalyticsProvider>,
// )

// await waitFor(() => {
// expect(mock).toHaveBeenCalledTimes(0)
// })

// expect(screen.queryByTestId('test')).toBe(null)
// })

// it('Provider should not render children when options are not loaded at first render, but load after options changed', async () => {
// const mock = vi
// .spyOn(AnalyticsBrowser, 'load')
// .mockResolvedValue([{} as Analytics, {} as Context])

// const settings = { writeKey: 'helloworld' }

// const { rerender } = render(
// <AnalyticsProvider
// settings={settings}
// initOptions={{}}
// areOptionsLoaded={false}
// shouldRenderOnlyWhenReady
// events={{
// event: () => () => Promise.resolve(),
// }}
// >
// <TestChildren />
// </AnalyticsProvider>,
// )

// await waitFor(() => {
// expect(mock).toHaveBeenCalledTimes(0)
// })

// expect(screen.queryByTestId('test')).toBe(null)

// rerender(
// <AnalyticsProvider
// settings={settings}
// initOptions={{}}
// areOptionsLoaded
// shouldRenderOnlyWhenReady
// events={{
// event: () => () => Promise.resolve(),
// }}
// >
// <TestChildren />
// </AnalyticsProvider>,
// )

// await waitFor(() => {
// expect(mock).toHaveBeenCalledTimes(1)
// })

// expect(screen.queryByTestId('test')).toBeTruthy()
// })

// it('Provider should not render children when options are not loaded at first render, but load after options changed even without settings', async () => {
// const mock = vi
// .spyOn(AnalyticsBrowser, 'load')
// .mockResolvedValue([{} as Analytics, {} as Context])

// const { rerender } = render(
// <AnalyticsProvider
// initOptions={{}}
// areOptionsLoaded={false}
// shouldRenderOnlyWhenReady
// events={{
// event: () => () => Promise.resolve(),
// }}
// >
// <TestChildren />
// </AnalyticsProvider>,
// )

// await waitFor(() => {
// expect(mock).toHaveBeenCalledTimes(0)
// })

// expect(screen.queryByTestId('test')).toBe(null)

// rerender(
// <AnalyticsProvider
// initOptions={{}}
// areOptionsLoaded
// shouldRenderOnlyWhenReady
// events={{
// event: () => () => Promise.resolve(),
// }}
// >
// <TestChildren />
// </AnalyticsProvider>,
// )

// await waitFor(() => {
// expect(mock).toHaveBeenCalledTimes(0)
// })

// expect(screen.queryByTestId('test')).toBeTruthy()
// })
// })
Loading
Loading