Fix note linked text in timeline view (in dark mode) (#6944)

This PR Fixes https://github.com/twentyhq/twenty/issues/6942

Other improvements : 
- Fetch activities (note and task) title only when loading timeline, so
we don't always have a clickable title.
- Fixed IconButton width regression.

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Gaurav Khanna 2024-09-10 07:33:08 -07:00 committed by GitHub
parent d1b4f85e8c
commit 91187dcf82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 107 additions and 26 deletions

View File

@ -3,7 +3,8 @@ import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { TimelineActivityContext } from '@/activities/timelineActivities/contexts/TimelineActivityContext';
import { useLinkedObject } from '@/activities/timelineActivities/hooks/useLinkedObject';
import { useLinkedObjectObjectMetadataItem } from '@/activities/timelineActivities/hooks/useLinkedObjectObjectMetadataItem';
import { EventIconDynamicComponent } from '@/activities/timelineActivities/rows/components/EventIconDynamicComponent';
import { EventRowDynamicComponent } from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
@ -100,7 +101,7 @@ export const EventRow = ({
const { labelIdentifierValue } = useContext(TimelineActivityContext);
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(event.createdAt);
const linkedObjectMetadataItem = useLinkedObject(
const linkedObjectMetadataItem = useLinkedObjectObjectMetadataItem(
event.linkedObjectMetadataId,
);

View File

@ -46,7 +46,7 @@ export const TimelineActivities = ({
const isTimelineActivitiesEmpty =
!timelineActivities || timelineActivities.length === 0;
if (loading && isTimelineActivitiesEmpty) {
if (loading) {
return <SkeletonLoader withSubSections />;
}

View File

@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const useLinkedObject = (id: string) => {
export const useLinkedObjectObjectMetadataItem = (id: string) => {
const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue(
objectMetadataItemsState,
);

View File

@ -0,0 +1,43 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
import { isNonEmptyArray } from '@sniptt/guards';
export const useLinkedObjectsTitle = (linkedObjectIds: string[]) => {
const { loading } = useCombinedFindManyRecords({
skip: !isNonEmptyArray(linkedObjectIds),
operationSignatures: [
{
objectNameSingular: CoreObjectNameSingular.Task,
variables: {
filter: {
id: {
in: linkedObjectIds ?? [],
},
},
},
fields: {
id: true,
title: true,
},
},
{
objectNameSingular: CoreObjectNameSingular.Note,
variables: {
filter: {
id: {
in: linkedObjectIds ?? [],
},
},
},
fields: {
id: true,
title: true,
},
},
],
});
return {
loading,
};
};

View File

@ -1,3 +1,4 @@
import { useLinkedObjectsTitle } from '@/activities/timelineActivities/hooks/useLinkedObjectsTitle';
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
@ -19,10 +20,10 @@ export const useTimelineActivities = (
});
const {
records: TimelineActivities,
loading,
records: timelineActivities,
loading: loadingTimelineActivities,
fetchMoreRecords,
} = useFindManyRecords({
} = useFindManyRecords<TimelineActivity>({
objectNameSingular: CoreObjectNameSingular.TimelineActivity,
filter: {
[targetableObjectFieldIdName]: {
@ -38,8 +39,17 @@ export const useTimelineActivities = (
fetchPolicy: 'cache-and-network',
});
const activityIds = timelineActivities
.filter((timelineActivity) => timelineActivity.name.match(/note|task/i))
.map((timelineActivity) => timelineActivity.linkedRecordId);
const { loading: loadingLinkedObjectsTitle } =
useLinkedObjectsTitle(activityIds);
const loading = loadingTimelineActivities || loadingLinkedObjectsTitle;
return {
timelineActivities: TimelineActivities as TimelineActivity[],
timelineActivities,
loading,
fetchMoreRecords,
};

View File

@ -1,5 +1,4 @@
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import {
@ -8,15 +7,21 @@ import {
StyledEventRowItemColumn,
} from '@/activities/timelineActivities/rows/components/EventRowDynamicComponent';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { isNonEmptyString } from '@sniptt/guards';
type EventRowActivityProps = EventRowDynamicComponentProps;
const StyledLinkedActivity = styled.span`
color: ${({ theme }) => theme.font.color.primary};
cursor: pointer;
text-decoration: underline;
`;
export const StyledEventRowItemText = styled.span`
color: ${({ theme }) => theme.font.color.primary};
`;
export const EventRowActivity = ({
event,
authorFullName,
@ -30,9 +35,21 @@ export const EventRowActivity = ({
throw new Error('Could not find linked record id for event');
}
const [activityInStore] = useRecoilState(
recordStoreFamilyState(event.linkedRecordId),
);
const getActivityFromCache = useGetRecordFromCache({
objectNameSingular,
recordGqlFields: {
id: true,
title: true,
},
});
const activityInStore = getActivityFromCache(event.linkedRecordId);
const activityTitle = isNonEmptyString(activityInStore?.title)
? activityInStore?.title
: isNonEmptyString(event.linkedRecordCachedName)
? event.linkedRecordCachedName
: 'Untitled';
const openActivityRightDrawer = useOpenActivityRightDrawer({
objectNameSingular,
@ -44,15 +61,11 @@ export const EventRowActivity = ({
<StyledEventRowItemAction>
{`${eventAction} a related ${eventObject}`}
</StyledEventRowItemAction>
{activityInStore ? (
<StyledLinkedActivity
onClick={() => openActivityRightDrawer(event.linkedRecordId)}
>
{event.linkedRecordCachedName}
</StyledLinkedActivity>
) : (
<span>{event.linkedRecordCachedName}</span>
)}
<StyledLinkedActivity
onClick={() => openActivityRightDrawer(event.linkedRecordId)}
>
{activityTitle}
</StyledLinkedActivity>
</>
);
};

View File

@ -0,0 +1 @@
export type TimelineActivityLinkedObject = 'note' | 'task';

View File

@ -0,0 +1,12 @@
import { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { TimelineActivityLinkedObject } from '@/activities/timelineActivities/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

@ -11,13 +11,13 @@ export const useCombinedFindManyRecords = ({
skip = false,
}: {
operationSignatures: RecordGqlOperationSignature[];
skip: boolean;
skip?: boolean;
}) => {
const findManyQuery = useGenerateCombinedFindManyRecordsQuery({
operationSignatures,
});
const { data } = useQuery<MultiObjectRecordQueryResult>(
const { data, loading } = useQuery<MultiObjectRecordQueryResult>(
findManyQuery ?? EMPTY_QUERY,
{
skip,
@ -35,5 +35,6 @@ export const useCombinedFindManyRecords = ({
return {
result: resultWithoutConnection,
loading,
};
};

View File

@ -1,6 +1,6 @@
import React from 'react';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import React from 'react';
import { IconComponent } from 'twenty-ui';
export type IconButtonSize = 'medium' | 'small';
@ -233,7 +233,7 @@ const StyledButton = styled.button<
white-space: nowrap;
width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
min-width: ${({ size }) => (size === 'small' ? '24px' : '32px')};
&:focus {
outline: none;