Skip to content

Commit

Permalink
Migration to Google Analytics 4 (#471)
Browse files Browse the repository at this point in the history
  • Loading branch information
antoinejaussoin authored Feb 8, 2023
1 parent 811034b commit f336643
Show file tree
Hide file tree
Showing 29 changed files with 487 additions and 84 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,6 @@ RATE_LIMIT_WINDOW=900000
RATE_LIMIT_MAX=500
RATE_LIMIT_WS_POINTS=600
RATE_LIMIT_WS_DURATION=60
WS_MAX_BUFFER_SIZE=10000
WS_MAX_BUFFER_SIZE=10000
GA4_SECRET=
GA4_MEASUREMENT_ID=G-XXXXXXXXXX
2 changes: 1 addition & 1 deletion .github/workflows/alpha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: 'Alpha Build'

on:
push:
branches: [v4190/release]
branches: [v4192/ga4]

jobs:
build:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ This will run a demo version, which you can turn into a fully licenced version b

## Versions History

### Version 4.19.2

- Migration to Google Analytics V4

### Version 4.19.1

- Hotfix: Issue with password account creation
Expand Down
4 changes: 3 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@retrospected/backend",
"version": "4.19.1",
"version": "4.19.2",
"license": "GNU GPLv3",
"private": true,
"type": "module",
Expand Down Expand Up @@ -42,6 +42,7 @@
"@types/passport-local": "1.0.34",
"@types/passport-microsoft": "0.0.0",
"@types/passport-twitter": "1.0.37",
"@types/request": "^2.48.8",
"@types/shortid": "0.0.29",
"@types/socket.io-redis": "3.0.0",
"@types/uuid": "9.0.0",
Expand Down Expand Up @@ -83,6 +84,7 @@
"prettier": "2.8.1",
"rate-limiter-flexible": "2.4.1",
"redis": "3.1.2",
"request": "^2.88.2",
"rimraf": "3.0.2",
"scripty": "^2.1.1",
"shortid": "2.2.16",
Expand Down
6 changes: 6 additions & 0 deletions backend/src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ export type TrackingEvent =
| 'trial/modal/open'
| 'ack/error'
| 'ack/refresh'
| 'register/password'
| 'register/oauth'
| 'register/anonymous'
| 'subscribe/initial'
| 'subscribe/launch-stripe'
| 'subscribe/purchased'
| 'language/change/';

export type ColumnDefinitionType =
Expand Down
2 changes: 2 additions & 0 deletions backend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ const config: BackendConfig = {
'MAIL_ALLOW_SELF_SIGNED_CERTS',
false
),
GA4_SECRET: defaults('GA4_SECRET', ''),
GA4_MEASUREMENT_ID: defaults('GA4_MEASUREMENT_ID', ''),
};

export default config;
10 changes: 10 additions & 0 deletions backend/src/db/actions/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ export async function getUserByUsername(
});
}

export async function getUserByEmail(
email: string
): Promise<UserEntity | null> {
return await transaction(async (manager) => {
const userRepository = manager.withRepository(UserRepository);
const user = await userRepository.findOne({ where: { email } });
return user ? user : null;
});
}

export async function updateIdentity(
identityId: string,
updatedIdentity: Partial<UserIdentityEntity>
Expand Down
9 changes: 0 additions & 9 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,15 +615,6 @@ db().then(() => {
}
});

// app.get('/api/test', async (req, res) => {
// await sendSelfHostWelcome(
// '[email protected]',
// 'Antoine J',
// 'BLAH-BLAH-BLAH'
// );
// res.send('done');
// });

setupSentryErrorHandler(app);
});

