Connect EventTracker to TB endpoint (#7240)
#7091 EventTrackers send information of events to the TinyBird instance: In order to test: 1. Set ANALYTICS_ENABLED= true and TELEMETRY_ENABLED=true in evironment-variables.ts 2. Set the TINYBIRD_TOKEN in environment variables (go to TiniyBird Tokens) 3. Log in to twenty's TinyBird and go to datasources/analytics_events in twenty_analytics workspace 4. Run twenty and navigate it 5. New events will be logged in the datasources, containing their timestamp, sessionId and payload. <img width="1189" alt="Screenshot 2024-09-24 at 17 23 01" src="https://github.com/user-attachments/assets/85375897-504d-4e75-98e4-98e6a9671f98"> Example of payload when user is not logged in ``` {"hostName":"localhost", "pathname":"/welcome", "locale":"en-US", "userAgent":"Mozilla/5.0", "href":"http://localhost:3001/welcome", "referrer":"", "timeZone":"Europe/Barcelona"} ``` Example of payload when user is logged in ``` {"userId":"2020202", "workspaceId":"202", "workspaceDisplayName":"Apple", "workspaceDomainName":"apple.dev", "hostName":"localhost", "pathname":"/objects/companies", "locale":"en-US", "userAgent":"Mozilla/5.0Chrome/128.0.0.0Safari/537.36", "href":"http://localhost:3001/objects/companies", "referrer":"", "timeZone":"Europe/Paris"} ``` --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
committed by
GitHub
parent
c9e882f4c0
commit
16bb1f22e4
@ -1,8 +1,8 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const TRACK = gql`
|
||||
mutation Track($type: String!, $data: JSON!) {
|
||||
track(type: $type, data: $data) {
|
||||
mutation Track($type: String!, $sessionId: String!, $data: JSON!) {
|
||||
track(type: $type, sessionId: $sessionId, data: $data) {
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { gql } from '@apollo/client';
|
||||
import { MockedProvider, MockedResponse } from '@apollo/client/testing';
|
||||
import { expect } from '@storybook/test';
|
||||
import { act, renderHook, waitFor } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useEventTracker } from '../useEventTracker';
|
||||
@ -11,15 +11,23 @@ const mocks: MockedResponse[] = [
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
mutation Track($type: String!, $data: JSON!) {
|
||||
track(type: $type, data: $data) {
|
||||
mutation Track($action: String!, $payload: JSON!) {
|
||||
track(action: $action, payload: $payload) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
type: 'exampleType',
|
||||
data: { location: { pathname: '/examplePath' } },
|
||||
action: 'exampleType',
|
||||
payload: {
|
||||
sessionId: 'exampleId',
|
||||
pathname: '',
|
||||
userAgent: '',
|
||||
timeZone: '',
|
||||
locale: '',
|
||||
href: '',
|
||||
referrer: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
@ -43,7 +51,15 @@ const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
describe('useEventTracker', () => {
|
||||
it('should make the call to track the event', async () => {
|
||||
const eventType = 'exampleType';
|
||||
const eventData = { location: { pathname: '/examplePath' } };
|
||||
const eventData = {
|
||||
sessionId: 'exampleId',
|
||||
pathname: '',
|
||||
userAgent: '',
|
||||
timeZone: '',
|
||||
locale: '',
|
||||
href: '',
|
||||
referrer: '',
|
||||
};
|
||||
const { result } = renderHook(() => useEventTracker(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
@ -1,32 +1,46 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { telemetryState } from '@/client-config/states/telemetryState';
|
||||
import { useTrackMutation } from '~/generated/graphql';
|
||||
|
||||
interface EventLocation {
|
||||
pathname: string;
|
||||
}
|
||||
|
||||
export interface EventData {
|
||||
location: EventLocation;
|
||||
pathname: string;
|
||||
userAgent: string;
|
||||
timeZone: string;
|
||||
locale: string;
|
||||
href: string;
|
||||
referrer: string;
|
||||
}
|
||||
export const ANALYTICS_COOKIE_NAME = 'analyticsCookie';
|
||||
export const getSessionId = (): string => {
|
||||
const cookie: { [key: string]: string } = {};
|
||||
document.cookie.split(';').forEach((el) => {
|
||||
const [key, value] = el.split('=');
|
||||
cookie[key.trim()] = value;
|
||||
});
|
||||
return cookie[ANALYTICS_COOKIE_NAME];
|
||||
};
|
||||
|
||||
export const setSessionId = (domain?: string): void => {
|
||||
const sessionId = getSessionId() || crypto.randomUUID();
|
||||
const baseCookie = `${ANALYTICS_COOKIE_NAME}=${sessionId}; Max-Age=1800; path=/; secure`;
|
||||
const cookie = domain ? baseCookie + `; domain=${domain}` : baseCookie;
|
||||
|
||||
document.cookie = cookie;
|
||||
};
|
||||
|
||||
export const useEventTracker = () => {
|
||||
const telemetry = useRecoilValue(telemetryState);
|
||||
const [createEventMutation] = useTrackMutation();
|
||||
|
||||
return useCallback(
|
||||
(eventType: string, eventData: EventData) => {
|
||||
if (telemetry.enabled) {
|
||||
createEventMutation({
|
||||
variables: {
|
||||
type: eventType,
|
||||
data: eventData,
|
||||
(eventAction: string, eventPayload: EventData) => {
|
||||
createEventMutation({
|
||||
variables: {
|
||||
action: eventAction,
|
||||
payload: {
|
||||
sessionId: getSessionId(),
|
||||
...eventPayload,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
[createEventMutation, telemetry],
|
||||
[createEventMutation],
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { MemoryRouter, useLocation } from 'react-router-dom';
|
||||
import { ApolloError, gql } from '@apollo/client';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import fetchMock, { enableFetchMocks } from 'jest-fetch-mock';
|
||||
import { MemoryRouter, useLocation } from 'react-router-dom';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import { useApolloFactory } from '../useApolloFactory';
|
||||
@ -77,8 +77,8 @@ describe('useApolloFactory', () => {
|
||||
await act(async () => {
|
||||
await result.current.factory.mutate({
|
||||
mutation: gql`
|
||||
mutation Track($type: String!, $data: JSON!) {
|
||||
track(type: $type, data: $data) {
|
||||
mutation Track($type: String!, $sessionId: String!, $data: JSON!) {
|
||||
track(type: $type, sessionId: $sessionId, data: $data) {
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,8 +41,8 @@ const makeRequest = async () => {
|
||||
|
||||
await client.mutate({
|
||||
mutation: gql`
|
||||
mutation Track($type: String!, $data: JSON!) {
|
||||
track(type: $type, data: $data) {
|
||||
mutation Track($type: String!, $sessionId: String!, $data: JSON!) {
|
||||
track(type: $type, sessionId: $sessionId, data: $data) {
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { expect } from '@storybook/test';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
import { iconsState } from 'twenty-ui';
|
||||
|
||||
@ -12,7 +12,6 @@ import { billingState } from '@/client-config/states/billingState';
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||
import { telemetryState } from '@/client-config/states/telemetryState';
|
||||
|
||||
import { email, mocks, password, results, token } from '../__mocks__/useAuth';
|
||||
|
||||
@ -81,7 +80,6 @@ describe('useAuth', () => {
|
||||
const billing = useRecoilValue(billingState);
|
||||
const isSignInPrefilled = useRecoilValue(isSignInPrefilledState);
|
||||
const supportChat = useRecoilValue(supportChatState);
|
||||
const telemetry = useRecoilValue(telemetryState);
|
||||
const isDebugMode = useRecoilValue(isDebugModeState);
|
||||
return {
|
||||
...useAuth(),
|
||||
@ -92,7 +90,6 @@ describe('useAuth', () => {
|
||||
billing,
|
||||
isSignInPrefilled,
|
||||
supportChat,
|
||||
telemetry,
|
||||
isDebugMode,
|
||||
},
|
||||
};
|
||||
@ -126,9 +123,6 @@ describe('useAuth', () => {
|
||||
supportDriver: 'none',
|
||||
supportFrontChatId: null,
|
||||
});
|
||||
expect(state.telemetry).toEqual({
|
||||
enabled: true,
|
||||
});
|
||||
expect(state.isDebugMode).toBe(false);
|
||||
});
|
||||
|
||||
|
||||
@ -21,7 +21,6 @@ import { isClientConfigLoadedState } from '@/client-config/states/isClientConfig
|
||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
|
||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||
import { telemetryState } from '@/client-config/states/telemetryState';
|
||||
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import {
|
||||
@ -224,7 +223,6 @@ export const useAuth = () => {
|
||||
.getLoadable(isSignInPrefilledState)
|
||||
.getValue();
|
||||
const supportChat = snapshot.getLoadable(supportChatState).getValue();
|
||||
const telemetry = snapshot.getLoadable(telemetryState).getValue();
|
||||
const isDebugMode = snapshot.getLoadable(isDebugModeState).getValue();
|
||||
const captchaProvider = snapshot
|
||||
.getLoadable(captchaProviderState)
|
||||
@ -242,7 +240,6 @@ export const useAuth = () => {
|
||||
set(billingState, billing);
|
||||
set(isSignInPrefilledState, isSignInPrefilled);
|
||||
set(supportChatState, supportChat);
|
||||
set(telemetryState, telemetry);
|
||||
set(isDebugModeState, isDebugMode);
|
||||
set(captchaProviderState, captchaProvider);
|
||||
set(isClientConfigLoadedState, isClientConfigLoaded);
|
||||
|
||||
@ -12,7 +12,6 @@ import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilled
|
||||
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
|
||||
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
|
||||
import { supportChatState } from '@/client-config/states/supportChatState';
|
||||
import { telemetryState } from '@/client-config/states/telemetryState';
|
||||
import { useGetClientConfigQuery } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
@ -24,7 +23,6 @@ export const ClientConfigProviderEffect = () => {
|
||||
const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState);
|
||||
|
||||
const setBilling = useSetRecoilState(billingState);
|
||||
const setTelemetry = useSetRecoilState(telemetryState);
|
||||
const setSupportChat = useSetRecoilState(supportChatState);
|
||||
|
||||
const setSentryConfig = useSetRecoilState(sentryConfigState);
|
||||
@ -56,7 +54,6 @@ export const ClientConfigProviderEffect = () => {
|
||||
setIsSignUpDisabled(data?.clientConfig.signUpDisabled);
|
||||
|
||||
setBilling(data?.clientConfig.billing);
|
||||
setTelemetry(data?.clientConfig.telemetry);
|
||||
setSupportChat(data?.clientConfig.support);
|
||||
|
||||
setSentryConfig({
|
||||
@ -79,7 +76,6 @@ export const ClientConfigProviderEffect = () => {
|
||||
setIsDebugMode,
|
||||
setIsSignInPrefilled,
|
||||
setIsSignUpDisabled,
|
||||
setTelemetry,
|
||||
setSupportChat,
|
||||
setBilling,
|
||||
setSentryConfig,
|
||||
|
||||
@ -16,9 +16,6 @@ export const GET_CLIENT_CONFIG = gql`
|
||||
signInPrefilled
|
||||
signUpDisabled
|
||||
debugMode
|
||||
telemetry {
|
||||
enabled
|
||||
}
|
||||
support {
|
||||
supportDriver
|
||||
supportFrontChatId
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
import { createState } from 'twenty-ui';
|
||||
|
||||
import { Telemetry } from '~/generated/graphql';
|
||||
|
||||
export const telemetryState = createState<Telemetry>({
|
||||
key: 'telemetryState',
|
||||
defaultValue: { enabled: true },
|
||||
});
|
||||
Reference in New Issue
Block a user