Enforce front project structure through ESLINT (#7863)

Fixes: https://github.com/twentyhq/twenty/issues/7329
This commit is contained in:
Charles Bochet
2024-10-20 20:20:19 +02:00
committed by GitHub
parent f801f3aa9f
commit eccf0bf8ba
260 changed files with 500 additions and 290 deletions

View File

@ -0,0 +1,141 @@
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { filterOutInvalidTimelineActivities } from '@/activities/timeline-activities/utils/filterOutInvalidTimelineActivities';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
const noteObjectMetadataItem = {
nameSingular: CoreObjectNameSingular.Note,
namePlural: 'notes',
fields: [{ name: 'field1' }, { name: 'field2' }, { name: 'field3' }],
} as ObjectMetadataItem;
describe('filterOutInvalidTimelineActivities', () => {
it('should filter out TimelineActivities with deleted fields from the properties diff', () => {
const events = [
{
id: '1',
name: 'event1',
properties: {
diff: {
field1: { before: 'value1', after: 'value2' },
field2: { before: 'value3', after: 'value4' },
field3: { before: 'value5', after: 'value6' },
},
},
},
{
id: '2',
name: 'event2',
properties: {
diff: {
field1: { before: 'value7', after: 'value8' },
field2: { before: 'value9', after: 'value10' },
field4: { before: 'value11', after: 'value12' },
},
},
},
] as TimelineActivity[];
const mainObjectMetadataItem = {
nameSingular: 'objectNameSingular',
namePlural: 'objectNamePlural',
fields: [{ name: 'field1' }, { name: 'field2' }, { name: 'field3' }],
} as ObjectMetadataItem;
const filteredEvents = filterOutInvalidTimelineActivities(
events,
'objectNameSingular',
[mainObjectMetadataItem, noteObjectMetadataItem],
);
expect(filteredEvents).toEqual([
{
id: '1',
name: 'event1',
properties: {
diff: {
field1: { before: 'value1', after: 'value2' },
field2: { before: 'value3', after: 'value4' },
field3: { before: 'value5', after: 'value6' },
},
},
},
{
id: '2',
name: 'event2',
properties: {
diff: {
field1: { before: 'value7', after: 'value8' },
field2: { before: 'value9', after: 'value10' },
},
},
},
]);
});
it('should return an empty array if all TimelineActivities have deleted fields in the properties diff', () => {
const events = [
{
id: '1',
name: 'event1',
properties: {
diff: {
field3: { before: 'value5', after: 'value6' },
},
},
},
{
id: '2',
name: 'event2',
properties: {
diff: {
field4: { before: 'value11', after: 'value12' },
},
},
},
] as TimelineActivity[];
const mainObjectMetadataItem = {
nameSingular: 'objectNameSingular',
namePlural: 'objectNamePlural',
fields: [{ name: 'field1' }, { name: 'field2' }],
} as ObjectMetadataItem;
const filteredEvents = filterOutInvalidTimelineActivities(
events,
'objectNameSingular',
[mainObjectMetadataItem, noteObjectMetadataItem],
);
expect(filteredEvents).toEqual([]);
});
it('should return the same TimelineActivities if there are no properties diffs', () => {
const events = [
{
id: '1',
name: 'event1',
properties: {},
},
{
id: '2',
name: 'event2',
properties: {},
},
] as TimelineActivity[];
const mainObjectMetadataItem = {
nameSingular: 'objectNameSingular',
namePlural: 'objectNamePlural',
fields: [{ name: 'field1' }, { name: 'field2' }],
} as ObjectMetadataItem;
const filteredEvents = filterOutInvalidTimelineActivities(
events,
'objectNameSingular',
[mainObjectMetadataItem, noteObjectMetadataItem],
);
expect(filteredEvents).toEqual(events);
});
});

View File

@ -0,0 +1,63 @@
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { getTimelineActivityAuthorFullName } from '@/activities/timeline-activities/utils/getTimelineActivityAuthorFullName';
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
describe('getTimelineActivityAuthorFullName', () => {
it('should return "You" if the current workspace member is the author', () => {
const event = {
workspaceMember: {
id: '123',
name: {
firstName: 'John',
lastName: 'Doe',
},
},
};
const currentWorkspaceMember = {
id: '123',
};
const result = getTimelineActivityAuthorFullName(
event as TimelineActivity,
currentWorkspaceMember as CurrentWorkspaceMember,
);
expect(result).toBe('You');
});
it('should return the full name of the workspace member if they are not the current workspace member', () => {
const event = {
workspaceMember: {
id: '456',
name: {
firstName: 'Jane',
lastName: 'Smith',
},
},
};
const currentWorkspaceMember = {
id: '123',
};
const result = getTimelineActivityAuthorFullName(
event as TimelineActivity,
currentWorkspaceMember as CurrentWorkspaceMember,
);
expect(result).toBe('Jane Smith');
});
it('should return "Twenty" if the workspace member is not defined', () => {
const event = {};
const currentWorkspaceMember = {
id: '123',
};
const result = getTimelineActivityAuthorFullName(
event as TimelineActivity,
currentWorkspaceMember as CurrentWorkspaceMember,
);
expect(result).toBe('Twenty');
});
});

View File

@ -0,0 +1,19 @@
import { mockedTimelineActivities } from '~/testing/mock-data/timeline-activities';
import { groupEventsByMonth } from '../groupEventsByMonth';
describe('groupEventsByMonth', () => {
it('should group activities by month', () => {
const grouped = groupEventsByMonth(mockedTimelineActivities);
expect(grouped).toHaveLength(2);
expect(grouped[0].items).toHaveLength(4);
expect(grouped[1].items).toHaveLength(1);
expect(grouped[0].year).toBe(2023);
expect(grouped[1].year).toBe(2022);
expect(grouped[0].month).toBe(3);
expect(grouped[1].month).toBe(4);
});
});

View File

@ -0,0 +1,62 @@
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const filterOutInvalidTimelineActivities = (
timelineActivities: TimelineActivity[],
mainObjectSingularName: string,
objectMetadataItems: ObjectMetadataItem[],
): TimelineActivity[] => {
const mainObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === mainObjectSingularName,
);
const noteObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === CoreObjectNameSingular.Note,
);
if (!mainObjectMetadataItem || !noteObjectMetadataItem) {
throw new Error('Object metadata items not found');
}
const fieldMetadataItemMap = new Map(
mainObjectMetadataItem.fields.map((field) => [field.name, field]),
);
const noteFieldMetadataItemMap = new Map(
noteObjectMetadataItem.fields.map((field) => [field.name, field]),
);
return timelineActivities.filter((timelineActivity) => {
const diff = timelineActivity.properties?.diff;
const canSkipValidation = !diff;
if (canSkipValidation) {
return true;
}
const isNoteOrTask =
timelineActivity.name.startsWith('linked-note') ||
timelineActivity.name.startsWith('linked-task');
const validDiffEntries = Object.entries(diff).filter(([diffKey]) =>
isNoteOrTask
? // Note and Task objects have the same field metadata
noteFieldMetadataItemMap.has(diffKey)
: fieldMetadataItemMap.has(diffKey),
);
if (validDiffEntries.length === 0) {
return false;
}
timelineActivity.properties = {
...timelineActivity.properties,
diff: Object.fromEntries(validDiffEntries),
};
return true;
});
};

