Time line activity wrong type (#11673)

# Introduction
Closes https://github.com/twentyhq/core-team-issues/issues/874

`TimeLineActivity` fields `` and `` were typed as required whereas in
reality are nullable, resulting in the related sentry error.
Refactored the type then the related components in order to handle
nullable use case
This commit is contained in:
Paul Rastoin
2025-04-22 17:17:08 +02:00
committed by GitHub
parent efab98a8f8
commit de1489aabb
6 changed files with 49 additions and 18 deletions

View File

@ -3,11 +3,14 @@ import { useRecoilValue } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useLinkedObjectObjectMetadataItem = (id: string) => { export const useLinkedObjectObjectMetadataItem = (id: string | null) => {
const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue( const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue(
objectMetadataItemsState, objectMetadataItemsState,
); );
if (id === null) {
return null;
}
return ( return (
objectMetadataItems.find( objectMetadataItems.find(
(objectMetadataItem) => objectMetadataItem.id === id, (objectMetadataItem) => objectMetadataItem.id === id,

View File

@ -6,6 +6,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { isDefined } from 'twenty-shared/utils';
// do we need to test this? // do we need to test this?
export const useTimelineActivities = ( export const useTimelineActivities = (
@ -41,7 +42,8 @@ export const useTimelineActivities = (
const activityIds = timelineActivities const activityIds = timelineActivities
.filter((timelineActivity) => timelineActivity.name.match(/note|task/i)) .filter((timelineActivity) => timelineActivity.name.match(/note|task/i))
.map((timelineActivity) => timelineActivity.linkedRecordId); .map((timelineActivity) => timelineActivity.linkedRecordId)
.filter(isDefined);
const { loading: loadingLinkedObjectsTitle } = const { loading: loadingLinkedObjectsTitle } =
useLinkedObjectsTitle(activityIds); useLinkedObjectsTitle(activityIds);

View File

@ -5,12 +5,13 @@ import {
StyledEventRowItemAction, StyledEventRowItemAction,
StyledEventRowItemColumn, StyledEventRowItemColumn,
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent'; } from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
import { isTimelineActivityWithLinkedRecord } from '@/activities/timeline-activities/types/TimelineActivity';
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { OverflowingTextWithTooltip } from 'twenty-ui/display'; import { OverflowingTextWithTooltip } from 'twenty-ui/display';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
type EventRowActivityProps = EventRowDynamicComponentProps; type EventRowActivityProps = EventRowDynamicComponentProps;
@ -67,7 +68,7 @@ export const EventRowActivity = ({
const eventObject = eventLinkedObject.replace('linked-', ''); const eventObject = eventLinkedObject.replace('linked-', '');
if (!event.linkedRecordId) { if (!isTimelineActivityWithLinkedRecord(event)) {
throw new Error('Could not find linked record id for event'); throw new Error('Could not find linked record id for event');
} }
@ -81,11 +82,18 @@ export const EventRowActivity = ({
const activityInStore = getActivityFromCache(event.linkedRecordId); const activityInStore = getActivityFromCache(event.linkedRecordId);
const activityTitle = isNonEmptyString(activityInStore?.title) const computeActivityTitle = () => {
? activityInStore?.title if (isNonEmptyString(activityInStore?.title)) {
: isNonEmptyString(event.linkedRecordCachedName) return activityInStore?.title;
? event.linkedRecordCachedName }
: 'Untitled';
if (isNonEmptyString(event.linkedRecordCachedName)) {
return event.linkedRecordCachedName;
}
return 'Untitled';
};
const activityTitle = computeActivityTitle();
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();

View File

@ -9,6 +9,7 @@ import {
StyledEventRowItemAction, StyledEventRowItemAction,
StyledEventRowItemColumn, StyledEventRowItemColumn,
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent'; } from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
import { isTimelineActivityWithLinkedRecord } from '@/activities/timeline-activities/types/TimelineActivity';
type EventRowCalendarEventProps = EventRowDynamicComponentProps; type EventRowCalendarEventProps = EventRowDynamicComponentProps;
@ -45,9 +46,11 @@ export const EventRowCalendarEvent = ({
</StyledEventRowItemAction> </StyledEventRowItemAction>
<EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} /> <EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} />
</StyledRowContainer> </StyledRowContainer>
<EventCard isOpen={isOpen}> {isTimelineActivityWithLinkedRecord(event) && (
<EventCardCalendarEvent calendarEventId={event.linkedRecordId} /> <EventCard isOpen={isOpen}>
</EventCard> <EventCardCalendarEvent calendarEventId={event.linkedRecordId} />
</EventCard>
)}
</StyledEventRowCalendarEventContainer> </StyledEventRowCalendarEventContainer>
); );
}; };

View File

@ -9,6 +9,7 @@ import {
StyledEventRowItemColumn, StyledEventRowItemColumn,
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent'; } from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
import { EventCardMessage } from '@/activities/timeline-activities/rows/message/components/EventCardMessage'; import { EventCardMessage } from '@/activities/timeline-activities/rows/message/components/EventCardMessage';
import { isTimelineActivityWithLinkedRecord } from '@/activities/timeline-activities/types/TimelineActivity';
type EventRowMessageProps = EventRowDynamicComponentProps; type EventRowMessageProps = EventRowDynamicComponentProps;
@ -49,10 +50,12 @@ export const EventRowMessage = ({
<EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} /> <EventCardToggleButton isOpen={isOpen} setIsOpen={setIsOpen} />
</StyledRowContainer> </StyledRowContainer>
<EventCard isOpen={isOpen}> <EventCard isOpen={isOpen}>
<EventCardMessage {isTimelineActivityWithLinkedRecord(event) && (
messageId={event.linkedRecordId} <EventCardMessage
authorFullName={authorFullName} messageId={event.linkedRecordId}
/> authorFullName={authorFullName}
/>
)}
</EventCard> </EventCard>
</StyledEventRowMessageContainer> </StyledEventRowMessageContainer>
); );

View File

@ -1,3 +1,4 @@
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceMember } from '~/generated/graphql'; import { WorkspaceMember } from '~/generated/graphql';
export type TimelineActivity = { export type TimelineActivity = {
@ -10,7 +11,18 @@ export type TimelineActivity = {
properties: any; properties: any;
name: string; name: string;
linkedRecordCachedName: string; linkedRecordCachedName: string;
linkedRecordId: string; linkedRecordId: string | null;
linkedObjectMetadataId: string; linkedObjectMetadataId: string | null;
__typename: 'TimelineActivity'; __typename: 'TimelineActivity';
} & Record<string, any>; } & Record<string, any>;
export type TimelineActivityWithRecord = TimelineActivity & {
linkedRecordId: string;
linkedObjectMetadataId: string;
};
export const isTimelineActivityWithLinkedRecord = (
timelineActivity: TimelineActivity,
): timelineActivity is TimelineActivityWithRecord =>
isDefined(timelineActivity.linkedObjectMetadataId) &&
isDefined(timelineActivity.linkedRecordId);