Skip to content

Commit

Permalink
feat(next): glean emit view event
Browse files Browse the repository at this point in the history
Because:

- Record SubPlat P1 metric view event using glean

This commit:

- Adds payments-metrics library
- Adds PaymentsGleanManager to format events data and CMS data into the
  required format for Glean events recording.
- Adds PaymentsGleanService to handle metrics events emitted by Next.js
- Adds the manager and service to the NestApp
- Adds new config values to payments-next for Glean reporting
- Emit view event from Checkout start page

Closes #FXA-10087
  • Loading branch information
StaberindeZA committed Sep 19, 2024
1 parent 1328ec8 commit 7dd569d
Show file tree
Hide file tree
Showing 51 changed files with 1,086 additions and 26 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ packages/fxa-settings/test/

# payments-next
apps/payments/next/public/locales
apps/payments/next/lib/metrics/glean/server_events.ts

# payments-metrics
libs/payments/metrics/src/lib/glean/__generated__/server_events.ts

Library
.node
Expand Down
6 changes: 6 additions & 0 deletions apps/payments/next/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ STATS_D_CONFIG__HOST=
STATS_D_CONFIG__PORT=
STATS_D_CONFIG__PREFIX=

# Glean Config
GLEAN_CONFIG__APPLICATION_ID=
# GLEAN_CONFIG__VERSION= # Set in next.config.js
GLEAN_CONFIG__CHANNEL='development'
GLEAN_CONFIG__LOGGER_APP_NAME='fxa-payments-next'

# CSP Config
CSP__ACCOUNTS_STATIC_CDN=https://accounts-static.cdn.mozilla.net
CSP__PAYPAL_API='https://www.sandbox.paypal.com'
Expand Down
6 changes: 6 additions & 0 deletions apps/payments/next/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ STATS_D_CONFIG__HOST=
STATS_D_CONFIG__PORT=
STATS_D_CONFIG__PREFIX=

# Glean Config
GLEAN_CONFIG__APPLICATION_ID=
# GLEAN_CONFIG__VERSION= # Set in next.config.js
GLEAN_CONFIG__CHANNEL='production'
GLEAN_CONFIG__LOGGER_APP_NAME='fxa-payments-next'

# CSP Config
CSP__ACCOUNTS_STATIC_CDN=https://accounts-static.cdn.mozilla.net
CSP__PAYPAL_API='https://www.paypal.com'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ import {
} from 'apps/payments/next/app/_lib/apiClient';
import { auth, signIn } from 'apps/payments/next/auth';
import { CheckoutParams } from '../layout';
import { getCommonGleanMetrics } from 'apps/payments/next/lib/utils/getCommonGleanMetrics';

export const dynamic = 'force-dynamic';

export default async function Checkout({ params }: { params: CheckoutParams }) {
export default async function Checkout({
params,
searchParams,
}: {
params: CheckoutParams;
searchParams: Record<string, string>;
}) {
// Temporarily defaulting to `accept-language`
// This to be updated in FXA-9404
//const locale = getLocaleFromRequest(
Expand All @@ -44,6 +51,17 @@ export default async function Checkout({ params }: { params: CheckoutParams }) {
cmsPromise,
]);

getApp()
.getGleanEmitter()
.emit('fxaPaySetupView', {
currency: 'USD',
checkoutType: 'without-accounts',
params: { ...params },
searchParams,
...cart,
...getCommonGleanMetrics(),
});

return (
<section>
{!session && (
Expand Down
8 changes: 7 additions & 1 deletion apps/payments/next/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,10 @@ export class PaymentsNextConfig extends NestAppRootConfig {
nextPublicSentryTracesSampleRate!: number;
}

export const config = validate(process.env, PaymentsNextConfig);
export const config = validate(
{
...process.env,
GLEAN_CONFIG__VERSION: process.env['GLEAN_CONFIG__VERSION'],
},
PaymentsNextConfig
);
16 changes: 16 additions & 0 deletions apps/payments/next/lib/utils/getCommonGleanMetrics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { headers } from 'next/headers';
import { userAgentFromString } from 'next/server';
import { getIpAddress } from './getIpAddress';

export function getCommonGleanMetrics() {
const userAgentString = headers().get('user-agent') || '';
const userAgent = userAgentFromString(userAgentString);
return {
ipAddress: getIpAddress(),
userAgent: userAgentString,
deviceType: userAgent.device.type || 'desktop',
};
}
9 changes: 9 additions & 0 deletions apps/payments/next/lib/utils/getIpAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import 'server-only';
import { headers } from 'next/headers';

export function getIpAddress() {
return (headers().get('x-forwarded-for') ?? '127.0.0.1').split(',')[0];
}
2 changes: 2 additions & 0 deletions apps/payments/next/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const nextConfig = {
},
env: {
version,
GLEAN_CONFIG__VERSION: version,
},
experimental: {
instrumentationHook: true,
Expand All @@ -34,6 +35,7 @@ const nextConfig = {
'hot-shots',
'knex',
'kysely',
'mozlog',
'mysql2',
'nest-typed-config',
'superagent',
Expand Down
2 changes: 1 addition & 1 deletion apps/payments/next/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
},
"glean-generate": {
"dependsOn": ["glean-lint"],
"command": "yarn glean translate libs/shared/metrics/glean/src/registry/subplat-backend-metrics.yaml -f typescript_server -o apps/payments/next/lib/metrics/glean"
"command": "yarn glean translate libs/shared/metrics/glean/src/registry/subplat-backend-metrics.yaml -f typescript_server -o libs/payments/metrics/src/lib/glean/__generated__"
},
"glean-lint": {
"command": "yarn glean glinter libs/shared/metrics/glean/src/registry/subplat-backend-metrics.yaml"
Expand Down
18 changes: 18 additions & 0 deletions libs/payments/metrics/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
15 changes: 15 additions & 0 deletions libs/payments/metrics/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"jsc": {
"target": "es2017",
"parser": {
"syntax": "typescript",
"decorators": true,
"dynamicImport": true
},
"transform": {
"decoratorMetadata": true,
"legacyDecorator": true
}
}
}

15 changes: 15 additions & 0 deletions libs/payments/metrics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# payments-metrics

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build payments-metrics` to build the library.

## Running unit tests

Run `nx run payments-metrics:test-unit` to execute the unit tests via [Jest](https://jestjs.io).

## Running integration tests

Run `nx run payments-metrics:test-integration` to execute the integration tests via [Jest](https://jestjs.io).
30 changes: 30 additions & 0 deletions libs/payments/metrics/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint-disable */
import { readFileSync } from 'fs';

// Reading the SWC compilation config and remove the "exclude"
// for the test files to be compiled by SWC
const { exclude: _, ...swcJestConfig } = JSON.parse(
readFileSync(`${__dirname}/.swcrc`, 'utf-8')
);

// disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves.
// If we do not disable this, SWC Core will read .swcrc and won't transform our test files due to "exclude"
if (swcJestConfig.swcrc === undefined) {
swcJestConfig.swcrc = false;
}

// Uncomment if using global setup/teardown files being transformed via swc
// https://nx.dev/packages/jest/documents/overview#global-setup/teardown-with-nx-libraries
// jest needs EsModule Interop to find the default exported setup/teardown functions
// swcJestConfig.module.noInterop = false;

export default {
displayName: 'payments-metrics',
preset: '../../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['@swc/jest', swcJestConfig],
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../../coverage/libs/payments/metrics',
};
8 changes: 8 additions & 0 deletions libs/payments/metrics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@fxa/payments/metrics",
"version": "0.0.1",
"dependencies": {},
"type": "commonjs",
"main": "./index.cjs",
"private": true
}
45 changes: 45 additions & 0 deletions libs/payments/metrics/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "payments-metrics",
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/payments/metrics/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/payments/metrics",
"main": "libs/payments/metrics/src/index.ts",
"tsConfig": "libs/payments/metrics/tsconfig.lib.json",
"assets": ["libs/payments/metrics/*.md"],
"declaration": true
},
"dependsOn": ["glean-generate"]
},
"glean-generate": {
"dependsOn": ["glean-lint"],
"command": "yarn glean translate libs/shared/metrics/glean/src/registry/subplat-backend-metrics.yaml -f typescript_server -o libs/payments/metrics/src/lib/glean/__generated__"
},
"glean-lint": {
"command": "yarn glean glinter libs/shared/metrics/glean/src/registry/subplat-backend-metrics.yaml"
},
"test-unit": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/payments/metrics/jest.config.ts",
"testPathPattern": ["^(?!.*\\.in\\.spec\\.ts$).*$"]
},
"dependsOn": ["glean-generate"]
},
"test-integration": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/payments/metrics/jest.config.ts",
"testPathPattern": ["\\.in\\.spec\\.ts$"]
}
}
}
}
8 changes: 8 additions & 0 deletions libs/payments/metrics/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
export * from './lib/glean/glean.types';
export * from './lib/glean/glean.manager';
export * from './lib/glean/glean.service';
export * from './lib/glean/glean.config';
export * from './lib/glean/glean.provider';
24 changes: 24 additions & 0 deletions libs/payments/metrics/src/lib/glean/glean.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { IsEnum, IsString } from 'class-validator';

enum GleanChannel {
Development = 'development',
Stage = 'stage',
Production = 'production',
}

export class PaymentsGleanConfig {
@IsString()
applicationId!: string;

@IsString()
version!: string;

@IsEnum(GleanChannel)
channel!: string;

@IsString()
loggerAppName!: string;
}
85 changes: 85 additions & 0 deletions libs/payments/metrics/src/lib/glean/glean.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { faker } from '@faker-js/faker';
import {
CartMetrics,
CmsMetricsData,
CommonMetrics,
FxaPaySetupMetrics,
FxaPaySetupViewMetrics,
} from './glean.types';
import { ResultCartFactory } from '@fxa/payments/cart';

export const ParamsFactory = (
override?: Record<string, string>
): Record<string, string> => ({
locale: faker.helpers.arrayElement(['en-US', 'de', 'es', 'fr-FR']),
offeringId: faker.helpers.arrayElement([
'vpn',
'relay-phone',
'relay-email',
'hubs',
'mdnplus',
]),
interval: faker.helpers.arrayElement([
'daily',
'weekly',
'monthly',
'6monthly',
'yearly',
]),
...override,
});

export const CommonMetricsFactory = (
override?: Partial<CommonMetrics>
): CommonMetrics => ({
ipAddress: faker.internet.ip(),
deviceType: faker.string.alphanumeric(),
userAgent: faker.internet.userAgent(),
params: {},
searchParams: {},
...override,
});

export const CartMetricsFactory = (
override?: Partial<CartMetrics>
): CartMetrics => {
const resultCart = ResultCartFactory();

return {
uid: resultCart.uid,
errorReasonId: resultCart.errorReasonId,
couponCode: resultCart.couponCode,
currency: faker.finance.currencyCode(),
...override,
};
};

export const FxaPaySetupMetricsFactory = (
override?: Partial<FxaPaySetupMetrics>
): FxaPaySetupMetrics => ({
...CommonMetricsFactory(),
...CartMetricsFactory(),
...override,
});

export const FxaPaySetupViewMetricsFactory = (
override?: Partial<FxaPaySetupViewMetrics>
): FxaPaySetupViewMetrics => ({
...FxaPaySetupMetricsFactory(),
checkoutType: faker.helpers.arrayElement([
'with-accounts',
'without-accounts',
]),
...override,
});

export const CmsMetricsDataFactory = (
override?: Partial<CmsMetricsData>
): CmsMetricsData => ({
productId: `product_${faker.string.alphanumeric({ length: 14 })}`,
priceId: `price_${faker.string.alphanumeric({ length: 14 })}`,
...override,
});
Loading

0 comments on commit 7dd569d

Please sign in to comment.