Activity as standard object (#6219)
In this PR I layout the first steps to migrate Activity to a traditional Standard objects Since this is a big transition, I'd rather split it into several deployments / PRs <img width="1512" alt="image" src="https://github.com/user-attachments/assets/012e2bbf-9d1b-4723-aaf6-269ef588b050"> --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: Faisal-imtiyaz123 <142205282+Faisal-imtiyaz123@users.noreply.github.com> Co-authored-by: Prateek Jain <prateekj1171998@gmail.com>
This commit is contained in:
@ -1,9 +1,9 @@
|
||||
import { useContext } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { useLinkedObject } from '@/activities/timeline/hooks/useLinkedObject';
|
||||
import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext';
|
||||
import { useLinkedObject } from '@/activities/timelineActivities/hooks/useLinkedObject';
|
||||
import { EventIconDynamicComponent } from '@/activities/timelineActivities/rows/components/EventIconDynamicComponent';
|
||||
import { EventRowDynamicComponent } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
|
||||
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
||||
|
||||
@ -2,8 +2,8 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
|
||||
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||
import { TimelineCreateButtonGroup } from '@/activities/timeline/components/TimelineCreateButtonGroup';
|
||||
import { EventList } from '@/activities/timelineActivities/components/EventList';
|
||||
import { TimelineCreateButtonGroup } from '@/activities/timelineActivities/components/TimelineCreateButtonGroup';
|
||||
import { useTimelineActivities } from '@/activities/timelineActivities/hooks/useTimelineActivities';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import AnimatedPlaceholder from '@/ui/layout/animated-placeholder/components/AnimatedPlaceholder';
|
||||
@ -35,8 +35,10 @@ const StyledMainContainer = styled.div`
|
||||
|
||||
export const TimelineActivities = ({
|
||||
targetableObject,
|
||||
isInRightDrawer = false,
|
||||
}: {
|
||||
targetableObject: ActivityTargetableObject;
|
||||
isInRightDrawer?: boolean;
|
||||
}) => {
|
||||
const { timelineActivities, loading, fetchMoreRecords } =
|
||||
useTimelineActivities(targetableObject);
|
||||
@ -63,7 +65,7 @@ export const TimelineActivities = ({
|
||||
There are no activities associated with this record.{' '}
|
||||
</AnimatedPlaceholderEmptySubTitle>
|
||||
</AnimatedPlaceholderEmptyTextContainer>
|
||||
<TimelineCreateButtonGroup />
|
||||
<TimelineCreateButtonGroup isInRightDrawer={isInRightDrawer} />
|
||||
</AnimatedPlaceholderEmptyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useActivities } from '@/activities/hooks/useActivities';
|
||||
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timeline/constants/FindManyTimelineActivitiesOrderBy';
|
||||
import { FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY } from '@/activities/timelineActivities/constants/FindManyTimelineActivitiesOrderBy';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const TimelineActivitiesQueryEffect = ({
|
||||
@ -9,6 +10,8 @@ export const TimelineActivitiesQueryEffect = ({
|
||||
targetableObject: ActivityTargetableObject;
|
||||
}) => {
|
||||
useActivities({
|
||||
objectNameSingular:
|
||||
targetableObject.targetObjectNameSingular as CoreObjectNameSingular,
|
||||
targetableObjects: [targetableObject],
|
||||
activitiesFilters: {},
|
||||
activitiesOrderByVariables: FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY,
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { IconCheckbox, IconNotes, IconPaperclip } from 'twenty-ui';
|
||||
|
||||
import { Button } from '@/ui/input/button/components/Button';
|
||||
import { ButtonGroup } from '@/ui/input/button/components/ButtonGroup';
|
||||
import { TAB_LIST_COMPONENT_ID } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
|
||||
export const TimelineCreateButtonGroup = ({
|
||||
isInRightDrawer = false,
|
||||
}: {
|
||||
isInRightDrawer?: boolean;
|
||||
}) => {
|
||||
const { activeTabIdState } = useTabList(
|
||||
`${TAB_LIST_COMPONENT_ID}-${isInRightDrawer}`,
|
||||
);
|
||||
const setActiveTabId = useSetRecoilState(activeTabIdState);
|
||||
|
||||
return (
|
||||
<ButtonGroup variant={'secondary'}>
|
||||
<Button
|
||||
Icon={IconNotes}
|
||||
title="Note"
|
||||
onClick={() => {
|
||||
setActiveTabId('notes');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
Icon={IconCheckbox}
|
||||
title="Task"
|
||||
onClick={() => {
|
||||
setActiveTabId('tasks');
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
Icon={IconPaperclip}
|
||||
title="File"
|
||||
onClick={() => setActiveTabId('files')}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,8 @@
|
||||
import { RecordGqlOperationOrderBy } from '@/object-record/graphql/types/RecordGqlOperationOrderBy';
|
||||
|
||||
export const FIND_MANY_TIMELINE_ACTIVITIES_ORDER_BY: RecordGqlOperationOrderBy =
|
||||
[
|
||||
{
|
||||
createdAt: 'DescNullsFirst',
|
||||
},
|
||||
];
|
||||
@ -0,0 +1,16 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const useLinkedObject = (id: string) => {
|
||||
const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
|
||||
return (
|
||||
objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.id === id,
|
||||
) ?? null
|
||||
);
|
||||
};
|
||||
@ -1,7 +1,9 @@
|
||||
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
|
||||
// do we need to test this?
|
||||
@ -12,6 +14,10 @@ export const useTimelineActivities = (
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.TimelineActivity,
|
||||
});
|
||||
|
||||
const {
|
||||
records: TimelineActivities,
|
||||
loading,
|
||||
@ -28,18 +34,7 @@ export const useTimelineActivities = (
|
||||
createdAt: 'DescNullsFirst',
|
||||
},
|
||||
],
|
||||
recordGqlFields: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
linkedObjectMetadataId: true,
|
||||
linkedRecordCachedName: true,
|
||||
linkedRecordId: true,
|
||||
name: true,
|
||||
properties: true,
|
||||
happensAt: true,
|
||||
workspaceMember: true,
|
||||
person: true,
|
||||
},
|
||||
recordGqlFields: generateDepthOneRecordGqlFields({ objectMetadataItem }),
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
StyledEventRowItemAction,
|
||||
StyledEventRowItemColumn,
|
||||
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
|
||||
type EventRowActivityProps = EventRowDynamicComponentProps;
|
||||
@ -19,11 +20,10 @@ const StyledLinkedActivity = styled.span`
|
||||
export const EventRowActivity = ({
|
||||
event,
|
||||
authorFullName,
|
||||
}: EventRowActivityProps) => {
|
||||
objectNameSingular,
|
||||
}: EventRowActivityProps & { objectNameSingular: CoreObjectNameSingular }) => {
|
||||
const [, eventAction] = event.name.split('.');
|
||||
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||
|
||||
if (!event.linkedRecordId) {
|
||||
throw new Error('Could not find linked record id for event');
|
||||
}
|
||||
@ -32,6 +32,10 @@ export const EventRowActivity = ({
|
||||
recordStoreFamilyState(event.linkedRecordId),
|
||||
);
|
||||
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledEventRowItemColumn>{authorFullName}</StyledEventRowItemColumn>
|
||||
|
||||
@ -5,6 +5,7 @@ import { EventRowCalendarEvent } from '@/activities/timelineActivities/rows/cale
|
||||
import { EventRowMainObject } from '@/activities/timelineActivities/rows/main-object/components/EventRowMainObject';
|
||||
import { EventRowMessage } from '@/activities/timelineActivities/rows/message/components/EventRowMessage';
|
||||
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export interface EventRowDynamicComponentProps {
|
||||
@ -57,8 +58,7 @@ export const EventRowDynamicComponent = ({
|
||||
authorFullName={authorFullName}
|
||||
/>
|
||||
);
|
||||
case 'task':
|
||||
case 'note':
|
||||
case 'linked-task':
|
||||
return (
|
||||
<EventRowActivity
|
||||
labelIdentifierValue={labelIdentifierValue}
|
||||
@ -66,6 +66,18 @@ export const EventRowDynamicComponent = ({
|
||||
mainObjectMetadataItem={mainObjectMetadataItem}
|
||||
linkedObjectMetadataItem={linkedObjectMetadataItem}
|
||||
authorFullName={authorFullName}
|
||||
objectNameSingular={CoreObjectNameSingular.Task}
|
||||
/>
|
||||
);
|
||||
case 'linked-note':
|
||||
return (
|
||||
<EventRowActivity
|
||||
labelIdentifierValue={labelIdentifierValue}
|
||||
event={event}
|
||||
mainObjectMetadataItem={mainObjectMetadataItem}
|
||||
linkedObjectMetadataItem={linkedObjectMetadataItem}
|
||||
authorFullName={authorFullName}
|
||||
objectNameSingular={CoreObjectNameSingular.Note}
|
||||
/>
|
||||
);
|
||||
case mainObjectMetadataItem?.nameSingular:
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
|
||||
export const objectShowPageTargetableObjectState =
|
||||
atom<ActivityTargetableObject | null>({
|
||||
key: 'objectShowPageTargetableObjectState',
|
||||
default: null,
|
||||
});
|
||||
@ -13,4 +13,4 @@ export type TimelineActivity = {
|
||||
linkedRecordId: string;
|
||||
linkedObjectMetadataId: string;
|
||||
__typename: 'TimelineActivity';
|
||||
};
|
||||
} & Record<string, any>;
|
||||
|
||||
Reference in New Issue
Block a user