add dynamic dates for webhookGraphDataUsage (#7720)

**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
This commit is contained in:
Ana Sofia Marin Alexandre
2024-10-18 11:00:21 +02:00
committed by GitHub
parent 0c24001e23
commit 8cadcdf577
28 changed files with 631 additions and 132 deletions

View File

@ -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',
};

View File

@ -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: () => [
@ -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: () => [
@ -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: () => [
@ -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: () => [
@ -64,6 +63,6 @@ describe('detectDateFormat', () => {
const result = detectDateFormat();
expect(result).toBe(DateFormat.MONTH_FIRST);
expect(result).toBe('MONTH_FIRST');
});
});

View File

@ -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(() => ({
@ -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(() => ({
@ -24,7 +23,7 @@ describe('detectTimeFormat', () => {
const result = detectTimeFormat();
expect(result).toBe(TimeFormat.HOUR_24);
expect(result).toBe('HOUR_24');
expect(mockResolvedOptions).toHaveBeenCalled();
});
});

View File

@ -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');
});
});

View File

@ -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);
@ -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';
};

View File

@ -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';
};

View File

@ -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}`,
);
};

View File

@ -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:

View File

@ -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: