mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-22 21:50:43 +03:00
Add Telemetry (#466)
* Telemetry v1 * Add package-lock.json to gitignore
This commit is contained in:
parent
74ea2718ca
commit
eb7fb2ba8e
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
**/**/.env
|
**/**/.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules/
|
node_modules/
|
||||||
|
# yarn is the recommended package manager across the project
|
||||||
|
.package-lock.json
|
@ -1,13 +1,16 @@
|
|||||||
---
|
---
|
||||||
sidebar_class_name: coming-soon
|
|
||||||
sidebar_custom_props:
|
sidebar_custom_props:
|
||||||
icon: TbChartDots
|
icon: TbChartDots
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Data collected
|
# Data collected
|
||||||
...
|
We record page view events using a unique identifier for each workspace/user.
|
||||||
|
Additionally we also collect the workspace's domain.
|
||||||
|
We do not set any cookie for telemetry.
|
||||||
|
We do not collect any email, first name, last name, phone number, date of birth, address, username, etc.
|
||||||
|
|
||||||
# Opting-out of telemetry
|
# Opting-out of telemetry
|
||||||
...
|
Opting out is simple. To do this, edit your .env on the server side and include the following:
|
||||||
|
```
|
||||||
|
IS_TELEMETRY_ENABLED=false
|
||||||
|
```
|
13378
docs/package-lock.json
generated
13378
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ import { Navigate, Route, Routes } from 'react-router-dom';
|
|||||||
|
|
||||||
import { RequireAuth } from '@/auth/components/RequireAuth';
|
import { RequireAuth } from '@/auth/components/RequireAuth';
|
||||||
import { RequireNotAuth } from '@/auth/components/RequireNotAuth';
|
import { RequireNotAuth } from '@/auth/components/RequireNotAuth';
|
||||||
|
import { useTrackPageView } from '@/analytics/hooks/useTrackPageView';
|
||||||
import { useGoToHotkeys } from '@/hotkeys/hooks/useGoToHotkeys';
|
import { useGoToHotkeys } from '@/hotkeys/hooks/useGoToHotkeys';
|
||||||
import { DefaultLayout } from '@/ui/layout/DefaultLayout';
|
import { DefaultLayout } from '@/ui/layout/DefaultLayout';
|
||||||
import { Index } from '~/pages/auth/Index';
|
import { Index } from '~/pages/auth/Index';
|
||||||
@ -18,6 +19,8 @@ export function App() {
|
|||||||
useGoToHotkeys('o', '/opportunities');
|
useGoToHotkeys('o', '/opportunities');
|
||||||
useGoToHotkeys('s', '/settings/profile');
|
useGoToHotkeys('s', '/settings/profile');
|
||||||
|
|
||||||
|
useTrackPageView();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
@ -641,6 +641,12 @@ export type EnumPipelineProgressableTypeFilter = {
|
|||||||
notIn?: InputMaybe<Array<PipelineProgressableType>>;
|
notIn?: InputMaybe<Array<PipelineProgressableType>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Event = {
|
||||||
|
__typename?: 'Event';
|
||||||
|
/** Boolean that confirms query was dispatched */
|
||||||
|
success: Scalars['Boolean'];
|
||||||
|
};
|
||||||
|
|
||||||
export type IntNullableFilter = {
|
export type IntNullableFilter = {
|
||||||
equals?: InputMaybe<Scalars['Int']>;
|
equals?: InputMaybe<Scalars['Int']>;
|
||||||
gt?: InputMaybe<Scalars['Int']>;
|
gt?: InputMaybe<Scalars['Int']>;
|
||||||
@ -676,6 +682,7 @@ export type LoginToken = {
|
|||||||
export type Mutation = {
|
export type Mutation = {
|
||||||
__typename?: 'Mutation';
|
__typename?: 'Mutation';
|
||||||
challenge: LoginToken;
|
challenge: LoginToken;
|
||||||
|
createEvent: Event;
|
||||||
createOneComment: Comment;
|
createOneComment: Comment;
|
||||||
createOneCommentThread: CommentThread;
|
createOneCommentThread: CommentThread;
|
||||||
createOneCompany: Company;
|
createOneCompany: Company;
|
||||||
@ -700,6 +707,12 @@ export type MutationChallengeArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationCreateEventArgs = {
|
||||||
|
data: Scalars['JSON'];
|
||||||
|
type: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateOneCommentArgs = {
|
export type MutationCreateOneCommentArgs = {
|
||||||
data: CommentCreateInput;
|
data: CommentCreateInput;
|
||||||
};
|
};
|
||||||
@ -1657,6 +1670,14 @@ export type DeleteCompaniesMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type DeleteCompaniesMutation = { __typename?: 'Mutation', deleteManyCompany: { __typename?: 'AffectedRows', count: number } };
|
export type DeleteCompaniesMutation = { __typename?: 'Mutation', deleteManyCompany: { __typename?: 'AffectedRows', count: number } };
|
||||||
|
|
||||||
|
export type CreateEventMutationVariables = Exact<{
|
||||||
|
type: Scalars['String'];
|
||||||
|
data: Scalars['JSON'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type CreateEventMutation = { __typename?: 'Mutation', createEvent: { __typename?: 'Event', success: boolean } };
|
||||||
|
|
||||||
export type GetPipelinesQueryVariables = Exact<{
|
export type GetPipelinesQueryVariables = Exact<{
|
||||||
where?: InputMaybe<PipelineWhereInput>;
|
where?: InputMaybe<PipelineWhereInput>;
|
||||||
}>;
|
}>;
|
||||||
@ -2422,6 +2443,40 @@ export function useDeleteCompaniesMutation(baseOptions?: Apollo.MutationHookOpti
|
|||||||
export type DeleteCompaniesMutationHookResult = ReturnType<typeof useDeleteCompaniesMutation>;
|
export type DeleteCompaniesMutationHookResult = ReturnType<typeof useDeleteCompaniesMutation>;
|
||||||
export type DeleteCompaniesMutationResult = Apollo.MutationResult<DeleteCompaniesMutation>;
|
export type DeleteCompaniesMutationResult = Apollo.MutationResult<DeleteCompaniesMutation>;
|
||||||
export type DeleteCompaniesMutationOptions = Apollo.BaseMutationOptions<DeleteCompaniesMutation, DeleteCompaniesMutationVariables>;
|
export type DeleteCompaniesMutationOptions = Apollo.BaseMutationOptions<DeleteCompaniesMutation, DeleteCompaniesMutationVariables>;
|
||||||
|
export const CreateEventDocument = gql`
|
||||||
|
mutation CreateEvent($type: String!, $data: JSON!) {
|
||||||
|
createEvent(type: $type, data: $data) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type CreateEventMutationFn = Apollo.MutationFunction<CreateEventMutation, CreateEventMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useCreateEventMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useCreateEventMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useCreateEventMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [createEventMutation, { data, loading, error }] = useCreateEventMutation({
|
||||||
|
* variables: {
|
||||||
|
* type: // value for 'type'
|
||||||
|
* data: // value for 'data'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useCreateEventMutation(baseOptions?: Apollo.MutationHookOptions<CreateEventMutation, CreateEventMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<CreateEventMutation, CreateEventMutationVariables>(CreateEventDocument, options);
|
||||||
|
}
|
||||||
|
export type CreateEventMutationHookResult = ReturnType<typeof useCreateEventMutation>;
|
||||||
|
export type CreateEventMutationResult = Apollo.MutationResult<CreateEventMutation>;
|
||||||
|
export type CreateEventMutationOptions = Apollo.BaseMutationOptions<CreateEventMutation, CreateEventMutationVariables>;
|
||||||
export const GetPipelinesDocument = gql`
|
export const GetPipelinesDocument = gql`
|
||||||
query GetPipelines($where: PipelineWhereInput) {
|
query GetPipelines($where: PipelineWhereInput) {
|
||||||
findManyPipeline(where: $where) {
|
findManyPipeline(where: $where) {
|
||||||
|
32
front/src/modules/analytics/hooks/useEventTracker.ts
Normal file
32
front/src/modules/analytics/hooks/useEventTracker.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useCreateEventMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { useIsTelemetryEnabled } from './useIsTelemetryEnabled';
|
||||||
|
|
||||||
|
interface EventLocation {
|
||||||
|
pathname: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventData {
|
||||||
|
location: EventLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useEventTracker() {
|
||||||
|
const telemetryEnabled = useIsTelemetryEnabled();
|
||||||
|
const [createEventMutation] = useCreateEventMutation();
|
||||||
|
|
||||||
|
return useCallback(
|
||||||
|
(eventType: string, eventData: EventData) => {
|
||||||
|
if (telemetryEnabled) {
|
||||||
|
createEventMutation({
|
||||||
|
variables: {
|
||||||
|
type: eventType,
|
||||||
|
data: eventData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[createEventMutation, telemetryEnabled],
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export function useIsTelemetryEnabled() {
|
||||||
|
// TODO: replace by clientConfig
|
||||||
|
return process.env.IS_TELEMETRY_ENABLED !== 'false';
|
||||||
|
}
|
7
front/src/modules/analytics/hooks/useTrackEvent.ts
Normal file
7
front/src/modules/analytics/hooks/useTrackEvent.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { EventData, useEventTracker } from './useEventTracker';
|
||||||
|
|
||||||
|
export function useTrackEvent(eventType: string, eventData: EventData) {
|
||||||
|
const eventTracker = useEventTracker();
|
||||||
|
|
||||||
|
return eventTracker(eventType, eventData);
|
||||||
|
}
|
17
front/src/modules/analytics/hooks/useTrackPageView.ts
Normal file
17
front/src/modules/analytics/hooks/useTrackPageView.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useEventTracker } from './useEventTracker';
|
||||||
|
|
||||||
|
export function useTrackPageView() {
|
||||||
|
const location = useLocation();
|
||||||
|
const eventTracker = useEventTracker();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventTracker('pageview', {
|
||||||
|
location: {
|
||||||
|
pathname: location.pathname,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [location, eventTracker]);
|
||||||
|
}
|
9
front/src/modules/analytics/services/index.ts
Normal file
9
front/src/modules/analytics/services/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const CREATE_EVENT = gql`
|
||||||
|
mutation CreateEvent($type: String!, $data: JSON!) {
|
||||||
|
createEvent(type: $type, data: $data) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@ -2,6 +2,7 @@ import { getOperationName } from '@apollo/client/utilities';
|
|||||||
import { graphql } from 'msw';
|
import { graphql } from 'msw';
|
||||||
|
|
||||||
import { GET_COMPANIES } from '@/companies/services';
|
import { GET_COMPANIES } from '@/companies/services';
|
||||||
|
import { CREATE_EVENT } from '@/analytics/services';
|
||||||
import { GET_PEOPLE, UPDATE_PERSON } from '@/people/services';
|
import { GET_PEOPLE, UPDATE_PERSON } from '@/people/services';
|
||||||
import {
|
import {
|
||||||
SEARCH_COMPANY_QUERY,
|
SEARCH_COMPANY_QUERY,
|
||||||
@ -102,4 +103,11 @@ export const graphqlMocks = [
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
graphql.mutation(getOperationName(CREATE_EVENT) ?? '', (req, res, ctx) => {
|
||||||
|
return res(
|
||||||
|
ctx.data({
|
||||||
|
createEvent: { success: 1, __typename: 'Event' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@casl/prisma": "^1.4.0",
|
"@casl/prisma": "^1.4.0",
|
||||||
"@nestjs/apollo": "^11.0.5",
|
"@nestjs/apollo": "^11.0.5",
|
||||||
|
"@nestjs/axios": "^3.0.0",
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
"@nestjs/config": "^2.3.2",
|
"@nestjs/config": "^2.3.2",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
@ -43,6 +44,7 @@
|
|||||||
"@paljs/plugins": "^5.3.3",
|
"@paljs/plugins": "^5.3.3",
|
||||||
"@prisma/client": "^4.13.0",
|
"@prisma/client": "^4.13.0",
|
||||||
"apollo-server-express": "^3.12.0",
|
"apollo-server-express": "^3.12.0",
|
||||||
|
"axios": "^1.4.0",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
|
@ -10,6 +10,8 @@ import { GraphQLError } from 'graphql';
|
|||||||
import { PrismaModule } from './database/prisma.module';
|
import { PrismaModule } from './database/prisma.module';
|
||||||
import { HealthModule } from './health/health.module';
|
import { HealthModule } from './health/health.module';
|
||||||
import { AbilityModule } from './ability/ability.module';
|
import { AbilityModule } from './ability/ability.module';
|
||||||
|
import { EventModule } from './core/analytics/event.module';
|
||||||
|
import GraphQLJSON from 'graphql-type-json';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -21,6 +23,7 @@ import { AbilityModule } from './ability/ability.module';
|
|||||||
context: ({ req }) => ({ req }),
|
context: ({ req }) => ({ req }),
|
||||||
driver: ApolloDriver,
|
driver: ApolloDriver,
|
||||||
autoSchemaFile: true,
|
autoSchemaFile: true,
|
||||||
|
resolvers: { JSON: GraphQLJSON },
|
||||||
plugins: [ApolloServerPluginLandingPageLocalDefault()],
|
plugins: [ApolloServerPluginLandingPageLocalDefault()],
|
||||||
formatError: (error: GraphQLError) => {
|
formatError: (error: GraphQLError) => {
|
||||||
error.extensions.stacktrace = undefined;
|
error.extensions.stacktrace = undefined;
|
||||||
@ -31,6 +34,7 @@ import { AbilityModule } from './ability/ability.module';
|
|||||||
HealthModule,
|
HealthModule,
|
||||||
AbilityModule,
|
AbilityModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
|
EventModule,
|
||||||
],
|
],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
|
15
server/src/core/analytics/dto/create-event.input.ts
Normal file
15
server/src/core/analytics/dto/create-event.input.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { ArgsType, Field } from '@nestjs/graphql';
|
||||||
|
import GraphQLJSON from 'graphql-type-json';
|
||||||
|
import { IsNotEmpty, IsString, IsObject } from 'class-validator';
|
||||||
|
|
||||||
|
@ArgsType()
|
||||||
|
export class CreateEventInput {
|
||||||
|
@Field({ description: 'Type of the event' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@Field(() => GraphQLJSON, { description: 'Event data in JSON format' })
|
||||||
|
@IsObject()
|
||||||
|
data: JSON;
|
||||||
|
}
|
9
server/src/core/analytics/event.entity.ts
Normal file
9
server/src/core/analytics/event.entity.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { ObjectType, Field, Int } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class Event {
|
||||||
|
@Field(() => Boolean, {
|
||||||
|
description: 'Boolean that confirms query was dispatched',
|
||||||
|
})
|
||||||
|
success: boolean;
|
||||||
|
}
|
10
server/src/core/analytics/event.module.ts
Normal file
10
server/src/core/analytics/event.module.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { EventService } from './event.service';
|
||||||
|
import { EventResolver } from './event.resolver';
|
||||||
|
import { HttpModule } from '@nestjs/axios';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [EventResolver, EventService],
|
||||||
|
imports: [HttpModule],
|
||||||
|
})
|
||||||
|
export class EventModule {}
|
19
server/src/core/analytics/event.resolver.spec.ts
Normal file
19
server/src/core/analytics/event.resolver.spec.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { EventResolver } from './event.resolver';
|
||||||
|
import { EventService } from './event.service';
|
||||||
|
|
||||||
|
describe('EventResolver', () => {
|
||||||
|
let resolver: EventResolver;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [EventResolver, EventService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
resolver = module.get<EventResolver>(EventResolver);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(resolver).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
24
server/src/core/analytics/event.resolver.ts
Normal file
24
server/src/core/analytics/event.resolver.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Resolver, Mutation, Args, Context } from '@nestjs/graphql';
|
||||||
|
import { EventService } from './event.service';
|
||||||
|
import { Event } from './event.entity';
|
||||||
|
import { CreateEventInput } from './dto/create-event.input';
|
||||||
|
import { OptionalJwtAuthGuard } from 'src/guards/optional-jwt.auth.guard';
|
||||||
|
import { UseGuards } from '@nestjs/common';
|
||||||
|
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||||
|
import { User, Workspace } from '@prisma/client';
|
||||||
|
import { AuthUser } from 'src/decorators/auth-user.decorator';
|
||||||
|
|
||||||
|
@UseGuards(OptionalJwtAuthGuard)
|
||||||
|
@Resolver(() => Event)
|
||||||
|
export class EventResolver {
|
||||||
|
constructor(private readonly eventService: EventService) {}
|
||||||
|
|
||||||
|
@Mutation(() => Event)
|
||||||
|
createEvent(
|
||||||
|
@Args() createEventInput: CreateEventInput,
|
||||||
|
@AuthWorkspace() workspace: Workspace | undefined,
|
||||||
|
@AuthUser() user: User | undefined,
|
||||||
|
) {
|
||||||
|
return this.eventService.create(createEventInput, user, workspace);
|
||||||
|
}
|
||||||
|
}
|
18
server/src/core/analytics/event.service.spec.ts
Normal file
18
server/src/core/analytics/event.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { EventService } from './event.service';
|
||||||
|
|
||||||
|
describe('EventService', () => {
|
||||||
|
let service: EventService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [EventService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<EventService>(EventService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
38
server/src/core/analytics/event.service.ts
Normal file
38
server/src/core/analytics/event.service.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreateEventInput } from './dto/create-event.input';
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
import { anonymize } from 'src/utils/anonymize';
|
||||||
|
import { User, Workspace } from '@prisma/client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EventService {
|
||||||
|
constructor(private readonly httpService: HttpService) {}
|
||||||
|
|
||||||
|
create(
|
||||||
|
createEventInput: CreateEventInput,
|
||||||
|
user: User | undefined,
|
||||||
|
workspace: Workspace | undefined,
|
||||||
|
) {
|
||||||
|
if (process.env.IS_TELEMETRY_ENABLED === 'false') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
type: createEventInput.type,
|
||||||
|
data: {
|
||||||
|
userUUID: user ? anonymize(user.id) : undefined,
|
||||||
|
workspaceUUID: workspace ? anonymize(workspace.id) : undefined,
|
||||||
|
workspaceDomain: workspace ? workspace.domainName : undefined,
|
||||||
|
...createEventInput.data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.httpService
|
||||||
|
.post('https://t.twenty.com/api/v1/s2s/event?noToken', data)
|
||||||
|
.subscribe({
|
||||||
|
error: () => null,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import { PersonModule } from './person/person.module';
|
|||||||
import { PipelineModule } from './pipeline/pipeline.module';
|
import { PipelineModule } from './pipeline/pipeline.module';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { WorkspaceModule } from './workspace/workspace.module';
|
import { WorkspaceModule } from './workspace/workspace.module';
|
||||||
|
import { EventModule } from './analytics/event.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -16,6 +17,7 @@ import { WorkspaceModule } from './workspace/workspace.module';
|
|||||||
PersonModule,
|
PersonModule,
|
||||||
PipelineModule,
|
PipelineModule,
|
||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
|
EventModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
AuthModule,
|
AuthModule,
|
||||||
@ -25,6 +27,7 @@ import { WorkspaceModule } from './workspace/workspace.module';
|
|||||||
PersonModule,
|
PersonModule,
|
||||||
PipelineModule,
|
PipelineModule,
|
||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
|
EventModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreModule {}
|
export class CoreModule {}
|
||||||
|
20
server/src/guards/optional-jwt.auth.guard.ts
Normal file
20
server/src/guards/optional-jwt.auth.guard.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { getRequest } from 'src/utils/extract-request';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OptionalJwtAuthGuard extends AuthGuard(['jwt']) {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequest(context: ExecutionContext) {
|
||||||
|
const request = getRequest(context);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequest(err, user, info) {
|
||||||
|
if (err || info) return null;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
9
server/src/utils/anonymize.ts
Normal file
9
server/src/utils/anonymize.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
export function anonymize(input) {
|
||||||
|
if (process.env.IS_TELEMETRY_ANONYMIZATION_ENABLED === 'false') {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
// md5 shorter than sha-256 and collisions are not a security risk in this use-case
|
||||||
|
return crypto.createHash('md5').update(input).digest('hex');
|
||||||
|
}
|
1909
server/yarn.lock
1909
server/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user