View File

@ -0,0 +1,12 @@
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { TimelineActivityLinkedObject } from '@/activities/timeline-activities/types/TimelineActivityLinkedObject';
export const filterTimelineActivityByLinkedObjectTypes =
(linkedObjectTypes: TimelineActivityLinkedObject[]) =>
(timelineActivity: TimelineActivity) => {
return linkedObjectTypes.some((linkedObjectType) => {
const linkedObjectPartInName = timelineActivity.name.split('.')[0];
return linkedObjectPartInName.includes(linkedObjectType);
});
};

View File

@ -0,0 +1,15 @@
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
import { isDefined } from '~/utils/isDefined';
export const getTimelineActivityAuthorFullName = (
event: TimelineActivity,
currentWorkspaceMember: CurrentWorkspaceMember,
) => {
if (isDefined(event.workspaceMember)) {
return currentWorkspaceMember.id === event.workspaceMember.id
? 'You'
: `${event.workspaceMember?.name.firstName} ${event.workspaceMember?.name.lastName}`;
}
return 'Twenty';
};

View File

@ -0,0 +1,33 @@
import { TimelineActivity } from '@/activities/timeline-activities/types/TimelineActivity';
import { isDefined } from '~/utils/isDefined';
export type EventGroup = {
month: number;
year: number;
items: TimelineActivity[];
};
export const groupEventsByMonth = (events: TimelineActivity[]) => {
const acitivityGroups: EventGroup[] = [];
for (const event of events) {
const d = new Date(event.createdAt);
const month = d.getMonth();
const year = d.getFullYear();
const matchingGroup = acitivityGroups.find(
(x) => x.year === year && x.month === month,
);
if (isDefined(matchingGroup)) {
matchingGroup.items.push(event);
} else {
acitivityGroups.push({
year,
month,
items: [event],
});
}
}
return acitivityGroups.sort((a, b) => b.year - a.year || b.month - a.month);
};