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

feat(wallet-dashboard): Integrate Amplitude #4930

Merged
merged 9 commits into from
Jan 30, 2025
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
14 changes: 14 additions & 0 deletions apps/wallet-dashboard/ampli.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Zone": "eu",
"OrgId": "100007351",
"WorkspaceId": "72fb85fc-aed9-46ef-83a2-9345888a1678",
"SourceId": "ca44ad20-3cfd-4618-aa11-4b8befb0b123",
"Branch": "main",
"Version": "1.0.0",
"VersionId": "954386e3-441d-4aa5-b9ad-1f01e0a20e55",
"Runtime": "browser:typescript-ampli-v2",
"Platform": "Browser",
"Language": "TypeScript",
"SDK": "@amplitude/analytics-browser@^1.0",
"Path": "./lib/utils/analytics/ampli"
}
2 changes: 2 additions & 0 deletions apps/wallet-dashboard/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Metadata } from 'next';
import { AppProviders } from '@/providers';
import { FontLinks } from '@/components/FontLinks';
import { ConnectionGuard } from '@/components/connection-guard';
import { Amplitude } from '@/components/Amplitude';

const inter = Inter({ subsets: ['latin'] });

Expand All @@ -26,6 +27,7 @@ export default function RootLayout({
<body className={inter.className}>
<AppProviders>
<FontLinks />
<Amplitude />
<ConnectionGuard>{children}</ConnectionGuard>
</AppProviders>
</body>
Expand Down
24 changes: 24 additions & 0 deletions apps/wallet-dashboard/components/Amplitude.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

'use client';

import { ampli, initAmplitude } from '@/lib/utils/analytics';
import { useEffect } from 'react';

async function load() {
await initAmplitude();
await ampli.openedWalletDashboard({
pagePath: location.pathname,
pagePathFragment: `${location.pathname}${location.search}${location.hash}`,
walletDashboardRev: process.env.NEXT_PUBLIC_DASHBOARD_REV,
});
}

export function Amplitude() {
useEffect(() => {
load();
}, []);

return null;
}
1 change: 1 addition & 0 deletions apps/wallet-dashboard/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './VirtualList';
export * from './ExternalImage';
export * from './PageSizeSelector';
export * from './PaginationOptions';
export * from './Amplitude';

export * from './account-balance/AccountBalance';
export * from './coins';
Expand Down
221 changes: 221 additions & 0 deletions apps/wallet-dashboard/lib/utils/analytics/ampli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
/**
* Ampli - A strong typed wrapper for your Analytics
*
* This file is generated by Amplitude.
* To update run 'ampli pull web'
*
* Required dependencies: @amplitude/analytics-browser@^1.3.0
* Tracking Plan Version: 1
* Build: 1.0.0
* Runtime: browser:typescript-ampli-v2
*
* [View Tracking Plan](https://data.eu.amplitude.com/iota-foundation/IOTA%20Wallet%20Dashboard/events/main/latest)
*
* [Full Setup Instructions](https://data.eu.amplitude.com/iota-foundation/IOTA%20Wallet%20Dashboard/implementation/web)
*/

import * as amplitude from '@amplitude/analytics-browser';

export type Environment = 'iotawalletdashboard';

export const ApiKey: Record<Environment, string> = {
iotawalletdashboard: '4d570cb7dc58e267349bde33f7b8bdeb',
};

/**
* Default Amplitude configuration options. Contains tracking plan information.
*/
export const DefaultConfiguration: BrowserOptions = {
plan: {
version: '1',
branch: 'main',
source: 'web',
versionId: '954386e3-441d-4aa5-b9ad-1f01e0a20e55',
},
...{
ingestionMetadata: {
sourceName: 'browser-typescript-ampli',
sourceVersion: '2.0.0',
},
},
serverZone: amplitude.Types.ServerZone.EU,
};

export interface LoadOptionsBase {
disabled?: boolean;
}

export type LoadOptionsWithEnvironment = LoadOptionsBase & {
environment: Environment;
client?: { configuration?: BrowserOptions };
};
export type LoadOptionsWithApiKey = LoadOptionsBase & {
client: { apiKey: string; configuration?: BrowserOptions };
};
export type LoadOptionsWithClientInstance = LoadOptionsBase & {
client: { instance: BrowserClient };
};

export type LoadOptions =
| LoadOptionsWithEnvironment
| LoadOptionsWithApiKey
| LoadOptionsWithClientInstance;

export interface OpenedWalletDashboardProperties {
activeNetwork?: string;
activeOrigin?: string;
pagePath?: string;
pagePathFragment?: string;
walletDashboardRev?: string;
}

export class OpenedWalletDashboard implements BaseEvent {
event_type = 'Opened wallet dashboard';

constructor(public event_properties?: OpenedWalletDashboardProperties) {
this.event_properties = event_properties;
}
}

export type PromiseResult<T> = { promise: Promise<T | void> };

const getVoidPromiseResult = () => ({ promise: Promise.resolve() });

// prettier-ignore
export class Ampli {
private disabled: boolean = false;
private amplitude?: BrowserClient;

get client(): BrowserClient {
this.isInitializedAndEnabled();
return this.amplitude!;
}

get isLoaded(): boolean {
return this.amplitude != null;
}

private isInitializedAndEnabled(): boolean {
if (!this.amplitude) {
console.error('ERROR: Ampli is not yet initialized. Have you called ampli.load() on app start?');
return false;
}
return !this.disabled;
}

/**
* Initialize the Ampli SDK. Call once when your application starts.
*
* @param options Configuration options to initialize the Ampli SDK with.
*/
load(options: LoadOptions): PromiseResult<void> {
this.disabled = options.disabled ?? false;

if (this.amplitude) {
console.warn('WARNING: Ampli is already intialized. Ampli.load() should be called once at application startup.');
return getVoidPromiseResult();
}

let apiKey: string | null = null;
if (options.client && 'apiKey' in options.client) {
apiKey = options.client.apiKey;
} else if ('environment' in options) {
apiKey = ApiKey[options.environment];
}

if (options.client && 'instance' in options.client) {
this.amplitude = options.client.instance;
} else if (apiKey) {
this.amplitude = amplitude.createInstance();
const configuration = (options.client && 'configuration' in options.client) ? options.client.configuration : {};
return this.amplitude.init(apiKey, undefined, { ...DefaultConfiguration, ...configuration });
} else {
console.error("ERROR: ampli.load() requires 'environment', 'client.apiKey', or 'client.instance'");
}

return getVoidPromiseResult();
}

/**
* Identify a user and set user properties.
*
* @param userId The user's id.
* @param options Optional event options.
*/
identify(
userId: string | undefined,
options?: EventOptions,
): PromiseResult<Result> {
if (!this.isInitializedAndEnabled()) {
return getVoidPromiseResult();
}

if (userId) {
options = {...options, user_id: userId};
}

const amplitudeIdentify = new amplitude.Identify();
return this.amplitude!.identify(
amplitudeIdentify,
options,
);
}

/**
* Flush the event.
*/
flush() : PromiseResult<Result> {
if (!this.isInitializedAndEnabled()) {
return getVoidPromiseResult();
}

return this.amplitude!.flush();
}

/**
* Track event
*
* @param event The event to track.
* @param options Optional event options.
*/
track(event: Event, options?: EventOptions): PromiseResult<Result> {
if (!this.isInitializedAndEnabled()) {
return getVoidPromiseResult();
}

return this.amplitude!.track(event, undefined, options);
}

/**
* Opened wallet dashboard
*
* [View in Tracking Plan](https://data.eu.amplitude.com/iota-foundation/IOTA%20Wallet%20Dashboard/events/main/latest/Opened%20wallet%20dashboard)
*
* Event has no description in tracking plan.
*
* @param properties The event's properties (e.g. activeNetwork)
* @param options Amplitude event options.
*/
openedWalletDashboard(
properties?: OpenedWalletDashboardProperties,
options?: EventOptions,
) {
return this.track(new OpenedWalletDashboard(properties), options);
}
}

export const ampli = new Ampli();

// BASE TYPES
type BrowserOptions = amplitude.Types.BrowserOptions;

export type BrowserClient = amplitude.Types.BrowserClient;
export type BaseEvent = amplitude.Types.BaseEvent;
export type IdentifyEvent = amplitude.Types.IdentifyEvent;
export type GroupEvent = amplitude.Types.GroupIdentifyEvent;
export type Event = amplitude.Types.Event;
export type EventOptions = amplitude.Types.EventOptions;
export type Result = amplitude.Types.Result;
32 changes: 32 additions & 0 deletions apps/wallet-dashboard/lib/utils/analytics/amplitude.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import * as amplitude from '@amplitude/analytics-browser';
import { LogLevel, TransportType, type UserSession } from '@amplitude/analytics-types';
import { PersistableStorage } from '@iota/core';

import { ampli } from './ampli';

const IS_PROD_ENV = process.env.NODE_ENV == 'production';

export const persistableStorage = new PersistableStorage<UserSession>();

export async function initAmplitude() {
await ampli.load({
environment: 'iotawalletdashboard',
// Flip this if you'd like to test Amplitude locally
disabled: !IS_PROD_ENV,
client: {
configuration: {
cookieStorage: persistableStorage,
logLevel: IS_PROD_ENV ? LogLevel.Warn : amplitude.Types.LogLevel.Debug,
},
},
});

window.addEventListener('pagehide', () => {
amplitude.setTransport(TransportType.SendBeacon);
amplitude.flush();
});
}
5 changes: 5 additions & 0 deletions apps/wallet-dashboard/lib/utils/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

export * from './ampli';
export * from './amplitude';
7 changes: 6 additions & 1 deletion apps/wallet-dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
"lint": "next lint && pnpm run prettier:check",
"prettier:check": "prettier -c --ignore-unknown --ignore-path=../../.prettierignore --ignore-path=.prettierignore .",
"prettier:fix": "prettier -w --ignore-unknown --ignore-path=../../.prettierignore --ignore-path=.prettierignore .",
"test": "jest"
"test": "jest",
"ampli": "ampli",
"pull-amplitude": "ampli pull web && node prependLicense.mjs && prettier -w ampli.json lib/utils/analytics/ampli/index.ts"
},
"engines": {
"node": ">= 20"
},
"dependencies": {
"@amplitude/analytics-browser": "^1.10.3",
"@amplitude/analytics-types": "^0.20.0",
"@growthbook/growthbook": "^1.0.0",
"@growthbook/growthbook-react": "^1.0.0",
"@iota/apps-ui-icons": "workspace:*",
Expand All @@ -34,6 +38,7 @@
"zustand": "^4.4.1"
},
"devDependencies": {
"@amplitude/ampli": "^1.31.5",
"@tanstack/react-query-devtools": "^5.58.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.14.10",
Expand Down
15 changes: 15 additions & 0 deletions apps/wallet-dashboard/prependlicense.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

import { readFile, writeFile } from 'fs/promises';

const LICENSE =
'// Copyright (c) Mysten Labs, Inc.\n// Modifications Copyright (c) 2024 IOTA Stiftung\n// SPDX-License-Identifier: Apache-2.0\n\n';

async function prependLicense(filename) {
const content = await readFile(filename, 'utf8');
writeFile(filename, LICENSE + content);
}

prependLicense('lib/utils/analytics/ampli/index.ts');
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading