Enforce front project structure through ESLINT (#7863)
Fixes: https://github.com/twentyhq/twenty/issues/7329
This commit is contained in:
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
});
|
||||
};
|
||||
@ -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);
|
||||
});
|
||||
};
|
||||
@ -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';
|
||||
};
|
||||
@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user