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
10 changed files with 107 additions and 26 deletions

View File

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

View File

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

View File

@ -3,7 +3,7 @@ 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 useLinkedObject = (id: string) => { export const useLinkedObjectObjectMetadataItem = (id: string) => {
const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue( const objectMetadataItems: ObjectMetadataItem[] = useRecoilValue(
objectMetadataItemsState, 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 { TimelineActivity } from '@/activities/timelineActivities/types/TimelineActivity';
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName'; import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
@ -19,10 +20,10 @@ export const useTimelineActivities = (
}); });
const { const {
records: TimelineActivities, records: timelineActivities,
loading, loading: loadingTimelineActivities,
fetchMoreRecords, fetchMoreRecords,
} = useFindManyRecords({ } = useFindManyRecords<TimelineActivity>({
objectNameSingular: CoreObjectNameSingular.TimelineActivity, objectNameSingular: CoreObjectNameSingular.TimelineActivity,
filter: { filter: {
[targetableObjectFieldIdName]: { [targetableObjectFieldIdName]: {
@ -38,8 +39,17 @@ export const useTimelineActivities = (
fetchPolicy: 'cache-and-network', 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 { return {
timelineActivities: TimelineActivities as TimelineActivity[], timelineActivities,
loading, loading,
fetchMoreRecords, fetchMoreRecords,
}; };

View File

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

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

View File

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