diff --git a/apps/builder/ANALYTICS_FEATURE_FLAG.md b/apps/builder/ANALYTICS_FEATURE_FLAG.md
index a0493c92..8d013310 100644
--- a/apps/builder/ANALYTICS_FEATURE_FLAG.md
+++ b/apps/builder/ANALYTICS_FEATURE_FLAG.md
@@ -93,13 +93,29 @@ The analytics feature flag controls:
### Tracked Events
-When analytics is enabled, the following user interactions **within the UI Builder** are tracked:
-
-- **Ecosystem Selection**: When users select blockchain ecosystems (EVM, Solana, etc.) in the builder
-- **Network Selection**: When users choose specific networks within ecosystems in the builder
-- **Export Actions**: When users click the "Export" button to generate standalone applications
-- **Wizard Progress**: Each step progression through the form builder wizard interface
-- **Sidebar Interactions**: Import/Export button clicks in the storage sidebar of the builder
+When analytics is enabled, the following GA4 events are sent from **within the UI Builder**. All
+events are fired from `src/hooks/useBuilderAnalytics.ts` (builder-specific) or the shared
+`useAnalytics` hook from `@openzeppelin/ui-react` (`page_view`, `network_selected`).
+
+Every string parameter is normalised to `"unknown"` when the value is missing or empty, so GA4
+event-scoped custom dimensions are never dropped for lack of a value.
+
+| Event | Parameters | Fires when | Call site |
+| ---------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
+| `page_view` | `page_title`, `page_location` (GA defaults) | Once on initial load, automatically by `gtag('config')`. No manual `trackPageView` calls. | `AnalyticsProvider` in `App.tsx` |
+| `ecosystem_selected` | `ecosystem` | User picks an ecosystem in the chain selector, or a deep link changes the ecosystem | `ChainSelector.tsx`, `useBuilderLifecycle.ts` |
+| `network_selected` | `network_id`, `ecosystem` | User picks a network, or a deep link resolves a network | `ChainSelector.tsx`, `useBuilderLifecycle.ts` |
+| `wizard_step` | `step_number` (1-indexed), `step_name`, `network_id`, `ecosystem` | Once per Next/Back click; describes the step being entered | `Common/WizardLayout.tsx` |
+| `export_clicked` | `export_type`, `network_id`, `ecosystem` | Once after an app export succeeds | `UIBuilder/hooks/useCompleteStepState.ts` |
+| `sidebar_interaction` | `action` (`import` \| `export`), `network_id`, `ecosystem` | Once per sidebar Import/Export click | `Sidebar/AppSidebar/MainActions.tsx` |
+| `transaction_executed` | `network_id`, `ecosystem`, `execution_method` | A transaction succeeds from the form preview | `StepFormCustomization/FormPreview.tsx` |
+| `contract_ui_created` | `network_id`, `ecosystem`, `total_records` | Once when a new Contract UI record is first persisted by auto-save | `UIBuilder/hooks/builder/useAutoSave.ts` |
+| `relayer_service_configured` | `network_id`, `ecosystem` | Once per mount when relayer URL, API key and relayer selection are all set | `StepFormCustomization/components/RelayerConfiguration/index.tsx` |
+| `uikit_changed` | `network_id`, `ecosystem`, `uikit_name` | User selects a UI kit in builder settings | `StepFormCustomization/components/UiKitSettings.tsx` |
+| `address_book_opened` | `network_id`, `ecosystem` | Once when the address book dialog opens (not on network changes while open) | `AddressBook/AddressBookDialog.tsx` |
+
+GA4 event-scoped custom dimensions to register: `ecosystem`, `network_id`, `step_name`,
+`step_number`, `export_type`, `action`, `execution_method`, `uikit_name`, `total_records`.
**Important:** These analytics only track user behavior within the builder tool itself. The standalone applications that users export do not contain any analytics tracking.
diff --git a/apps/builder/src/components/Common/WizardLayout.tsx b/apps/builder/src/components/Common/WizardLayout.tsx
index 3d2fe821..c57d5619 100644
--- a/apps/builder/src/components/Common/WizardLayout.tsx
+++ b/apps/builder/src/components/Common/WizardLayout.tsx
@@ -4,6 +4,7 @@ import React, { ReactNode } from 'react';
import { Button } from '@openzeppelin/ui-components';
import { cn } from '@openzeppelin/ui-utils';
+import type { AnalyticsNetworkContext } from '../../hooks/useBuilderAnalytics';
import { useBuilderAnalytics } from '../../hooks/useBuilderAnalytics';
export interface WizardStep {
@@ -20,6 +21,8 @@ interface WizardLayoutProps {
isWidgetExpanded?: boolean;
currentStepIndex: number;
onStepChange: (index: number) => void;
+ /** Network context attached to `wizard_step` analytics events. */
+ analyticsContext?: AnalyticsNetworkContext;
}
export function WizardLayout({
@@ -29,6 +32,7 @@ export function WizardLayout({
isWidgetExpanded = false,
currentStepIndex,
onStepChange,
+ analyticsContext,
}: WizardLayoutProps) {
const isFirstStep = currentStepIndex === 0;
const isLastStep = currentStepIndex === steps.length - 1;
@@ -43,7 +47,7 @@ export function WizardLayout({
const nextStep = steps[nextStepIndex];
// Track wizard step progression
- trackWizardStep(nextStepIndex + 1, nextStep.id); // Step numbers are 1-indexed for analytics
+ trackWizardStep(nextStepIndex + 1, nextStep.id, analyticsContext); // Step numbers are 1-indexed for analytics
onStepChange(nextStepIndex);
};
@@ -54,7 +58,7 @@ export function WizardLayout({
const prevStep = steps[prevStepIndex];
// Track wizard step progression (going backwards)
- trackWizardStep(prevStepIndex + 1, prevStep.id); // Step numbers are 1-indexed for analytics
+ trackWizardStep(prevStepIndex + 1, prevStep.id, analyticsContext); // Step numbers are 1-indexed for analytics
onStepChange(prevStepIndex);
}
diff --git a/apps/builder/src/components/Common/__tests__/WizardLayout.analytics.test.tsx b/apps/builder/src/components/Common/__tests__/WizardLayout.analytics.test.tsx
new file mode 100644
index 00000000..c3c316b1
--- /dev/null
+++ b/apps/builder/src/components/Common/__tests__/WizardLayout.analytics.test.tsx
@@ -0,0 +1,78 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { WizardLayout, type WizardStep } from '../WizardLayout';
+
+const mockTrackWizardStep = vi.fn();
+
+vi.mock('../../../hooks/useBuilderAnalytics', () => ({
+ useBuilderAnalytics: () => ({
+ trackWizardStep: mockTrackWizardStep,
+ }),
+}));
+
+const steps: WizardStep[] = [
+ { id: 'chain', title: 'Chain', component:
chain
},
+ { id: 'contract', title: 'Contract', component: contract
},
+ { id: 'function', title: 'Function', component: function
},
+ { id: 'customize', title: 'Customize', component: customize
},
+ { id: 'complete', title: 'Complete', component: complete
},
+];
+
+const analyticsContext = { networkId: 'ethereum-mainnet', ecosystem: 'evm' };
+
+describe('WizardLayout analytics', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it('fires wizard_step exactly once per Next click with the entered step and context', () => {
+ const onStepChange = vi.fn();
+ render(
+
+ );
+
+ fireEvent.click(screen.getByRole('button', { name: /next/i }));
+
+ expect(mockTrackWizardStep).toHaveBeenCalledTimes(1);
+ expect(mockTrackWizardStep).toHaveBeenCalledWith(3, 'function', analyticsContext);
+ expect(onStepChange).toHaveBeenCalledWith(2);
+ });
+
+ it('fires wizard_step exactly once per Back click with the entered step and context', () => {
+ const onStepChange = vi.fn();
+ render(
+
+ );
+
+ fireEvent.click(screen.getByRole('button', { name: /back/i }));
+
+ expect(mockTrackWizardStep).toHaveBeenCalledTimes(1);
+ expect(mockTrackWizardStep).toHaveBeenCalledWith(3, 'function', analyticsContext);
+ expect(onStepChange).toHaveBeenCalledWith(2);
+ });
+
+ it('does not fire wizard_step on render or when no navigation happens', () => {
+ render();
+
+ expect(mockTrackWizardStep).not.toHaveBeenCalled();
+ });
+
+ it('passes undefined context when none is provided so the hook applies fallbacks', () => {
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: /next/i }));
+
+ expect(mockTrackWizardStep).toHaveBeenCalledWith(3, 'function', undefined);
+ });
+});
diff --git a/apps/builder/src/components/Sidebar/AppSidebar/MainActions.tsx b/apps/builder/src/components/Sidebar/AppSidebar/MainActions.tsx
index 1ec1bc7f..2eded234 100644
--- a/apps/builder/src/components/Sidebar/AppSidebar/MainActions.tsx
+++ b/apps/builder/src/components/Sidebar/AppSidebar/MainActions.tsx
@@ -13,6 +13,7 @@ import { SidebarButton } from '@openzeppelin/ui-components';
import { useContractUIStorage } from '../../../contexts/useContractUIStorage';
import { useBuilderAnalytics } from '../../../hooks/useBuilderAnalytics';
import { AddressBookDialog } from '../../AddressBook/AddressBookDialog';
+import { useUIBuilderStore } from '../../UIBuilder/hooks/useUIBuilderStore';
import { recordHasMeaningfulContent } from '../../UIBuilder/utils/meaningfulContent';
interface MainActionsProps {
@@ -31,17 +32,21 @@ export default function MainActions({
}: MainActionsProps) {
const { exportContractUIs, contractUIs } = useContractUIStorage();
const { trackSidebarInteraction } = useBuilderAnalytics();
+ const analyticsContext = useUIBuilderStore((s) => ({
+ networkId: s.selectedNetworkConfigId,
+ ecosystem: s.selectedEcosystem,
+ }));
const handleExport = async () => {
// Track sidebar export interaction
- trackSidebarInteraction('export');
+ trackSidebarInteraction('export', analyticsContext);
await exportContractUIs(); // Export all configurations
};
const handleImport = () => {
// Track sidebar import interaction
- trackSidebarInteraction('import');
+ trackSidebarInteraction('import', analyticsContext);
onShowImportDialog();
};
diff --git a/apps/builder/src/components/Sidebar/AppSidebar/__tests__/MainActions.analytics.test.tsx b/apps/builder/src/components/Sidebar/AppSidebar/__tests__/MainActions.analytics.test.tsx
new file mode 100644
index 00000000..5301ff81
--- /dev/null
+++ b/apps/builder/src/components/Sidebar/AppSidebar/__tests__/MainActions.analytics.test.tsx
@@ -0,0 +1,93 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import type { Ecosystem } from '@openzeppelin/ui-types';
+
+import { uiBuilderStore } from '../../../UIBuilder/hooks/uiBuilderStore';
+import MainActions from '../MainActions';
+
+const mockTrackSidebarInteraction = vi.fn();
+const mockExportContractUIs = vi.fn().mockResolvedValue(undefined);
+
+vi.mock('../../../../hooks/useBuilderAnalytics', () => ({
+ useBuilderAnalytics: () => ({
+ trackSidebarInteraction: mockTrackSidebarInteraction,
+ }),
+}));
+
+vi.mock('../../../../contexts/useContractUIStorage', () => ({
+ useContractUIStorage: () => ({
+ exportContractUIs: mockExportContractUIs,
+ contractUIs: [
+ {
+ id: 'r1',
+ title: 'Saved UI',
+ ecosystem: 'evm',
+ networkId: 'ethereum-mainnet',
+ contractAddress: '0x0000000000000000000000000000000000000001',
+ functionId: 'transfer',
+ formConfig: { fields: [{ id: 'f1' }] },
+ metadata: {},
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ },
+ ],
+ }),
+}));
+
+vi.mock('../../../AddressBook/AddressBookDialog', () => ({
+ AddressBookDialog: () => null,
+}));
+
+vi.mock('../../../UIBuilder/utils/meaningfulContent', () => ({
+ recordHasMeaningfulContent: () => true,
+}));
+
+describe('MainActions analytics', () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ uiBuilderStore.updateState(() => ({
+ selectedNetworkConfigId: 'stellar-testnet',
+ selectedEcosystem: 'stellar' as Ecosystem,
+ }));
+ });
+
+ it('fires sidebar_interaction import exactly once with the selected network context', () => {
+ const onShowImportDialog = vi.fn();
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: /import/i }));
+
+ expect(mockTrackSidebarInteraction).toHaveBeenCalledTimes(1);
+ expect(mockTrackSidebarInteraction).toHaveBeenCalledWith('import', {
+ networkId: 'stellar-testnet',
+ ecosystem: 'stellar',
+ });
+ expect(onShowImportDialog).toHaveBeenCalledTimes(1);
+ });
+
+ it('fires sidebar_interaction export exactly once with the selected network context', () => {
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: /export/i }));
+
+ expect(mockTrackSidebarInteraction).toHaveBeenCalledTimes(1);
+ expect(mockTrackSidebarInteraction).toHaveBeenCalledWith('export', {
+ networkId: 'stellar-testnet',
+ ecosystem: 'stellar',
+ });
+ expect(mockExportContractUIs).toHaveBeenCalledTimes(1);
+ });
+
+ it('passes null network id when no network has been selected yet', () => {
+ uiBuilderStore.updateState(() => ({ selectedNetworkConfigId: null }));
+ render();
+
+ fireEvent.click(screen.getByRole('button', { name: /import/i }));
+
+ expect(mockTrackSidebarInteraction).toHaveBeenCalledWith('import', {
+ networkId: null,
+ ecosystem: 'stellar',
+ });
+ });
+});
diff --git a/apps/builder/src/components/UIBuilder/hooks/useCompleteStepState.ts b/apps/builder/src/components/UIBuilder/hooks/useCompleteStepState.ts
index bc693d28..c3d24ad2 100644
--- a/apps/builder/src/components/UIBuilder/hooks/useCompleteStepState.ts
+++ b/apps/builder/src/components/UIBuilder/hooks/useCompleteStepState.ts
@@ -77,7 +77,11 @@ export function useCompleteStepState() {
);
// Track successful export action
- trackExportAction('react-vite'); // Default export type - could be made dynamic based on template choice
+ // Default export type - could be made dynamic based on template choice
+ trackExportAction('react-vite', {
+ networkId: networkConfig.id,
+ ecosystem: networkConfig.ecosystem,
+ });
if (result.data instanceof Blob) {
downloadBlob(result.data, result.fileName);
diff --git a/apps/builder/src/components/UIBuilder/index.tsx b/apps/builder/src/components/UIBuilder/index.tsx
index 7e7f71b8..f2eba942 100644
--- a/apps/builder/src/components/UIBuilder/index.tsx
+++ b/apps/builder/src/components/UIBuilder/index.tsx
@@ -309,6 +309,10 @@ export function UIBuilder() {
isWidgetExpanded={state.isWidgetVisible}
currentStepIndex={state.currentStepIndex}
onStepChange={handleStepChange}
+ analyticsContext={{
+ networkId: state.selectedNetworkConfigId,
+ ecosystem: state.selectedEcosystem,
+ }}
/>
diff --git a/apps/builder/src/hooks/__tests__/useBuilderAnalytics.test.ts b/apps/builder/src/hooks/__tests__/useBuilderAnalytics.test.ts
index b551ee04..b6635459 100644
--- a/apps/builder/src/hooks/__tests__/useBuilderAnalytics.test.ts
+++ b/apps/builder/src/hooks/__tests__/useBuilderAnalytics.test.ts
@@ -83,24 +83,61 @@ describe('useBuilderAnalytics', () => {
});
describe('trackExportAction', () => {
- it('should track export action event', () => {
+ it('should track export action event with network context', () => {
+ const { result } = renderHook(() => useBuilderAnalytics());
+
+ result.current.trackExportAction('react-vite', {
+ networkId: 'ethereum-mainnet',
+ ecosystem: 'evm',
+ });
+
+ expect(mockTrackEvent).toHaveBeenCalledWith('export_clicked', {
+ export_type: 'react-vite',
+ network_id: 'ethereum-mainnet',
+ ecosystem: 'evm',
+ });
+ });
+
+ it('should fall back to unknown when context is missing', () => {
const { result } = renderHook(() => useBuilderAnalytics());
result.current.trackExportAction('react-vite');
- expect(mockTrackEvent).toHaveBeenCalledWith('export_clicked', { export_type: 'react-vite' });
+ expect(mockTrackEvent).toHaveBeenCalledWith('export_clicked', {
+ export_type: 'react-vite',
+ network_id: 'unknown',
+ ecosystem: 'unknown',
+ });
});
});
describe('trackWizardStep', () => {
- it('should track wizard step progression', () => {
+ it('should track wizard step progression with network context', () => {
const { result } = renderHook(() => useBuilderAnalytics());
- result.current.trackWizardStep(2, 'configure');
+ result.current.trackWizardStep(2, 'configure', {
+ networkId: 'stellar-testnet',
+ ecosystem: 'stellar',
+ });
expect(mockTrackEvent).toHaveBeenCalledWith('wizard_step', {
step_number: 2,
step_name: 'configure',
+ network_id: 'stellar-testnet',
+ ecosystem: 'stellar',
+ });
+ });
+
+ it('should normalise null/empty context values to unknown', () => {
+ const { result } = renderHook(() => useBuilderAnalytics());
+
+ result.current.trackWizardStep(1, 'select-chain', { networkId: null, ecosystem: '' });
+
+ expect(mockTrackEvent).toHaveBeenCalledWith('wizard_step', {
+ step_number: 1,
+ step_name: 'select-chain',
+ network_id: 'unknown',
+ ecosystem: 'unknown',
});
});
@@ -116,12 +153,31 @@ describe('useBuilderAnalytics', () => {
});
describe('trackSidebarInteraction', () => {
- it('should track sidebar interaction event', () => {
+ it('should track sidebar interaction event with network context', () => {
const { result } = renderHook(() => useBuilderAnalytics());
- result.current.trackSidebarInteraction('import');
+ result.current.trackSidebarInteraction('import', {
+ networkId: 'polygon-mainnet',
+ ecosystem: 'evm',
+ });
- expect(mockTrackEvent).toHaveBeenCalledWith('sidebar_interaction', { action: 'import' });
+ expect(mockTrackEvent).toHaveBeenCalledWith('sidebar_interaction', {
+ action: 'import',
+ network_id: 'polygon-mainnet',
+ ecosystem: 'evm',
+ });
+ });
+
+ it('should fall back to unknown when context is missing', () => {
+ const { result } = renderHook(() => useBuilderAnalytics());
+
+ result.current.trackSidebarInteraction('export');
+
+ expect(mockTrackEvent).toHaveBeenCalledWith('sidebar_interaction', {
+ action: 'export',
+ network_id: 'unknown',
+ ecosystem: 'unknown',
+ });
});
it('should track different actions', () => {
@@ -190,6 +246,30 @@ describe('useBuilderAnalytics', () => {
});
});
+ describe('unknown fallbacks', () => {
+ it('should never emit empty string dimensions', () => {
+ const { result } = renderHook(() => useBuilderAnalytics());
+
+ result.current.trackEcosystemSelection('');
+ result.current.trackTransactionExecuted('', '', '');
+ result.current.trackUiKitChanged('', 'evm', '');
+
+ expect(mockTrackEvent).toHaveBeenNthCalledWith(1, 'ecosystem_selected', {
+ ecosystem: 'unknown',
+ });
+ expect(mockTrackEvent).toHaveBeenNthCalledWith(2, 'transaction_executed', {
+ network_id: 'unknown',
+ ecosystem: 'unknown',
+ execution_method: 'unknown',
+ });
+ expect(mockTrackEvent).toHaveBeenNthCalledWith(3, 'uikit_changed', {
+ network_id: 'unknown',
+ ecosystem: 'evm',
+ uikit_name: 'unknown',
+ });
+ });
+ });
+
describe('trackAddressBookOpened', () => {
it('should track address_book_opened', () => {
const { result } = renderHook(() => useBuilderAnalytics());
diff --git a/apps/builder/src/hooks/useBuilderAnalytics.ts b/apps/builder/src/hooks/useBuilderAnalytics.ts
index 354517f6..dc017206 100644
--- a/apps/builder/src/hooks/useBuilderAnalytics.ts
+++ b/apps/builder/src/hooks/useBuilderAnalytics.ts
@@ -2,6 +2,32 @@ import { useMemo } from 'react';
import { useAnalytics } from '@openzeppelin/ui-react';
+/**
+ * GA4 rejects/ignores empty parameter values and `undefined` would silently drop the dimension,
+ * so every string dimension falls back to `'unknown'` to keep custom dimension reports complete.
+ */
+const UNKNOWN = 'unknown';
+
+function orUnknown(value: string | null | undefined): string {
+ return value && value.trim().length > 0 ? value : UNKNOWN;
+}
+
+/**
+ * Network context shared by most builder events. Both values are optional at the call site
+ * (e.g. the wizard before a network is chosen) and normalised to `'unknown'` when missing.
+ */
+export interface AnalyticsNetworkContext {
+ networkId?: string | null;
+ ecosystem?: string | null;
+}
+
+function networkParams(context: AnalyticsNetworkContext | undefined) {
+ return {
+ network_id: orUnknown(context?.networkId),
+ ecosystem: orUnknown(context?.ecosystem),
+ };
+}
+
/**
* UI Builder-specific analytics hook.
* Wraps the shared useAnalytics hook with builder-specific tracking events.
@@ -33,32 +59,49 @@ export function useBuilderAnalytics() {
* @param ecosystem - Selected ecosystem (e.g., 'evm', 'solana', 'stellar')
*/
trackEcosystemSelection: (ecosystem: string) => {
- analytics.trackEvent('ecosystem_selected', { ecosystem });
+ analytics.trackEvent('ecosystem_selected', { ecosystem: orUnknown(ecosystem) });
},
/**
- * Track export action event.
+ * Fires once when an app export completes successfully.
* @param exportType - Type of export (e.g., 'react-vite')
+ * @param context - Network the exported app targets
*/
- trackExportAction: (exportType: string) => {
- analytics.trackEvent('export_clicked', { export_type: exportType });
+ trackExportAction: (exportType: string, context?: AnalyticsNetworkContext) => {
+ analytics.trackEvent('export_clicked', {
+ export_type: orUnknown(exportType),
+ ...networkParams(context),
+ });
},
/**
- * Track wizard step progression.
- * @param stepNumber - Current step number
- * @param stepName - Name/identifier of the step
+ * Fires once per Next/Back click in the wizard, describing the step being entered.
+ * @param stepNumber - 1-indexed step number being entered
+ * @param stepName - Name/identifier of the step being entered
+ * @param context - Network selected so far (`'unknown'` before chain selection)
*/
- trackWizardStep: (stepNumber: number, stepName: string) => {
- analytics.trackEvent('wizard_step', { step_number: stepNumber, step_name: stepName });
+ trackWizardStep: (
+ stepNumber: number,
+ stepName: string,
+ context?: AnalyticsNetworkContext
+ ) => {
+ analytics.trackEvent('wizard_step', {
+ step_number: stepNumber,
+ step_name: orUnknown(stepName),
+ ...networkParams(context),
+ });
},
/**
- * Track sidebar interaction event.
+ * Fires once per sidebar Import/Export click.
* @param action - Action performed (e.g., 'import', 'export')
+ * @param context - Network currently selected in the builder
*/
- trackSidebarInteraction: (action: string) => {
- analytics.trackEvent('sidebar_interaction', { action });
+ trackSidebarInteraction: (action: string, context?: AnalyticsNetworkContext) => {
+ analytics.trackEvent('sidebar_interaction', {
+ action: orUnknown(action),
+ ...networkParams(context),
+ });
},
/**
@@ -69,9 +112,8 @@ export function useBuilderAnalytics() {
*/
trackTransactionExecuted: (networkId: string, ecosystem: string, executionMethod: string) => {
analytics.trackEvent('transaction_executed', {
- network_id: networkId,
- ecosystem,
- execution_method: executionMethod,
+ ...networkParams({ networkId, ecosystem }),
+ execution_method: orUnknown(executionMethod),
});
},
@@ -83,8 +125,7 @@ export function useBuilderAnalytics() {
*/
trackContractUiCreated: (networkId: string, ecosystem: string, totalRecords: number) => {
analytics.trackEvent('contract_ui_created', {
- network_id: networkId,
- ecosystem,
+ ...networkParams({ networkId, ecosystem }),
total_records: totalRecords,
});
},
@@ -95,10 +136,7 @@ export function useBuilderAnalytics() {
* @param ecosystem - Active ecosystem id
*/
trackRelayerServiceConfigured: (networkId: string, ecosystem: string) => {
- analytics.trackEvent('relayer_service_configured', {
- network_id: networkId,
- ecosystem,
- });
+ analytics.trackEvent('relayer_service_configured', networkParams({ networkId, ecosystem }));
},
/**
@@ -109,9 +147,8 @@ export function useBuilderAnalytics() {
*/
trackUiKitChanged: (networkId: string, ecosystem: string, uikitName: string) => {
analytics.trackEvent('uikit_changed', {
- network_id: networkId,
- ecosystem,
- uikit_name: uikitName,
+ ...networkParams({ networkId, ecosystem }),
+ uikit_name: orUnknown(uikitName),
});
},
@@ -121,10 +158,7 @@ export function useBuilderAnalytics() {
* @param ecosystem - Active ecosystem id, or `'unknown'`
*/
trackAddressBookOpened: (networkId: string, ecosystem: string) => {
- analytics.trackEvent('address_book_opened', {
- network_id: networkId,
- ecosystem,
- });
+ analytics.trackEvent('address_book_opened', networkParams({ networkId, ecosystem }));
},
}),
[analytics]