Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/twenty-front/src/generated-metadata/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,7 @@ export enum FeatureFlagKey {
IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED',
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED',
IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED',
IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED'
}
Expand Down
1 change: 1 addition & 0 deletions packages/twenty-front/src/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,7 @@ export enum FeatureFlagKey {
IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED',
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED',
IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED',
IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED',
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { v4 } from 'uuid';
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
Expand Down Expand Up @@ -35,11 +36,13 @@ import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePush
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import '@blocknote/core/fonts/inter.css';
import '@blocknote/mantine/style.css';
import { useCreateBlockNote } from '@blocknote/react';
import '@blocknote/react/style.css';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated/graphql';

type ActivityRichTextEditorProps = {
activityId: string;
Expand Down Expand Up @@ -70,21 +73,21 @@ export const ActivityRichTextEditor = ({
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();

const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);

const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: activityObjectNameSingular,
isMorphRelation: isAttachmentMigrated,
});

const { records: attachments } = useFindManyRecords<Attachment>({
objectNameSingular: CoreObjectNameSingular.Attachment,
filter: {
or: [
{
noteId: {
eq: activityId,
},
},
{
taskId: {
eq: activityId,
},
},
],
[attachmentTargetFieldIdName]: {
eq: activityId,
},
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { useAttachments } from '@/activities/files/hooks/useAttachments';
jest.mock('@/object-record/hooks/useFindManyRecords', () => ({
useFindManyRecords: jest.fn(),
}));
jest.mock('@/workspace/hooks/useIsFeatureEnabled', () => ({
useIsFeatureEnabled: jest.fn(),
}));

describe('useAttachments', () => {
afterEach(() => {
Expand All @@ -24,9 +27,13 @@ describe('useAttachments', () => {
const useFindManyRecordsMock = jest.requireMock(
'@/object-record/hooks/useFindManyRecords',
);
const useIsFeatureEnabledMock = jest.requireMock(
'@/workspace/hooks/useIsFeatureEnabled',
);
useFindManyRecordsMock.useFindManyRecords.mockReturnValue({
records: mockAttachments,
});
useIsFeatureEnabledMock.useIsFeatureEnabled.mockReturnValue(false);

const { result } = renderHook(() => useAttachments(mockTargetableObject));

Expand All @@ -42,7 +49,11 @@ describe('useAttachments', () => {
const useFindManyRecordsMock = jest.requireMock(
'@/object-record/hooks/useFindManyRecords',
);
const useIsFeatureEnabledMock = jest.requireMock(
'@/workspace/hooks/useIsFeatureEnabled',
);
useFindManyRecordsMock.useFindManyRecords.mockReturnValue({ records: [] });
useIsFeatureEnabledMock.useIsFeatureEnabled.mockReturnValue(false);

const { result } = renderHook(() => useAttachments(mockTargetableObject));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ import { type ActivityTargetableObject } from '@/activities/types/ActivityTarget
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated/graphql';

export const useAttachments = (targetableObject: ActivityTargetableObject) => {
const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);

const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
isMorphRelation: isAttachmentMigrated,
});

const { records: attachments, loading } = useFindManyRecords<Attachment>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared/utils';
import {
FileFolder,
useUploadFileMutation,
} from '~/generated-metadata/graphql';
import { FeatureFlagKey } from '~/generated/graphql';

export const useUploadAttachmentFile = () => {
const coreClient = useApolloCoreClient();
const [uploadFile] = useUploadFileMutation({ client: coreClient });
const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);

const { createOneRecord: createOneAttachment } =
useCreateOneRecord<Attachment>({
Expand Down Expand Up @@ -42,6 +47,7 @@ export const useUploadAttachmentFile = () => {

const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
isMorphRelation: isAttachmentMigrated,
});

const attachmentToCreate = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,20 @@ export type Attachment = {
name: string;
fullPath: string;
fileCategory: AttachmentFileCategory;
companyId: string;
personId: string;
companyId?: string | null;
personId?: string | null;
taskId?: string | null;
noteId?: string | null;
opportunityId?: string | null;
dashboardId?: string | null;
workflowId?: string | null;
targetCompanyId?: string | null;
targetPersonId?: string | null;
targetTaskId?: string | null;
targetNoteId?: string | null;
targetOpportunityId?: string | null;
targetDashboardId?: string | null;
targetWorkflowId?: string | null;
createdBy?: {
source: string;
workspaceMemberId: string | null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { capitalize } from 'twenty-shared/utils';

export const getActivityTargetObjectFieldIdName = ({
nameSingular,
isMorphRelation = false,
}: {
nameSingular: string;
isMorphRelation?: boolean;
}) => {
if (isMorphRelation) {
return `target${capitalize(nameSingular)}Id`;
}

return `${nameSingular}Id`;
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
type UUIDFilter,
} from 'twenty-shared/types';
import {
computeMorphRelationFieldName,
isDefined,
isEmptyObject,
isMatchingArrayFilter,
Expand All @@ -45,6 +46,7 @@ import {
isMatchingTSVectorFilter,
isMatchingUUIDFilter,
} from 'twenty-shared/utils';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';

const isLeafFilter = (
filter: RecordGqlOperationFilter,
Expand All @@ -59,6 +61,33 @@ const isAndFilter = (
const isImplicitAndFilter = (filter: RecordGqlOperationFilter) =>
Object.keys(filter).length > 1;

const doesMorphRelationJoinColumnMatch = ({
fieldMetadataItem,
filterKey,
}: {
fieldMetadataItem: FieldMetadataItem;
filterKey: string;
}): boolean => {
if (!isDefined(fieldMetadataItem.settings?.relationType)) {
return false;
}

const morphRelations = fieldMetadataItem.morphRelations ?? [];

return morphRelations.some((morphRelation) => {
const relationFieldName = computeMorphRelationFieldName({
fieldName: fieldMetadataItem.name,
relationType: fieldMetadataItem.settings?.relationType,
targetObjectMetadataNameSingular:
morphRelation.targetObjectMetadata.nameSingular,
targetObjectMetadataNamePlural:
morphRelation.targetObjectMetadata.namePlural,
});

return `${relationFieldName}Id` === filterKey;
});
};

const isOrFilter = (
filter: RecordGqlOperationFilter,
): filter is OrObjectRecordFilter => 'or' in filter && !!filter.or;
Expand Down Expand Up @@ -175,8 +204,17 @@ export const isRecordMatchingFilter = ({
objectMetadataItem.fields.find((field) => field.name === filterKey) ??
objectMetadataItem.fields.find(
(field) =>
field.type === FieldMetadataType.RELATION &&
(field.type === FieldMetadataType.RELATION ||
field.type === FieldMetadataType.MORPH_RELATION) &&
field.settings?.joinColumnName === filterKey,
) ??
objectMetadataItem.fields.find(
(field) =>
field.type === FieldMetadataType.MORPH_RELATION &&
doesMorphRelationJoinColumnMatch({
fieldMetadataItem: field,
filterKey,
}),
);

if (!isDefined(objectMetadataField)) {
Expand Down Expand Up @@ -366,9 +404,15 @@ export const isRecordMatchingFilter = ({
});
});
}
case FieldMetadataType.RELATION: {
case FieldMetadataType.RELATION:
case FieldMetadataType.MORPH_RELATION: {
const isJoinColumn =
objectMetadataField.settings?.joinColumnName === filterKey;
objectMetadataField.settings?.joinColumnName === filterKey ||
(objectMetadataField.type === FieldMetadataType.MORPH_RELATION &&
doesMorphRelationJoinColumnMatch({
fieldMetadataItem: objectMetadataField,
filterKey,
}));

if (isJoinColumn) {
return isMatchingUUIDFilter({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useCallback, useMemo, useRef } from 'react';
import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
import { type Attachment } from '@/activities/files/types/Attachment';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget';
Expand All @@ -20,6 +21,7 @@ import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import '@blocknote/core/fonts/inter.css';
import '@blocknote/mantine/style.css';
import { useCreateBlockNote } from '@blocknote/react';
Expand All @@ -28,6 +30,7 @@ import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared/utils';
import { useDebouncedCallback } from 'use-debounce';
import {
FeatureFlagKey,
PageLayoutType,
WidgetConfigurationType,
type StandaloneRichTextConfiguration,
Expand Down Expand Up @@ -63,13 +66,20 @@ export const StandaloneRichTextWidget = ({
const { updatePageLayoutWidget } = useUpdatePageLayoutWidget();
const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext();
const { uploadAttachmentFile } = useUploadAttachmentFile();
const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);

const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();

const isDashboard = layoutType === PageLayoutType.DASHBOARD;
const dashboardId = isDashboard ? targetRecordIdentifier?.id : undefined;
const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: CoreObjectNameSingular.Dashboard,
isMorphRelation: isAttachmentMigrated,
});

const configuration = widget.configuration as
| StandaloneRichTextConfiguration
Expand All @@ -80,7 +90,7 @@ export const StandaloneRichTextWidget = ({
const { records: attachments } = useFindManyRecords<Attachment>({
objectNameSingular: CoreObjectNameSingular.Attachment,
filter: isDefined(dashboardId)
? { dashboardId: { eq: dashboardId } }
? { [attachmentTargetFieldIdName]: { eq: dashboardId } }
: undefined,
skip: !isDefined(dashboardId),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ export const STANDARD_INDEX_FIELD_UNIVERSAL_IDENTIFIERS: Record<
Record<string, string[]>
> = {
attachment: {
taskIdIndex: [STANDARD_OBJECTS.attachment.fields.task.universalIdentifier],
noteIdIndex: [STANDARD_OBJECTS.attachment.fields.note.universalIdentifier],
taskIdIndex: [
STANDARD_OBJECTS.attachment.fields.targetTask.universalIdentifier,
],
noteIdIndex: [
STANDARD_OBJECTS.attachment.fields.targetNote.universalIdentifier,
],
personIdIndex: [
STANDARD_OBJECTS.attachment.fields.person.universalIdentifier,
STANDARD_OBJECTS.attachment.fields.targetPerson.universalIdentifier,
],
companyIdIndex: [
STANDARD_OBJECTS.attachment.fields.company.universalIdentifier,
STANDARD_OBJECTS.attachment.fields.targetCompany.universalIdentifier,
],
opportunityIdIndex: [
STANDARD_OBJECTS.attachment.fields.opportunity.universalIdentifier,
STANDARD_OBJECTS.attachment.fields.targetOpportunity.universalIdentifier,
],
dashboardIdIndex: [
STANDARD_OBJECTS.attachment.fields.dashboard.universalIdentifier,
STANDARD_OBJECTS.attachment.fields.targetDashboard.universalIdentifier,
],
workflowIdIndex: [
STANDARD_OBJECTS.attachment.fields.workflow.universalIdentifier,
STANDARD_OBJECTS.attachment.fields.targetWorkflow.universalIdentifier,
],
},
blocklist: {
Expand Down
Loading
Loading