Fix issues with one to many activity targets (#15656)
This PR fixes issues with one to many activity target bugs described here : https://github.com/twentyhq/twenty/issues/14280#issuecomment-3490170714 It also improves the request that fetches notes and tasks in index pages, to only fetch the notes and tasks identifier fields, thus reducing the amount of network load. Fixes https://github.com/twentyhq/twenty/issues/14280
This commit is contained in:
@@ -74,15 +74,6 @@ export const RecordChip = ({
|
||||
|
||||
// TODO temporary until we create a record show page for Workspaces members
|
||||
|
||||
const avatarChip = (
|
||||
<AvatarChip
|
||||
placeholder={recordChipData.name}
|
||||
placeholderColorSeed={record.id}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
/>
|
||||
);
|
||||
|
||||
if (
|
||||
forceDisableClick ||
|
||||
objectNameSingular === CoreObjectNameSingular.WorkspaceMember
|
||||
@@ -94,7 +85,16 @@ export const RecordChip = ({
|
||||
maxWidth={maxWidth}
|
||||
className={className}
|
||||
variant={ChipVariant.Transparent}
|
||||
leftComponent={isIconHidden ? null : avatarChip}
|
||||
leftComponent={
|
||||
isIconHidden ? null : (
|
||||
<AvatarChip
|
||||
placeholder={recordChipData.name}
|
||||
placeholderColorSeed={record.id}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -105,7 +105,16 @@ export const RecordChip = ({
|
||||
maxWidth={maxWidth}
|
||||
label={recordChipData.name}
|
||||
isLabelHidden={isLabelHidden}
|
||||
leftComponent={isIconHidden ? null : avatarChip}
|
||||
leftComponent={
|
||||
isIconHidden ? null : (
|
||||
<AvatarChip
|
||||
placeholder={recordChipData.name}
|
||||
placeholderColorSeed={record.id}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
/>
|
||||
)
|
||||
}
|
||||
className={className}
|
||||
variant={
|
||||
variant ??
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { generateDepthRecordGqlFieldsFromFields } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export type GenerateDepthRecordGqlFields = {
|
||||
objectMetadataItems: ObjectMetadataItem[];
|
||||
activityObjectNameSingular:
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task;
|
||||
depth: 0 | 1;
|
||||
shouldOnlyLoadActivityIdentifiers?: boolean;
|
||||
};
|
||||
|
||||
export const generateActivityTargetGqlFields = ({
|
||||
objectMetadataItems,
|
||||
activityObjectNameSingular,
|
||||
depth,
|
||||
shouldOnlyLoadActivityIdentifiers = true,
|
||||
}: GenerateDepthRecordGqlFields) => {
|
||||
const isNote = activityObjectNameSingular === CoreObjectNameSingular.Note;
|
||||
const activityTargetNameSingular = isNote
|
||||
? CoreObjectNameSingular.NoteTarget
|
||||
: CoreObjectNameSingular.TaskTarget;
|
||||
|
||||
const activityTargetObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === activityTargetNameSingular,
|
||||
);
|
||||
|
||||
const activityObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === activityObjectNameSingular,
|
||||
);
|
||||
|
||||
if (
|
||||
!isDefined(activityTargetObjectMetadataItem) ||
|
||||
!isDefined(activityObjectMetadataItem)
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (shouldOnlyLoadActivityIdentifiers) {
|
||||
const activityLabelIdentifierFieldMetadataItem =
|
||||
getLabelIdentifierFieldMetadataItem(activityObjectMetadataItem);
|
||||
|
||||
return {
|
||||
id: true,
|
||||
[activityObjectNameSingular]: {
|
||||
id: true,
|
||||
...(isDefined(activityLabelIdentifierFieldMetadataItem)
|
||||
? { [activityLabelIdentifierFieldMetadataItem.name]: true }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...generateDepthRecordGqlFieldsFromFields({
|
||||
depth,
|
||||
fields: activityTargetObjectMetadataItem.fields,
|
||||
objectMetadataItems,
|
||||
shouldOnlyLoadRelationIdentifiers: false,
|
||||
}),
|
||||
[activityObjectNameSingular]: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
-98
@@ -59,61 +59,12 @@ const mocks: MockedResponse[] = [
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
company {
|
||||
__typename
|
||||
domainName {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
id
|
||||
name
|
||||
}
|
||||
companyId
|
||||
createdAt
|
||||
deletedAt
|
||||
id
|
||||
note {
|
||||
__typename
|
||||
id
|
||||
title
|
||||
}
|
||||
noteId
|
||||
opportunity {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
opportunityId
|
||||
person {
|
||||
__typename
|
||||
avatarUrl
|
||||
id
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
personId
|
||||
pet {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
petId
|
||||
rocket {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
rocketId
|
||||
surveyResult {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
surveyResultId
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,61 +73,12 @@ const mocks: MockedResponse[] = [
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
company {
|
||||
__typename
|
||||
domainName {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
id
|
||||
name
|
||||
}
|
||||
companyId
|
||||
createdAt
|
||||
deletedAt
|
||||
id
|
||||
opportunity {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
opportunityId
|
||||
person {
|
||||
__typename
|
||||
avatarUrl
|
||||
id
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
personId
|
||||
pet {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
petId
|
||||
rocket {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
rocketId
|
||||
surveyResult {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
surveyResultId
|
||||
task {
|
||||
__typename
|
||||
id
|
||||
title
|
||||
}
|
||||
taskId
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-17
@@ -1,12 +1,11 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getImageIdentifierFieldMetadataItem } from '@/object-metadata/utils/getImageIdentifierFieldMetadataItem';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { hasObjectMetadataItemPositionField } from '@/object-metadata/utils/hasObjectMetadataItemPositionField';
|
||||
import { generateActivityTargetGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields';
|
||||
import { generateDepthRecordGqlFieldsFromFields } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromFields';
|
||||
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
|
||||
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
@@ -29,15 +28,6 @@ export const useRecordsFieldVisibleGqlFields = ({
|
||||
useRecordIndexContextOrThrow();
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectMetadataItem: noteTargetObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.NoteTarget,
|
||||
});
|
||||
|
||||
const { objectMetadataItem: taskTargetObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: CoreObjectNameSingular.TaskTarget,
|
||||
});
|
||||
|
||||
const allDepthOneGqlFields = generateDepthRecordGqlFieldsFromFields({
|
||||
objectMetadataItems,
|
||||
@@ -75,15 +65,17 @@ export const useRecordsFieldVisibleGqlFields = ({
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
deletedAt: true,
|
||||
noteTargets: generateDepthRecordGqlFieldsFromObject({
|
||||
objectMetadataItem: noteTargetObjectMetadataItem,
|
||||
noteTargets: generateActivityTargetGqlFields({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||
depth: 0,
|
||||
objectMetadataItems,
|
||||
depth: 1,
|
||||
shouldOnlyLoadActivityIdentifiers: true,
|
||||
}),
|
||||
taskTargets: generateDepthRecordGqlFieldsFromObject({
|
||||
taskTargets: generateActivityTargetGqlFields({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
depth: 0,
|
||||
objectMetadataItems,
|
||||
objectMetadataItem: taskTargetObjectMetadataItem,
|
||||
depth: 1,
|
||||
shouldOnlyLoadActivityIdentifiers: true,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
+2
-10
@@ -46,14 +46,6 @@ const StyledValueContainer = styled.div<{ readonly: boolean }>`
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
${({ readonly }) =>
|
||||
!readonly &&
|
||||
`
|
||||
&:hover .record-inline-cell-value-display {
|
||||
opacity: 0;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledLabelContainer = styled.div<{ width?: number }>`
|
||||
@@ -123,7 +115,7 @@ export const RecordInlineCellContainer = () => {
|
||||
<IconLabel stroke={theme.icon.stroke.sm} />
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
{showLabel && label && (
|
||||
{label && (
|
||||
<StyledLabelContainer width={labelWidth}>
|
||||
<OverflowingTextWithTooltip text={label} displayedMaxRows={1} />
|
||||
</StyledLabelContainer>
|
||||
@@ -144,7 +136,7 @@ export const RecordInlineCellContainer = () => {
|
||||
)}
|
||||
|
||||
<StyledValueContainer readonly={readonly ?? false} id={anchorId}>
|
||||
<RecordInlineCellValue className="record-inline-cell-value-display" />
|
||||
<RecordInlineCellValue />
|
||||
</StyledValueContainer>
|
||||
</StyledInlineCellBaseContainer>
|
||||
);
|
||||
|
||||
+7
-3
@@ -1,4 +1,3 @@
|
||||
import { css } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useIsFieldEmpty } from '@/object-record/record-field/ui/hooks/useIsFieldEmpty';
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
type RecordInlineCellContextProps,
|
||||
} from '@/object-record/record-inline-cell/components/RecordInlineCellContext';
|
||||
import { RecordInlineCellButton } from '@/object-record/record-inline-cell/components/RecordInlineCellEditButton';
|
||||
import { css } from '@emotion/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
const StyledRecordInlineCellNormalModeOuterContainer = styled.div<
|
||||
@@ -60,6 +60,8 @@ const StyledEmptyField = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.light};
|
||||
display: flex;
|
||||
height: 20px;
|
||||
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
`;
|
||||
|
||||
export const RecordInlineCellDisplayMode = ({
|
||||
@@ -89,6 +91,9 @@ export const RecordInlineCellDisplayMode = ({
|
||||
|
||||
const emptyPlaceHolder = showLabel ? t`Empty` : label;
|
||||
|
||||
const shouldShowEmptyPlaceholder =
|
||||
(isDisplayModeContentEmpty && !shouldDisplayEditModeOnFocus) || !children;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledRecordInlineCellNormalModeOuterContainer
|
||||
@@ -97,8 +102,7 @@ export const RecordInlineCellDisplayMode = ({
|
||||
onClick={onClick}
|
||||
>
|
||||
<StyledRecordInlineCellNormalModeInnerContainer>
|
||||
{(isDisplayModeContentEmpty && !shouldDisplayEditModeOnFocus) ||
|
||||
!children ? (
|
||||
{shouldShowEmptyPlaceholder ? (
|
||||
<StyledEmptyField>{emptyPlaceHolder}</StyledEmptyField>
|
||||
) : (
|
||||
children
|
||||
|
||||
+2
-12
@@ -28,13 +28,7 @@ const StyledClickableContainer = styled.div<{
|
||||
`};
|
||||
`;
|
||||
|
||||
type RecordInlineCellValueProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const RecordInlineCellValue = ({
|
||||
className,
|
||||
}: RecordInlineCellValueProps) => {
|
||||
export const RecordInlineCellValue = () => {
|
||||
const { readonly, loading, isCentered } = useRecordInlineCellContext();
|
||||
|
||||
if (loading === true) {
|
||||
@@ -42,11 +36,7 @@ export const RecordInlineCellValue = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledClickableContainer
|
||||
readonly={readonly}
|
||||
isCentered={isCentered}
|
||||
className={className}
|
||||
>
|
||||
<StyledClickableContainer readonly={readonly} isCentered={isCentered}>
|
||||
<RecordInlineCellDisplayMode isHovered={false}>
|
||||
<FieldDisplay />
|
||||
</RecordInlineCellDisplayMode>
|
||||
|
||||
+13
-27
@@ -1,6 +1,6 @@
|
||||
import { generateActivityTargetMorphFieldKeys } from '@/activities/utils/generateActivityTargetMorphFieldKeys';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { generateActivityTargetGqlFields } from '@/object-record/graphql/record-gql-fields/utils/generateActivityTargetGqlFields';
|
||||
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
|
||||
import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql/types/RecordGqlOperationSignatureFactory';
|
||||
|
||||
@@ -26,31 +26,17 @@ export const buildFindOneRecordForShowPageOperationSignature: RecordGqlOperation
|
||||
objectMetadataItems,
|
||||
depth: 1,
|
||||
}),
|
||||
...(objectMetadataItem.nameSingular === CoreObjectNameSingular.Task
|
||||
? {
|
||||
taskTargets: {
|
||||
id: true,
|
||||
__typename: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
note: true,
|
||||
noteId: true,
|
||||
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(objectMetadataItem.nameSingular === CoreObjectNameSingular.Note
|
||||
? {
|
||||
noteTargets: {
|
||||
id: true,
|
||||
__typename: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
task: true,
|
||||
taskId: true,
|
||||
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
noteTargets: generateActivityTargetGqlFields({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Note,
|
||||
depth: 1,
|
||||
objectMetadataItems,
|
||||
shouldOnlyLoadActivityIdentifiers: false,
|
||||
}),
|
||||
taskTargets: generateActivityTargetGqlFields({
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
depth: 1,
|
||||
objectMetadataItems,
|
||||
shouldOnlyLoadActivityIdentifiers: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user