Skip to content

Commit

Permalink
feat(wallet-dashboard): Integrate Amplitude (#4930)
Browse files Browse the repository at this point in the history
* feat(dashboard): Integrate Amplitude

* manypkg

* prettier fix dashboard

* missing header

* feat(dashboard): create prependLicense file in dashboard

* fix(explorer): update paths to pull amplitude in explorer scripts

---------

Co-authored-by: cpl121 <[email protected]>
  • Loading branch information
marc2332 and cpl121 authored Jan 30, 2025
1 parent c996846 commit acd4e8b
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 1 deletion.
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.

0 comments on commit acd4e8b

Please sign in to comment.