Logs show page (#4611)

* Being implementing events on the frontend

* Rename JSON to RAW JSON

* Fix handling of json field on frontend

* Log user id

* Add frontend tests

* Update packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/save-event-to-db.job.ts

Co-authored-by: Weiko <corentin@twenty.com>

* Move db calls to a dedicated repository

* Add server-side tests

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Félix Malfait
2024-03-22 14:01:16 +01:00
committed by GitHub
parent aee6d49ea9
commit d876b40056
38 changed files with 488 additions and 95 deletions

View File

@ -0,0 +1,22 @@
import { ReactElement } from 'react';
import { EventRow } from '@/activities/events/components/EventRow';
import { Event } from '@/activities/events/types/Event';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
type EventListProps = {
targetableObject: ActivityTargetableObject;
title: string;
events: Event[];
button?: ReactElement | false;
};
export const EventList = ({ events }: EventListProps) => {
return (
<>
{events &&
events.length > 0 &&
events.map((event: Event) => <EventRow key={event.id} event={event} />)}
</>
);
};

View File

@ -0,0 +1,11 @@
import { Event } from '@/activities/events/types/Event';
export const EventRow = ({ event }: { event: Event }) => {
return (
<>
<p>
{event.name}:<pre>{event.properties}</pre>
</p>
</>
);
};

View File

@ -0,0 +1,25 @@
import { isNonEmptyArray } from '@sniptt/guards';
import { EventList } from '@/activities/events/components/EventList';
import { useEvents } from '@/activities/events/hooks/useEvents';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
export const Events = ({
targetableObject,
}: {
targetableObject: ActivityTargetableObject;
}) => {
const { events } = useEvents(targetableObject);
if (!isNonEmptyArray(events)) {
return <div>No log yet</div>;
}
return (
<EventList
targetableObject={targetableObject}
title="All"
events={events ?? []}
/>
);
};

View File

@ -0,0 +1,91 @@
import { renderHook } from '@testing-library/react';
import { useEvents } from '@/activities/events/hooks/useEvents';
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
useFindManyRecords: jest.fn(),
}));
describe('useEvent', () => {
afterEach(() => {
jest.clearAllMocks();
});
it('fetches events correctly for a given targetableObject', () => {
const mockEvents = [
{
__typename: 'Event',
id: '166ec73f-26b1-4934-bb3b-c86c8513b99b',
opportunityId: null,
opportunity: null,
personId: null,
person: null,
company: {
__typename: 'Company',
address: 'Paris',
linkedinLink: {
__typename: 'Link',
label: '',
url: '',
},
xLink: {
__typename: 'Link',
label: '',
url: '',
},
position: 4,
domainName: 'microsoft.com',
employees: null,
createdAt: '2024-03-21T16:01:41.809Z',
annualRecurringRevenue: {
__typename: 'Currency',
amountMicros: 100000000,
currencyCode: 'USD',
},
idealCustomerProfile: false,
accountOwnerId: null,
updatedAt: '2024-03-22T08:28:44.812Z',
name: 'Microsoft',
id: '460b6fb1-ed89-413a-b31a-962986e67bb4',
},
workspaceMember: {
__typename: 'WorkspaceMember',
locale: 'en',
avatarUrl: '',
updatedAt: '2024-03-21T16:01:41.839Z',
name: {
__typename: 'FullName',
firstName: 'Tim',
lastName: 'Apple',
},
id: '20202020-0687-4c41-b707-ed1bfca972a7',
userEmail: 'tim@apple.dev',
colorScheme: 'Light',
createdAt: '2024-03-21T16:01:41.839Z',
userId: '20202020-9e3b-46d4-a556-88b9ddc2b034',
},
workspaceMemberId: '20202020-0687-4c41-b707-ed1bfca972a7',
createdAt: '2024-03-22T08:28:44.830Z',
name: 'updated.company',
companyId: '460b6fb1-ed89-413a-b31a-962986e67bb4',
properties: '{"diff": {"address": {"after": "Paris", "before": ""}}}',
updatedAt: '2024-03-22T08:28:44.830Z',
},
];
const mockTargetableObject = {
id: '1',
targetObjectNameSingular: 'Opportunity',
};
const useFindManyRecordsMock = jest.requireMock(
'@/object-record/hooks/useFindManyRecords',
);
useFindManyRecordsMock.useFindManyRecords.mockReturnValue({
records: mockEvents,
});
const { result } = renderHook(() => useEvents(mockTargetableObject));
expect(result.current.events).toEqual(mockEvents);
});
});

View File

@ -0,0 +1,28 @@
import { Event } from '@/activities/events/types/Event';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
// do we need to test this?
export const useEvents = (targetableObject: ActivityTargetableObject) => {
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
});
const { records: events } = useFindManyRecords({
objectNameSingular: CoreObjectNameSingular.Event,
filter: {
[targetableObjectFieldIdName]: {
eq: targetableObject.id,
},
},
orderBy: {
createdAt: 'DescNullsFirst',
},
});
return {
events: events as Event[],
};
};

View File

@ -0,0 +1,12 @@
export type Event = {
id: string;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
opportunityId: string | null;
companyId: string;
personId: string;
workspaceMemberId: string;
properties: any;
name: string;
};