Expand Down
18 changes: 17 additions & 1 deletion backend/src/stripe/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
SubscriptionDeletedPayload,
} from './types.js';
import { plans, getProduct } from './products.js';
import { updateUser } from '../db/actions/users.js';
import { getUserByEmail, updateUser } from '../db/actions/users.js';
import { registerLicence } from '../db/actions/licences.js';
import { getIdentityFromRequest } from '../utils.js';
import isValidDomain from '../security/is-valid-domain.js';
Expand All @@ -25,6 +25,7 @@ import {
startTrial,
getActiveSubscriptionWhereUserIsAdmin,
} from '../db/actions/subscriptions.js';
import { trackPurchase } from './../track/track.js';

const stripe = new Stripe(config.STRIPE_SECRET, {
apiVersion: '2022-11-15',
Expand Down Expand Up @@ -137,13 +138,28 @@ function stripeRouter(): Router {
const customerEmail = session.customer_details?.email || null;
const stripeCustomerId = session.customer! as string;
const stripeSessionId = session.id;
const user = customerEmail ? await getUserByEmail(customerEmail) : null;

const customer = (await stripe.customers.retrieve(
stripeCustomerId
)) as Stripe.Response<Stripe.Customer>;
const customerName = customer.name;

if (subEvent.data.object.payment_status === 'paid') {
if (session.line_items && session.line_items.data[0]) {
const lineItem = session.line_items.data[0];
trackPurchase(
stripeCustomerId,
user ? user.id : stripeCustomerId,
session.id,
(lineItem.price?.product as string | null) || lineItem.id,
lineItem.description,
lineItem.quantity || 0,
lineItem.currency,
lineItem.amount_total / 100
);
}

if (
product &&
product.product === config.STRIPE_SELF_HOSTED_PRODUCT
Expand Down
1 change: 1 addition & 0 deletions backend/src/track/GA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://developers.google.com/analytics/devguides/collection/protocol/ga4/reference?client_type=gtag#payload_post_body
97 changes: 97 additions & 0 deletions backend/src/track/track.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import config from './../config.js';
import request from 'request';
import chalkTemplate from 'chalk-template';

export function trackPurchase(
/**
* Uniquely identifies a user instance of a web client
*/
clientId: string,
/**
* A unique identifier for a user, cross device and platform
*/
userId: string,
transactionId: string,
productId: string,
productName: string,
quantity: number,
currency: string,
value: number
) {
const measurementId = config.GA4_MEASUREMENT_ID;
const secret = config.GA4_SECRET;

if (!measurementId || !secret) {
console.log(chalkTemplate`{red GA4 not configured}`);
return;
}

type EventPayload = {
/**
* Uniquely identifies a user instance of a web client
*/
client_id: string;
/**
* A unique identifier for a user, cross device and platform
*/
user_id?: string;
events: PurchaseEvent[];
};

type PurchaseEvent = {
name: string;
params: PurchaseEventParams;
};

type PurchaseEventParams = {
currency?: string;
value?: number;
transaction_id: string;
items: Item[];
};

type Item = {
item_id: string;
item_name: string;
quantity: number;
};

const payload: EventPayload = {
client_id: clientId,
user_id: userId,
events: [
{
name: 'purchase',
params: {
transaction_id: transactionId,
currency: currency,
value: value,
items: [
{ item_id: productId, item_name: productName, quantity: quantity },
],
},
},
],
};

console.log('Sending to GA: ', JSON.stringify(payload, null, 2));

request.post(
{
url:
'https://www.google-analytics.com/mp/collect?api_secret=' +
secret +
'&measurement_id=' +
measurementId,
body: JSON.stringify(payload),
},
function (err, res) {
console.log('Res: ', res.body);
if (err) {
console.error(err);
} else {
console.log('GA4 event sent successfully');
}
}
);
}
2 changes: 2 additions & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export interface BackendConfig {
MAIL_PASSWORD: string;
MAIL_SENDER: string;
MAIL_ALLOW_SELF_SIGNED_CERTS: boolean;
GA4_MEASUREMENT_ID: string;
GA4_SECRET: string;
}

export type LicenceMetadata = {
Expand Down
Loading

0 comments on commit f336643

Please sign in to comment.