Skip to content

Commit

Permalink
feat(web): Hub spot identify (#6547)
Browse files Browse the repository at this point in the history
  • Loading branch information
scopsy authored Sep 23, 2024
1 parent 99bbf11 commit 83bc3ec
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@
"packages/js/src/ui/index.directcss",
"unreadRead",
"apps/web/src/studio/components/workflows/step-editor/editor/files.ts",
"apps/web/src/pages/playground/web-container-configuration/sandbox-vite/*.ts"
"apps/web/src/pages/playground/web-container-configuration/sandbox-vite/*.ts",
"apps/api/src/app/analytics/usecases/hubspot-identify-form/hubspot-identify-form.usecase.ts"
]
}
25 changes: 23 additions & 2 deletions apps/api/src/app/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { Body, Controller, Post } from '@nestjs/common';
import { Body, Controller, Post, Headers } from '@nestjs/common';
import { SkipThrottle } from '@nestjs/throttler';
import { AnalyticsService, ExternalApiAccessible, UserSession } from '@novu/application-generic';
import { UserSessionData } from '@novu/shared';
import { ApiExcludeController } from '@nestjs/swagger';
import { UserAuthentication } from '../shared/framework/swagger/api.key.security';
import { HubspotIdentifyFormUsecase } from './usecases/hubspot-identify-form/hubspot-identify-form.usecase';
import { HubspotIdentifyFormCommand } from './usecases/hubspot-identify-form/hubspot-identify-form.command';

@Controller({
path: 'telemetry',
})
@SkipThrottle()
@ApiExcludeController()
export class AnalyticsController {
constructor(private analyticsService: AnalyticsService) {}
constructor(
private analyticsService: AnalyticsService,
private hubspotIdentifyFormUsecase: HubspotIdentifyFormUsecase
) {}

@Post('/measure')
@ExternalApiAccessible()
Expand All @@ -26,4 +31,20 @@ export class AnalyticsController {
success: true,
};
}

@Post('/identify')
@ExternalApiAccessible()
@UserAuthentication()
async identifyUser(@Body() body: any, @UserSession() user: UserSessionData): Promise<any> {
return this.hubspotIdentifyFormUsecase.execute(
HubspotIdentifyFormCommand.create({
email: user.email as string,
lastName: user.lastName,
firstName: user.firstName,
hubspotContext: body.hubspotContext,
pageUri: body.pageUri,
pageName: body.pageName,
})
);
}
}
9 changes: 5 additions & 4 deletions apps/api/src/app/analytics/analytics.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { AnalyticsService } from '@novu/application-generic';
import { HttpModule } from '@nestjs/axios';
import { AnalyticsController } from './analytics.controller';
import { SharedModule } from '../shared/shared.module';
import { AuthModule } from '../auth/auth.module';
import { HubspotIdentifyFormUsecase } from './usecases/hubspot-identify-form/hubspot-identify-form.usecase';

@Module({
imports: [SharedModule, AuthModule],
imports: [HttpModule],
controllers: [AnalyticsController],
providers: [],
providers: [AnalyticsService, HubspotIdentifyFormUsecase],
})
export class AnalyticsModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { BaseCommand } from '@novu/application-generic';
import { IsDefined, IsString, IsOptional } from 'class-validator';

export class HubspotIdentifyFormCommand extends BaseCommand {
@IsDefined()
@IsString()
email: string;

@IsOptional()
@IsString()
lastName?: string;

@IsOptional()
@IsString()
firstName?: string;

@IsOptional()
@IsString()
hubspotContext?: string;

@IsOptional()
@IsString()
pageUri?: string;

@IsOptional()
@IsString()
pageName?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';
import { HubspotIdentifyFormCommand } from './hubspot-identify-form.command';

@Injectable()
export class HubspotIdentifyFormUsecase {
private readonly hubspotPortalId = '44416662';
private readonly hubspotFormId = 'fc39aa98-4285-4322-9514-52da978baae8';

constructor(private httpService: HttpService) {}

async execute(command: HubspotIdentifyFormCommand): Promise<{ success: boolean; hubspotResponse?: any }> {
const hubspotSubmitUrl = `https://api.hsforms.com/submissions/v3/integration/submit/${this.hubspotPortalId}/${this.hubspotFormId}`;

const hubspotData = {
fields: [
{ name: 'email', value: command.email },
{ name: 'lastname', value: command.lastName || 'Unknown' },
{ name: 'firstname', value: command.firstName || 'Unknown' },
],
context: {
hutk: command.hubspotContext,
pageUri: command.pageUri,
pageName: command.pageName,
},
};

const response = await firstValueFrom(this.httpService.post(hubspotSubmitUrl, hubspotData));

return {
success: true,
hubspotResponse: response.data,
};
}
}
11 changes: 11 additions & 0 deletions apps/web/src/api/telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { api } from './api.client';

export const identifyUser = async (userData) => {
try {
const response = await api.post('/v1/telemetry/identify', userData);

return response.data;
} catch (error) {
console.error('Error identifying user:', error);
}
};
13 changes: 13 additions & 0 deletions apps/web/src/ee/clerk/components/QuestionnaireForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { useSegment } from '../../../components/providers/SegmentProvider';
import { BRIDGE_SYNC_SAMPLE_ENDPOINT } from '../../../config/index';
import { DynamicCheckBox } from '../../../pages/auth/components/dynamic-checkbox/DynamicCheckBox';
import { useWebContainerSupported } from '../../../hooks/useWebContainerSupport';
import { identifyUser } from '../../../api/telemetry';
import { hubspotCookie } from '../../../utils';

function updateClerkOrgMetadata(data: UpdateExternalOrganizationDto) {
return api.post('/v1/clerk/organization', data);
Expand Down Expand Up @@ -58,6 +60,17 @@ export function QuestionnaireForm() {
};
await updateOrganizationMutation(updateClerkOrgDto);

const hubspotContext = hubspotCookie.get();

await identifyUser({
location: (location.state as any)?.origin || 'web',
language: selectedLanguages,
jobTitle: data.jobTitle,
pageUri: window.location.href,
pageName: 'Create Organization Form',
hubspotContext: hubspotContext || '',
});

segment.track('Create Organization Form Submitted', {
location: (location.state as any)?.origin || 'web',
language: selectedLanguages,
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/utils/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export const novuRedirectURLCookie = createCookieHandler('nv_redirect_url');
*/
export const novuOnboardedCookie = createCookieHandler('nv_onboarding_step');

export const hubspotCookie = createCookieHandler('hubspotutk');

const ONBOARDING_COOKIE_EXPIRY_DAYS = 10 * 365;

// Never expires! Well it does, in 10 years but you will change device or browser by then :)
Expand Down

0 comments on commit 83bc3ec

Please sign in to comment.