Skip to content

Commit

Permalink
add dynamic dates for webhookGraphDataUsage (#7720)
Browse files Browse the repository at this point in the history
**Before:**
Only last 5 days where displayed on Developers Settings Webhook Usage
Graph.

![image](https://github.com/user-attachments/assets/7b7f2e6b-9637-489e-a7a7-5a3cb70525aa)


**Now**
Added component where you can select the time range where you want to
view the webhook usage. To do better the styling and content depassing .

<img width="652" alt="Screenshot 2024-10-15 at 16 56 45"
src="https://github.com/user-attachments/assets/d06e7f4c-a689-49a0-8839-f015ce36bab9">


**In order to test**

1. Set ANALYTICS_ENABLED to true
2. Set TINYBIRD_TOKEN to your token from the workspace
twenty_analytics_playground
3. Write your client tinybird token in
SettingsDeveloppersWebhookDetail.tsx in line 93
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.
8.  Select the desired time range in the dropdown

**To do list**

- Tooltip is truncated when accessing values at the right end of the
graph
- DateTicks needs to follow a more clear standard
- Update this PR with more representative images
  • Loading branch information
anamarn authored Oct 18, 2024
1 parent 0c24001 commit 8cadcdf
Show file tree
Hide file tree
Showing 28 changed files with 631 additions and 132 deletions.
4 changes: 3 additions & 1 deletion packages/twenty-front/src/generated/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export enum CaptchaDriverType {

export type ClientConfig = {
__typename?: 'ClientConfig';
analyticsEnabled: Scalars['Boolean'];
api: ApiConfig;
authProviders: AuthProviders;
billing: Billing;
Expand Down Expand Up @@ -1599,7 +1600,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;


export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } };
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, analyticsEnabled: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number } } };

export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;

