diff --git a/apps/api/package.json b/apps/api/package.json
index bd118d152..d65b8e296 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -19,6 +19,7 @@
"test": "cross-env TZ=UTC NODE_ENV=test E2E_RUNNER=true mocha --timeout 10000 --require ts-node/register --exit"
},
"dependencies": {
+ "@amplitude/analytics-node": "^1.3.6",
"@impler/client": "workspace:^",
"@impler/dal": "workspace:^",
"@impler/services": "workspace:^",
diff --git a/apps/api/src/app/review/review.module.ts b/apps/api/src/app/review/review.module.ts
index 29571acea..26b851ccd 100644
--- a/apps/api/src/app/review/review.module.ts
+++ b/apps/api/src/app/review/review.module.ts
@@ -5,10 +5,11 @@ import { SharedModule } from '@shared/shared.module';
import { AJVService } from './service/AJV.service';
import { Sandbox, SManager } from '../shared/services/sandbox';
import { QueueService } from '@shared/services/queue.service';
+import { AmplitudeService } from '@shared/services/amplitude.service';
@Module({
imports: [SharedModule],
- providers: [...USE_CASES, AJVService, QueueService, SManager, Sandbox],
+ providers: [...USE_CASES, AJVService, QueueService, SManager, Sandbox, AmplitudeService],
controllers: [ReviewController],
})
export class ReviewModule {}
diff --git a/apps/api/src/app/review/usecases/start-process/start-process.usecase.ts b/apps/api/src/app/review/usecases/start-process/start-process.usecase.ts
index d72a743eb..00cbcfd51 100644
--- a/apps/api/src/app/review/usecases/start-process/start-process.usecase.ts
+++ b/apps/api/src/app/review/usecases/start-process/start-process.usecase.ts
@@ -10,6 +10,7 @@ import {
} from '@impler/shared';
import { PaymentAPIService } from '@impler/services';
import { QueueService } from '@shared/services/queue.service';
+import { AmplitudeService } from '@shared/services/amplitude.service';
import { DalService, TemplateEntity, UploadRepository } from '@impler/dal';
@Injectable()
@@ -18,6 +19,7 @@ export class StartProcess {
private dalService: DalService,
private queueService: QueueService,
private uploadRepository: UploadRepository,
+ private amplitudeService: AmplitudeService,
private paymentAPIService: PaymentAPIService
) {}
@@ -58,6 +60,12 @@ export class StartProcess {
);
}
+ this.amplitudeService.recordsImported(userEmail, {
+ records: uploadInfo.totalRecords,
+ valid: uploadInfo.validRecords,
+ invalid: uploadInfo.invalidRecords,
+ });
+
this.queueService.publishToQueue(QueuesEnum.END_IMPORT, {
uploadId: _uploadId,
destination: destination,
diff --git a/apps/api/src/app/shared/services/amplitude.service.ts b/apps/api/src/app/shared/services/amplitude.service.ts
new file mode 100644
index 000000000..ab0ab2c41
--- /dev/null
+++ b/apps/api/src/app/shared/services/amplitude.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@nestjs/common';
+import { init, track } from '@amplitude/analytics-node';
+
+@Injectable()
+export class AmplitudeService {
+ constructor() {
+ if (process.env.AMPLITUDE_ID) {
+ init(process.env.AMPLITUDE_ID);
+ }
+ }
+ recordsImported(email: string, data: { records: number; valid: number; invalid: number }) {
+ if (process.env.AMPLITUDE_ID) {
+ track('RECORDS IMPORTED', data, {
+ user_id: email,
+ });
+ }
+ }
+}
diff --git a/apps/api/src/types/env.d.ts b/apps/api/src/types/env.d.ts
index d1f775186..028fac788 100644
--- a/apps/api/src/types/env.d.ts
+++ b/apps/api/src/types/env.d.ts
@@ -42,5 +42,7 @@ declare namespace NodeJS {
PAYMENT_API_BASE_URL: string;
PAYMENT_AUTH_KEY: string;
PAYMENT_AUTH_VALUE: string;
+
+ AMPLITUDE_ID: string;
}
}
diff --git a/apps/web/components/Integration/IntegrationModal.tsx b/apps/web/components/Integration/IntegrationModal.tsx
index be05877cf..2b6de8fe1 100644
--- a/apps/web/components/Integration/IntegrationModal.tsx
+++ b/apps/web/components/Integration/IntegrationModal.tsx
@@ -1,7 +1,9 @@
import getConfig from 'next/config';
import { useEffect, useState } from 'react';
import { Flex, Title, useMantineColorScheme } from '@mantine/core';
+
import { colors } from '@config';
+import { track } from '@libs/amplitude';
import { IntegrationEnum } from '@impler/shared';
import { IntegrationTabs } from './IntegrationTabs';
import { integrationData } from './IntegrationData';
@@ -27,6 +29,26 @@ export function IntegrationModal({ accessToken, projectId, templateId, integrati
setSelectedTab(Object.keys(integrationData[integration])[0]);
}, [integration]);
+ const onIntegrationFrameworkChange = (value: string) => {
+ track({
+ name: 'CHANGE INTEGRATION FRAMEWORK',
+ properties: {
+ framework: value,
+ },
+ });
+ setIntegration(value as IntegrationEnum);
+ };
+
+ const onIntegrationStepChange = (newStep: string) => {
+ track({
+ name: 'CHANGE INTEGRATION STEPS',
+ properties: {
+ step: newStep,
+ },
+ });
+ setSelectedTab(newStep);
+ };
+
const tabs = Object.keys(integrationData[integration]);
return (
@@ -35,12 +57,12 @@ export function IntegrationModal({ accessToken, projectId, templateId, integrati
Integrate
-
+
({
id: tab.toLowerCase().replace(/\s+/g, ''),
value: tab,
diff --git a/apps/web/hooks/useImportDetails.tsx b/apps/web/hooks/useImportDetails.tsx
index 37c7e83f0..01e61d7c1 100644
--- a/apps/web/hooks/useImportDetails.tsx
+++ b/apps/web/hooks/useImportDetails.tsx
@@ -83,6 +83,10 @@ export function useImportDetails({ templateId }: useImportDetailProps) {
};
const onIntegrationClick = () => {
+ track({
+ name: 'INTEGRATE',
+ properties: {},
+ });
if (templateData && profileInfo) {
modals.open({
modalId: MODAL_KEYS.INTEGRATION_DETAILS,
diff --git a/apps/web/hooks/useImports.tsx b/apps/web/hooks/useImports.tsx
index 2223a2404..9ac8f4aff 100644
--- a/apps/web/hooks/useImports.tsx
+++ b/apps/web/hooks/useImports.tsx
@@ -45,7 +45,9 @@ export function useImports() {
]);
track({
name: 'IMPORT CREATE',
- properties: {},
+ properties: {
+ framework: data.integration,
+ },
});
push(`/imports/${data._id}`);
notify(NOTIFICATION_KEYS.IMPORT_CREATED);
diff --git a/apps/web/hooks/useOnboardUserProjectForm.tsx b/apps/web/hooks/useOnboardUserProjectForm.tsx
index 4246b4352..190ecd5a2 100644
--- a/apps/web/hooks/useOnboardUserProjectForm.tsx
+++ b/apps/web/hooks/useOnboardUserProjectForm.tsx
@@ -20,7 +20,7 @@ export function useOnboardUserProjectForm() {
[API_KEYS.ONBOARD_USER],
(apiData) => commonApi(API_KEYS.ONBOARD_USER as any, { body: { ...apiData, onboarding: true } }),
{
- onSuccess: () => {
+ onSuccess: (_, onboardData) => {
if (profileInfo) {
setProfileInfo({
...profileInfo,
@@ -33,6 +33,14 @@ export function useOnboardUserProjectForm() {
duringOnboard: true,
},
});
+ track({
+ name: 'ONBOARD',
+ properties: {
+ companySize: onboardData.companySize,
+ role: onboardData.role,
+ source: onboardData.source,
+ },
+ });
push('/');
},
}
diff --git a/apps/web/hooks/useValidator.tsx b/apps/web/hooks/useValidator.tsx
index 4444df7ed..4a46cacf9 100644
--- a/apps/web/hooks/useValidator.tsx
+++ b/apps/web/hooks/useValidator.tsx
@@ -5,6 +5,7 @@ import { useMutation, useQuery } from '@tanstack/react-query';
import { notify } from '@libs/notify';
import { commonApi } from '@libs/api';
+import { track } from '@libs/amplitude';
import { API_KEYS, MODAL_KEYS, MODAL_TITLES } from '@config';
import { ICustomization, IErrorObject, IValidator } from '@impler/shared';
import { CodeOutput } from '@components/imports/validator/CodeOutput';
@@ -87,6 +88,12 @@ export function useValidator({ templateId }: UseSchemaProps) {
} else {
notify('VALIDATIONS_UPDATED');
}
+ track({
+ name: 'SAVE VALIDATION',
+ properties: {
+ isValid: !!output?.passed,
+ },
+ });
},
onError(error) {
notify('VALIDATIONS_UPDATED', { title: 'Something went wrong!', message: error?.message, color: 'red' });
diff --git a/apps/web/libs/amplitude.ts b/apps/web/libs/amplitude.ts
index 5b2fb90d4..fc822ebe1 100644
--- a/apps/web/libs/amplitude.ts
+++ b/apps/web/libs/amplitude.ts
@@ -47,7 +47,9 @@ type TrackData =
}
| {
name: 'IMPORT CREATE';
- properties: Record;
+ properties: {
+ framework: string;
+ };
}
| {
name: 'OUTPUT FORMAT UPDATED';
@@ -65,6 +67,14 @@ type TrackData =
duringOnboard: boolean;
};
}
+ | {
+ name: 'ONBOARD';
+ properties: {
+ companySize: string;
+ role: string;
+ source: string;
+ };
+ }
| {
name: 'PROJECT SWITCH';
properties: Record;
@@ -151,6 +161,28 @@ type TrackData =
properties: {
yearly: boolean;
};
+ }
+ | {
+ name: 'SAVE VALIDATION';
+ properties: {
+ isValid: boolean;
+ };
+ }
+ | {
+ name: 'INTEGRATE';
+ properties: Record;
+ }
+ | {
+ name: 'CHANGE INTEGRATION FRAMEWORK';
+ properties: {
+ framework: string;
+ };
+ }
+ | {
+ name: 'CHANGE INTEGRATION STEPS';
+ properties: {
+ step: string;
+ };
};
export function track({ name, properties }: TrackData) {
diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx
index 2df90b55d..1d5ebe3dd 100644
--- a/apps/web/pages/_app.tsx
+++ b/apps/web/pages/_app.tsx
@@ -23,7 +23,11 @@ const { publicRuntimeConfig } = getConfig();
if (typeof window !== 'undefined' && publicRuntimeConfig.NEXT_PUBLIC_AMPLITUDE_ID) {
init(publicRuntimeConfig.NEXT_PUBLIC_AMPLITUDE_ID, {
defaultTracking: {
+ sessions: false,
+ pageViews: false,
attribution: false,
+ fileDownloads: false,
+ formInteractions: false,
},
});
}
diff --git a/apps/web/pages/imports/[id].tsx b/apps/web/pages/imports/[id].tsx
index 672314505..1fac00137 100644
--- a/apps/web/pages/imports/[id].tsx
+++ b/apps/web/pages/imports/[id].tsx
@@ -105,7 +105,7 @@ export default function ImportDetails({}) {
>
Import
- } id="import" onClick={onIntegrationClick}>
+ } id="integration" onClick={onIntegrationClick}>
Integrate