From f06cdbdfc61aa1f3dd14e0880bf465dd44d0b3e3 Mon Sep 17 00:00:00 2001 From: Ana Sofia Marin Alexandre <61988046+anamarn@users.noreply.github.com> Date: Fri, 8 Nov 2024 06:00:51 -0300 Subject: [PATCH 01/18] refactor webhookAnalytics call and enrich analytics module (#8253) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **TLDR** Refactor WebhoonAnalytics Graph to a more abstract version AnalyticsGraph (in analytics module). Thus enabling the components to be used on different instances (ex: new endpoint, new kind of graph). **In order to test:** 1. Set ANALYTICS_ENABLED to true 2. Set TINYBIRD_JWT_TOKEN to the ADMIN token from the workspace twenty_analytics_playground 3. Set TINYBIRD_JWT_TOKEN to the datasource or your admin token from the workspace twenty_analytics_playground 4. Create a Webhook in twenty and set wich events it needs to track 5. Run twenty-worker in order to make the webhooks work. 6. Do your tasks in order to populate the data 7. Enter to settings> webhook>your webhook and the statistics section should be displayed. --------- Co-authored-by: Félix Malfait --- .../src/generated-metadata/graphql.ts | 12 +- .../twenty-front/src/generated/graphql.tsx | 29 ++- .../components/AnalyticsActivityGraph.tsx} | 68 +++---- .../components/AnalyticsGraphEffect.tsx | 32 +++ .../components/WebhookAnalyticsTooltip.tsx} | 6 +- .../constants/AnalyticsEndpointTypeMap.ts | 10 + .../constants/AnalyticsGraphDescriptionMap.ts | 10 + .../constants/AnalyticsGraphOptionMap.ts | 6 + .../constants/AnalyticsGraphTitleMap.ts | 10 + .../useAnalyticsTinybirdJwt.test.tsx | 87 ++++++++ .../hooks/__tests__/useGraphData.test.tsx | 87 ++++++++ .../hooks/useAnalyticsTinybirdJwts.ts | 16 ++ .../modules/analytics/hooks/useGraphData.tsx | 42 ++++ .../analyticsGraphDataComponentState.ts | 10 + .../AnalyticsGraphDataInstanceContext.ts | 4 + .../types/AnalyticsComponentProps.ts | 6 + .../modules/analytics/types/NivoLineInput.ts | 8 + .../computeAnalyticsGraphDataFunction.test.js | 56 ++++++ .../__tests__/fetchGraphDataOrThrow.test.js | 143 ++++++++++++++ ...essFunctionDurationToNivoLineInput.test.js | 65 ++++++ ...lessFunctionErrorstToNivoLineInput.test.js | 68 +++++++ ...hookAnalyticsResultToNivoLineInput.test.js | 187 ++++++++++++++++++ .../computeAnalyticsGraphDataFunction.ts | 25 +++ .../analytics/utils/computeStartEndDate.ts | 30 +++ .../analytics/utils/fetchGraphDataOrThrow.ts | 38 ++++ ...rverlessFunctionDurationToNivoLineInput.ts | 49 +++++ ...ServerlessFunctionErrorsToNivoLineInput.ts | 26 +++ ...apWebhookAnalyticsResultToNivoLineInput.ts | 43 ++++ .../modules/auth/states/currentUserState.ts | 2 +- ...tingsDevelopersWebhookUsageGraphEffect.tsx | 28 --- .../constants/WebhookGraphApiOptionsMap.ts | 6 - .../useAnalyticsTinybirdJwt.test.tsx | 47 ----- .../webhook/hooks/useAnalyticsTinybirdJwt.ts | 18 -- .../developers/webhook/hooks/useGraphData.tsx | 31 --- .../webhook/states/webhookGraphDataState.ts | 7 - .../__tests__/fetchGraphDataOrThrow.test.js | 119 ----------- .../webhook/utils/fetchGraphDataOrThrow.ts | 82 -------- ...ettingsServerlessFunctionMonitoringTab.tsx | 75 +++++++ .../modules/ui/input/components/TextArea.tsx | 5 + .../graphql/fragments/userQueryFragment.ts | 9 +- .../SettingsDevelopersWebhookDetail.tsx | 21 +- .../SettingsServerlessFunctionDetail.tsx | 23 ++- .../src/testing/mock-data/users.ts | 2 + .../analytics/analytics.resolver.ts | 2 +- .../analytics/analytics.service.ts | 51 ++++- .../analytics-tinybird-jwts.entity.ts | 22 +++ .../{ => entities}/analytics.entity.ts | 0 .../engine/core-modules/user/user.resolver.ts | 11 +- packages/twenty-tinybird/README.md | 6 + .../datasources/event.datasource | 6 +- .../datasources/pageview.datasource | 10 +- .../serverlessFunctionEventMV.datasource | 5 +- .../datasources/webhookEventMV.datasource | 8 +- .../twenty-tinybird/includes/timeSeries.incl | 58 +----- .../pipes/getServerlessFunctionDuration.pipe | 16 +- .../getServerlessFunctionErrorCount.pipe | 28 +++ .../pipes/getServerlessFunctionErrors.pipe | 41 ---- .../getServerlessFunctionSuccessRate.pipe | 34 ++++ .../pipes/getWebhookAnalytics.pipe | 16 +- .../materializeServerlessFunctionEvent.pipe | 3 +- .../display/icon/components/TablerIcons.ts | 1 + packages/twenty-ui/tsconfig.json | 2 +- 62 files changed, 1429 insertions(+), 539 deletions(-) rename packages/twenty-front/src/modules/{settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx => analytics/components/AnalyticsActivityGraph.tsx} (67%) create mode 100644 packages/twenty-front/src/modules/analytics/components/AnalyticsGraphEffect.tsx rename packages/twenty-front/src/modules/{settings/developers/webhook/components/SettingsDevelopersWebhookTooltip.tsx => analytics/components/WebhookAnalyticsTooltip.tsx} (94%) create mode 100644 packages/twenty-front/src/modules/analytics/constants/AnalyticsEndpointTypeMap.ts create mode 100644 packages/twenty-front/src/modules/analytics/constants/AnalyticsGraphDescriptionMap.ts create mode 100644 packages/twenty-front/src/modules/analytics/constants/AnalyticsGraphOptionMap.ts create mode 100644 packages/twenty-front/src/modules/analytics/constants/AnalyticsGraphTitleMap.ts create mode 100644 packages/twenty-front/src/modules/analytics/hooks/__tests__/useAnalyticsTinybirdJwt.test.tsx create mode 100644 packages/twenty-front/src/modules/analytics/hooks/__tests__/useGraphData.test.tsx create mode 100644 packages/twenty-front/src/modules/analytics/hooks/useAnalyticsTinybirdJwts.ts create mode 100644 packages/twenty-front/src/modules/analytics/hooks/useGraphData.tsx create mode 100644 packages/twenty-front/src/modules/analytics/states/analyticsGraphDataComponentState.ts create mode 100644 packages/twenty-front/src/modules/analytics/states/contexts/AnalyticsGraphDataInstanceContext.ts create mode 100644 packages/twenty-front/src/modules/analytics/types/AnalyticsComponentProps.ts create mode 100644 packages/twenty-front/src/modules/analytics/types/NivoLineInput.ts create mode 100644 packages/twenty-front/src/modules/analytics/utils/__tests__/computeAnalyticsGraphDataFunction.test.js create mode 100644 packages/twenty-front/src/modules/analytics/utils/__tests__/fetchGraphDataOrThrow.test.js create mode 100644 packages/twenty-front/src/modules/analytics/utils/__tests__/mapServerlessFunctionDurationToNivoLineInput.test.js create mode 100644 packages/twenty-front/src/modules/analytics/utils/__tests__/mapServerlessFunctionErrorstToNivoLineInput.test.js create mode 100644 packages/twenty-front/src/modules/analytics/utils/__tests__/mapWebhookAnalyticsResultToNivoLineInput.test.js create mode 100644 packages/twenty-front/src/modules/analytics/utils/computeAnalyticsGraphDataFunction.ts create mode 100644 packages/twenty-front/src/modules/analytics/utils/computeStartEndDate.ts create mode 100644 packages/twenty-front/src/modules/analytics/utils/fetchGraphDataOrThrow.ts create mode 100644 packages/twenty-front/src/modules/analytics/utils/mapServerlessFunctionDurationToNivoLineInput.ts create mode 100644 packages/twenty-front/src/modules/analytics/utils/mapServerlessFunctionErrorsToNivoLineInput.ts create mode 100644 packages/twenty-front/src/modules/analytics/utils/mapWebhookAnalyticsResultToNivoLineInput.ts delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraphEffect.tsx delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/constants/WebhookGraphApiOptionsMap.ts delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/hooks/__tests__/useAnalyticsTinybirdJwt.test.tsx delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/hooks/useAnalyticsTinybirdJwt.ts delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/hooks/useGraphData.tsx delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/states/webhookGraphDataState.ts delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/utils/__tests__/fetchGraphDataOrThrow.test.js delete mode 100644 packages/twenty-front/src/modules/settings/developers/webhook/utils/fetchGraphDataOrThrow.ts create mode 100644 packages/twenty-front/src/modules/settings/serverless-functions/components/tabs/SettingsServerlessFunctionMonitoringTab.tsx create mode 100644 packages/twenty-server/src/engine/core-modules/analytics/entities/analytics-tinybird-jwts.entity.ts rename packages/twenty-server/src/engine/core-modules/analytics/{ => entities}/analytics.entity.ts (100%) create mode 100644 packages/twenty-tinybird/pipes/getServerlessFunctionErrorCount.pipe delete mode 100644 packages/twenty-tinybird/pipes/getServerlessFunctionErrors.pipe create mode 100644 packages/twenty-tinybird/pipes/getServerlessFunctionSuccessRate.pipe diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 04eb88d31957..59787435f224 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -38,6 +38,16 @@ export type Analytics = { success: Scalars['Boolean']['output']; }; +export type AnalyticsTinybirdJwtMap = { + __typename?: 'AnalyticsTinybirdJwtMap'; + getPageviewsAnalytics: Scalars['String']['output']; + getServerlessFunctionDuration: Scalars['String']['output']; + getServerlessFunctionErrorCount: Scalars['String']['output']; + getServerlessFunctionSuccessRate: Scalars['String']['output']; + getUsersAnalytics: Scalars['String']['output']; + getWebhookAnalytics: Scalars['String']['output']; +}; + export type ApiConfig = { __typename?: 'ApiConfig'; mutationMaximumAffectedRecords: Scalars['Float']['output']; @@ -1497,7 +1507,7 @@ export type UpdateWorkspaceInput = { export type User = { __typename?: 'User'; - analyticsTinybirdJwt?: Maybe; + analyticsTinybirdJwts?: Maybe; canImpersonate: Scalars['Boolean']['output']; createdAt: Scalars['DateTime']['output']; defaultAvatarUrl?: Maybe; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 766a07067bab..5cd0f8983b35 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -31,6 +31,16 @@ export type Analytics = { success: Scalars['Boolean']; }; +export type AnalyticsTinybirdJwtMap = { + __typename?: 'AnalyticsTinybirdJwtMap'; + getPageviewsAnalytics: Scalars['String']; + getServerlessFunctionDuration: Scalars['String']; + getServerlessFunctionErrorCount: Scalars['String']; + getServerlessFunctionSuccessRate: Scalars['String']; + getUsersAnalytics: Scalars['String']; + getWebhookAnalytics: Scalars['String']; +}; + export type ApiConfig = { __typename?: 'ApiConfig'; mutationMaximumAffectedRecords: Scalars['Float']; @@ -1207,7 +1217,7 @@ export type UpdateWorkspaceInput = { export type User = { __typename?: 'User'; - analyticsTinybirdJwt?: Maybe; + analyticsTinybirdJwts?: Maybe; canImpersonate: Scalars['Boolean']; createdAt: Scalars['DateTime']; defaultAvatarUrl?: Maybe; @@ -1696,7 +1706,7 @@ export type ImpersonateMutationVariables = Exact<{ }>; -export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type ImpersonateMutation = { __typename?: 'Mutation', impersonate: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type RenewTokenMutationVariables = Exact<{ appToken: Scalars['String']; @@ -1729,7 +1739,7 @@ export type VerifyMutationVariables = Exact<{ }>; -export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; +export type VerifyMutation = { __typename?: 'Mutation', verify: { __typename?: 'Verify', user: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }, tokens: { __typename?: 'AuthTokenPair', accessToken: { __typename?: 'AuthToken', token: string, expiresAt: string }, refreshToken: { __typename?: 'AuthToken', token: string, expiresAt: string } } } }; export type CheckUserExistsQueryVariables = Exact<{ email: Scalars['String']; @@ -1816,7 +1826,7 @@ export type ListSsoIdentityProvidersByWorkspaceIdQueryVariables = Exact<{ [key: export type ListSsoIdentityProvidersByWorkspaceIdQuery = { __typename?: 'Query', listSSOIdentityProvidersByWorkspaceId: Array<{ __typename?: 'FindAvailableSSOIDPOutput', type: IdpType, id: string, name: string, issuer: string, status: SsoIdentityProviderStatus }> }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; @@ -1833,7 +1843,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, analyticsTinybirdJwt?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars: any, analyticsTinybirdJwts?: { __typename?: 'AnalyticsTinybirdJwtMap', getWebhookAnalytics: string, getPageviewsAnalytics: string, getUsersAnalytics: string, getServerlessFunctionDuration: string, getServerlessFunctionSuccessRate: string, getServerlessFunctionErrorCount: string } | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale?: string | null, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, hasValidEntrepriseKey: boolean, metadataVersion: number, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> } }; export type ActivateWorkflowVersionMutationVariables = Exact<{ workflowVersionId: Scalars['String']; @@ -2060,7 +2070,14 @@ export const UserQueryFragmentFragmentDoc = gql` email canImpersonate supportUserHash - analyticsTinybirdJwt + analyticsTinybirdJwts { + getWebhookAnalytics + getPageviewsAnalytics + getUsersAnalytics + getServerlessFunctionDuration + getServerlessFunctionSuccessRate + getServerlessFunctionErrorCount + } onboardingStatus workspaceMember { ...WorkspaceMemberQueryFragment diff --git a/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx b/packages/twenty-front/src/modules/analytics/components/AnalyticsActivityGraph.tsx similarity index 67% rename from packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx rename to packages/twenty-front/src/modules/analytics/components/AnalyticsActivityGraph.tsx index 9626c6712eef..1a124ad90c52 100644 --- a/packages/twenty-front/src/modules/settings/developers/webhook/components/SettingsDevelopersWebhookUsageGraph.tsx +++ b/packages/twenty-front/src/modules/analytics/components/AnalyticsActivityGraph.tsx @@ -1,23 +1,19 @@ -import { SettingsDevelopersWebhookTooltip } from '@/settings/developers/webhook/components/SettingsDevelopersWebhookTooltip'; -import { useGraphData } from '@/settings/developers/webhook/hooks/useGraphData'; -import { webhookGraphDataState } from '@/settings/developers/webhook/states/webhookGraphDataState'; +import { WebhookAnalyticsTooltip } from '@/analytics/components/WebhookAnalyticsTooltip'; +import { ANALYTICS_GRAPH_DESCRIPTION_MAP } from '@/analytics/constants/AnalyticsGraphDescriptionMap'; +import { ANALYTICS_GRAPH_TITLE_MAP } from '@/analytics/constants/AnalyticsGraphTitleMap'; +import { useGraphData } from '@/analytics/hooks/useGraphData'; +import { analyticsGraphDataComponentState } from '@/analytics/states/analyticsGraphDataComponentState'; +import { AnalyticsComponentProps as AnalyticsActivityGraphProps } from '@/analytics/types/AnalyticsComponentProps'; +import { computeAnalyticsGraphDataFunction } from '@/analytics/utils/computeAnalyticsGraphDataFunction'; import { Select } from '@/ui/input/components/Select'; +import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { ResponsiveLine } from '@nivo/line'; import { Section } from '@react-email/components'; -import { useState } from 'react'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useId, useState } from 'react'; import { H2Title } from 'twenty-ui'; -export type NivoLineInput = { - id: string | number; - color?: string; - data: Array<{ - x: number | string | Date; - y: number | string | Date; - }>; -}; const StyledGraphContainer = styled.div` background-color: ${({ theme }) => theme.background.secondary}; border: 1px solid ${({ theme }) => theme.border.color.medium}; @@ -33,34 +29,38 @@ const StyledTitleContainer = styled.div` justify-content: space-between; `; -type SettingsDevelopersWebhookUsageGraphProps = { - webhookId: string; -}; - -export const SettingsDevelopersWebhookUsageGraph = ({ - webhookId, -}: SettingsDevelopersWebhookUsageGraphProps) => { - const webhookGraphData = useRecoilValue(webhookGraphDataState); - const setWebhookGraphData = useSetRecoilState(webhookGraphDataState); +export const AnalyticsActivityGraph = ({ + recordId, + endpointName, +}: AnalyticsActivityGraphProps) => { + const [analyticsGraphData, setAnalyticsGraphData] = useRecoilComponentStateV2( + analyticsGraphDataComponentState, + ); const theme = useTheme(); const [windowLengthGraphOption, setWindowLengthGraphOption] = useState< '7D' | '1D' | '12H' | '4H' >('7D'); - const { fetchGraphData } = useGraphData(webhookId); + const { fetchGraphData } = useGraphData({ + recordId, + endpointName, + }); + + const transformDataFunction = computeAnalyticsGraphDataFunction(endpointName); + const dropdownId = useId(); return ( <> - {webhookGraphData.length ? ( + {analyticsGraphData.length ? (