Expand Down Expand Up @@ -2765,6 +2766,7 @@ export const GetClientConfigDocument = gql`
signInPrefilled
signUpDisabled
debugMode
analyticsEnabled
support {
supportDriver
supportFrontChatId
Expand Down
6 changes: 4 additions & 2 deletions packages/twenty-front/src/modules/auth/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {
import { isDefined } from '~/utils/isDefined';

import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersStates';
import { DateFormat } from '@/localization/constants/DateFormat';
import { TimeFormat } from '@/localization/constants/TimeFormat';
import { dateTimeFormatState } from '@/localization/states/dateTimeFormatState';
import { detectDateFormat } from '@/localization/utils/detectDateFormat';
import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';
Expand Down Expand Up @@ -143,12 +145,12 @@ export const useAuth = () => {
? getDateFormatFromWorkspaceDateFormat(
user.workspaceMember.dateFormat,
)
: detectDateFormat(),
: DateFormat[detectDateFormat()],
timeFormat: isDefined(user.workspaceMember.timeFormat)
? getTimeFormatFromWorkspaceTimeFormat(
user.workspaceMember.timeFormat,
)
: detectTimeFormat(),
: TimeFormat[detectTimeFormat()],
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';

import { apiConfigState } from '@/client-config/states/apiConfigState';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { captchaProviderState } from '@/client-config/states/captchaProviderState';
import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState';
import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState';
import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { useGetClientConfigQuery } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';

export const ClientConfigProviderEffect = () => {
const setAuthProviders = useSetRecoilState(authProvidersState);
const setIsDebugMode = useSetRecoilState(isDebugModeState);
const setIsAnalyticsEnabled = useSetRecoilState(isAnalyticsEnabledState);

const setIsSignInPrefilled = useSetRecoilState(isSignInPrefilledState);
const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState);
Expand Down Expand Up @@ -50,6 +51,7 @@ export const ClientConfigProviderEffect = () => {
magicLink: false,
});
setIsDebugMode(data?.clientConfig.debugMode);
setIsAnalyticsEnabled(data?.clientConfig.analyticsEnabled);
setIsSignInPrefilled(data?.clientConfig.signInPrefilled);
setIsSignUpDisabled(data?.clientConfig.signUpDisabled);

Expand Down Expand Up @@ -84,6 +86,7 @@ export const ClientConfigProviderEffect = () => {
setCaptchaProvider,
setChromeExtensionId,
setApiConfig,
setIsAnalyticsEnabled,
]);

return <></>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const GET_CLIENT_CONFIG = gql`
signInPrefilled
signUpDisabled
debugMode
analyticsEnabled
support {
supportDriver
supportFrontChatId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';

export const isAnalyticsEnabledState = createState<boolean>({
key: 'isAnalyticsEnabled',
defaultValue: false,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { DateFormat } from '@/localization/constants/DateFormat';

type DateFormatWithoutYear = {
[K in keyof typeof DateFormat]: string;
};
export const DATE_FORMAT_WITHOUT_YEAR: DateFormatWithoutYear = {
SYSTEM: 'SYSTEM',
MONTH_FIRST: 'MMM d',
DAY_FIRST: 'd MMM',
YEAR_FIRST: 'MMM d',
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { DateFormat } from '@/localization/constants/DateFormat';
import { detectDateFormat } from '@/localization/utils/detectDateFormat';

describe('detectDateFormat', () => {
it('should return DateFormat.MONTH_FIRST if the detected format starts with month', () => {
it('should return MONTH_FIRST if the detected format starts with month', () => {
// Mock the Intl.DateTimeFormat to return a specific format
const mockDateTimeFormat = jest.fn().mockReturnValue({
formatToParts: () => [
Expand All @@ -16,10 +15,10 @@ describe('detectDateFormat', () => {

const result = detectDateFormat();

expect(result).toBe(DateFormat.MONTH_FIRST);
expect(result).toBe('MONTH_FIRST');
});

it('should return DateFormat.DAY_FIRST if the detected format starts with day', () => {
it('should return DAY_FIRST if the detected format starts with day', () => {
// Mock the Intl.DateTimeFormat to return a specific format
const mockDateTimeFormat = jest.fn().mockReturnValue({
formatToParts: () => [
Expand All @@ -32,10 +31,10 @@ describe('detectDateFormat', () => {

const result = detectDateFormat();

expect(result).toBe(DateFormat.DAY_FIRST);
expect(result).toBe('DAY_FIRST');
});

it('should return DateFormat.YEAR_FIRST if the detected format starts with year', () => {
it('should return YEAR_FIRST if the detected format starts with year', () => {
// Mock the Intl.DateTimeFormat to return a specific format
const mockDateTimeFormat = jest.fn().mockReturnValue({
formatToParts: () => [
Expand All @@ -48,10 +47,10 @@ describe('detectDateFormat', () => {

const result = detectDateFormat();

expect(result).toBe(DateFormat.YEAR_FIRST);
expect(result).toBe('YEAR_FIRST');
});

it('should return DateFormat.MONTH_FIRST by default if the detected format does not match any specific order', () => {
it('should return MONTH_FIRST by default if the detected format does not match any specific order', () => {
// Mock the Intl.DateTimeFormat to return a specific format
const mockDateTimeFormat = jest.fn().mockReturnValue({
formatToParts: () => [
Expand All @@ -64,6 +63,6 @@ describe('detectDateFormat', () => {

const result = detectDateFormat();

expect(result).toBe(DateFormat.MONTH_FIRST);
expect(result).toBe('MONTH_FIRST');
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { TimeFormat } from '@/localization/constants/TimeFormat';
import { detectTimeFormat } from '@/localization/utils/detectTimeFormat';

describe('detectTimeFormat', () => {
it('should return TimeFormat.HOUR_12 if the hour format is 12-hour', () => {
it('should return HOUR_12 if the hour format is 12-hour', () => {
// Mock the resolvedOptions method to return hour12 as true
const mockResolvedOptions = jest.fn(() => ({ hour12: true }));
Intl.DateTimeFormat = jest.fn().mockImplementation(() => ({
Expand All @@ -11,11 +10,11 @@ describe('detectTimeFormat', () => {

const result = detectTimeFormat();

expect(result).toBe(TimeFormat.HOUR_12);
expect(result).toBe('HOUR_12');
expect(mockResolvedOptions).toHaveBeenCalled();
});

it('should return TimeFormat.HOUR_24 if the hour format is 24-hour', () => {
it('should return HOUR_24 if the hour format is 24-hour', () => {
// Mock the resolvedOptions method to return hour12 as false
const mockResolvedOptions = jest.fn(() => ({ hour12: false }));
Intl.DateTimeFormat = jest.fn().mockImplementation(() => ({
Expand All @@ -24,7 +23,7 @@ describe('detectTimeFormat', () => {

const result = detectTimeFormat();

expect(result).toBe(TimeFormat.HOUR_24);
expect(result).toBe('HOUR_24');
expect(mockResolvedOptions).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { detectDateFormat } from '@/localization/utils/detectDateFormat';
import { formatDateISOStringToDateTimeSimplified } from '@/localization/utils/formatDateISOStringToDateTimeSimplified';
import { formatInTimeZone } from 'date-fns-tz';
// Mock the imported modules
jest.mock('@/localization/utils/detectDateFormat');
jest.mock('date-fns-tz');

describe('formatDateISOStringToDateTimeSimplified', () => {
const mockDate = new Date('2023-08-15T10:30:00Z');
const mockTimeZone = 'America/New_York';
const mockTimeFormat = 'HH:mm';

beforeEach(() => {
jest.resetAllMocks();
});

it('should format the date correctly when DATE_FORMAT is MONTH_FIRST', () => {
detectDateFormat.mockReturnValue('MONTH_FIRST');
formatInTimeZone.mockReturnValue('Oct 15 · 06:30');

const result = formatDateISOStringToDateTimeSimplified(
mockDate,
mockTimeZone,
mockTimeFormat,
);

expect(detectDateFormat).toHaveBeenCalled();
expect(formatInTimeZone).toHaveBeenCalledWith(
mockDate,
mockTimeZone,
'MMM d · HH:mm',
);
expect(result).toBe('Oct 15 · 06:30');
});

it('should format the date correctly when DATE_FORMAT is DAY_FIRST', () => {
detectDateFormat.mockReturnValue('DAY_FIRST');
formatInTimeZone.mockReturnValue('15 Oct · 06:30');

const result = formatDateISOStringToDateTimeSimplified(
mockDate,
mockTimeZone,
mockTimeFormat,
);

expect(detectDateFormat).toHaveBeenCalled();
expect(formatInTimeZone).toHaveBeenCalledWith(
mockDate,
mockTimeZone,
'd MMM · HH:mm',
);
expect(result).toBe('15 Oct · 06:30');
});

it('should use the provided time format', () => {
detectDateFormat.mockReturnValue('MONTH_FIRST');
formatInTimeZone.mockReturnValue('Oct 15 · 6:30 AM');

const result = formatDateISOStringToDateTimeSimplified(
mockDate,
mockTimeZone,
'h:mm aa',
);

expect(formatInTimeZone).toHaveBeenCalledWith(
mockDate,
mockTimeZone,
'MMM d · h:mm aa',
);
expect(result).toBe('Oct 15 · 6:30 AM');
});

it('should handle different time zones', () => {
detectDateFormat.mockReturnValue('MONTH_FIRST');
formatInTimeZone.mockReturnValue('Oct 16 · 02:30');

const result = formatDateISOStringToDateTimeSimplified(
mockDate,
'Asia/Tokyo',
mockTimeFormat,
);

expect(formatInTimeZone).toHaveBeenCalledWith(
mockDate,
'Asia/Tokyo',
'MMM d · HH:mm',
);
expect(result).toBe('Oct 16 · 02:30');
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DateFormat } from '@/localization/constants/DateFormat';

export const detectDateFormat = (): DateFormat => {
export const detectDateFormat = (): keyof typeof DateFormat => {
const date = new Date();
const formatter = new Intl.DateTimeFormat(navigator.language);
const parts = formatter.formatToParts(date);
Expand All @@ -9,9 +9,9 @@ export const detectDateFormat = (): DateFormat => {
.filter((part) => ['year', 'month', 'day'].includes(part.type))
.map((part) => part.type);

if (partOrder[0] === 'month') return DateFormat.MONTH_FIRST;
if (partOrder[0] === 'day') return DateFormat.DAY_FIRST;
if (partOrder[0] === 'year') return DateFormat.YEAR_FIRST;
if (partOrder[0] === 'month') return 'MONTH_FIRST';
if (partOrder[0] === 'day') return 'DAY_FIRST';
if (partOrder[0] === 'year') return 'YEAR_FIRST';

return DateFormat.MONTH_FIRST;
return 'MONTH_FIRST';
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { TimeFormat } from '@/localization/constants/TimeFormat';
import { isDefined } from '~/utils/isDefined';

export const detectTimeFormat = () => {
export const detectTimeFormat = (): keyof typeof TimeFormat => {
const isHour12 = Intl.DateTimeFormat(navigator.language, {
hour: 'numeric',
}).resolvedOptions().hour12;

if (isDefined(isHour12) && isHour12) {
return TimeFormat.HOUR_12;
return 'HOUR_12';
}

return TimeFormat.HOUR_24;
return 'HOUR_24';
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DATE_FORMAT_WITHOUT_YEAR } from '@/localization/constants/DateFormatWithoutYear';
import { TimeFormat } from '@/localization/constants/TimeFormat';
import { detectDateFormat } from '@/localization/utils/detectDateFormat';
import { formatInTimeZone } from 'date-fns-tz';

export const formatDateISOStringToDateTimeSimplified = (
date: Date,
timeZone: string,
timeFormat: TimeFormat,
) => {
const simplifiedDateFormat = DATE_FORMAT_WITHOUT_YEAR[detectDateFormat()];

return formatInTimeZone(
date,
timeZone,
`${simplifiedDateFormat} · ${timeFormat}`,
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const getDateFormatFromWorkspaceDateFormat = (
) => {
switch (workspaceDateFormat) {
case WorkspaceMemberDateFormatEnum.System:
return detectDateFormat();
return DateFormat[detectDateFormat()];
case WorkspaceMemberDateFormatEnum.MonthFirst:
return DateFormat.MONTH_FIRST;
case WorkspaceMemberDateFormatEnum.DayFirst:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const getTimeFormatFromWorkspaceTimeFormat = (
) => {
switch (workspaceTimeFormat) {
case WorkspaceMemberTimeFormatEnum.System:
return detectTimeFormat();
return TimeFormat[detectTimeFormat()];
case WorkspaceMemberTimeFormatEnum.Hour_24:
return TimeFormat.HOUR_24;
case WorkspaceMemberTimeFormatEnum.Hour_12:
Expand Down
Loading

0 comments on commit 8cadcdf

Please sign in to comment.