chore: remove completed migration feature flags and upgrade commands <= 1.18 (#19074)

## Summary

- Remove 3 completed migration feature flags: `IS_ATTACHMENT_MIGRATED`,
`IS_NOTE_TARGET_MIGRATED`, `IS_TASK_TARGET_MIGRATED` — these were
already enabled by default for all new workspaces via
`DEFAULT_FEATURE_FLAGS`
- Delete all upgrade command directories for versions <= 1.18 (`1-16/`,
`1-17/`, `1-18/`) along with their module registrations, removing ~6,600
lines of dead migration code
- Simplify frontend utility functions
(`getActivityTargetObjectFieldIdName`, `getActivityTargetsFilter`,
`getActivityTargetFieldNameForObject`,
`generateActivityTargetMorphFieldKeys`,
`findActivitiesOperationSignatureFactory`) by removing the
`isMorphRelation` parameter and always using the morph relation path
- Remove feature flag checks from 7 frontend hooks/components that were
gating attachment and activity target behavior behind the removed flags
- Simplify `buildDefaultRelationFlatFieldMetadatasForCustomObject`
server util to always treat attachment, noteTarget, and taskTarget as
morph relations without checking feature flags
This commit is contained in:
Charles Bochet
2026-03-29 09:15:26 +02:00
committed by GitHub
parent f651413297
commit e8cb086b64
50 changed files with 29 additions and 6668 deletions
@@ -3,17 +3,9 @@ import { type ActivityTargetableObject } from '@/activities/types/ActivityTarget
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/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>({
@@ -4,12 +4,10 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useApolloClient, useMutation } from '@apollo/client/react';
import { t } from '@lingui/core/macro';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import {
FeatureFlagKey,
FieldMetadataType,
UploadFilesFieldFileDocument,
} from '~/generated-metadata/graphql';
@@ -19,10 +17,6 @@ export const useUploadAttachmentFile = () => {
const [uploadFilesFieldFile] = useMutation(UploadFilesFieldFileDocument, {
client: apolloClient,
});
const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);
const { objectMetadataItem: attachmentMetadata } = useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.Attachment,
});
@@ -58,7 +52,6 @@ export const useUploadAttachmentFile = () => {
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
isMorphRelation: isAttachmentMigrated,
});
const attachmentToCreate = {
@@ -6,7 +6,6 @@ import { type RecordGqlOperationSignatureFactory } from '@/object-record/graphql
type FindActivitiesOperationSignatureFactory = {
objectMetadataItems: EnrichedObjectMetadataItem[];
objectNameSingular: CoreObjectNameSingular;
isMorphRelation: boolean;
};
export const findActivitiesOperationSignatureFactory: RecordGqlOperationSignatureFactory<
@@ -14,7 +13,6 @@ export const findActivitiesOperationSignatureFactory: RecordGqlOperationSignatur
> = ({
objectMetadataItems,
objectNameSingular,
isMorphRelation,
}: FindActivitiesOperationSignatureFactory) => {
const body = {
bodyV2: {
@@ -67,10 +65,7 @@ export const findActivitiesOperationSignatureFactory: RecordGqlOperationSignatur
deletedAt: true,
note: true,
noteId: true,
...generateActivityTargetMorphFieldKeys(
objectMetadataItems,
isMorphRelation,
),
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}
: {
@@ -82,10 +77,7 @@ export const findActivitiesOperationSignatureFactory: RecordGqlOperationSignatur
deletedAt: true,
task: true,
taskId: true,
...generateActivityTargetMorphFieldKeys(
objectMetadataItems,
isMorphRelation,
),
...generateActivityTargetMorphFieldKeys(objectMetadataItems),
},
}),
},
@@ -1,5 +1,5 @@
import {
CoreObjectNameSingular,
type CoreObjectNameSingular,
type RecordGqlOperationOrderBy,
} from 'twenty-shared/types';
@@ -12,9 +12,6 @@ import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMeta
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const useActivityTargetsForTargetableObjects = ({
objectNameSingular,
@@ -37,22 +34,8 @@ export const useActivityTargetsForTargetableObjects = ({
const objectMetadataItems = useAtomStateValue<EnrichedObjectMetadataItem[]>(
objectMetadataItemsSelector,
);
const isNoteTargetMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
);
const isTaskTargetMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
);
const isMorphRelation =
objectNameSingular === CoreObjectNameSingular.Task
? isTaskTargetMigrated
: isNoteTargetMigrated;
const activityTargetsFilter = getActivityTargetsFilter({
targetableObjects: targetableObjects,
activityObjectNameSingular: objectNameSingular,
objectMetadataItems,
isMorphRelation,
targetableObjects,
});
const FIND_ACTIVITY_TARGETS_OPERATION_SIGNATURE =
@@ -7,15 +7,13 @@ import { type TaskTarget } from '@/activities/types/TaskTarget';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { type CoreObjectNameSingular } from 'twenty-shared/types';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useUpsertFindManyRecordsQueryInCache';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { sortByAscString } from '~/utils/array/sortByAscString';
export const usePrepareFindManyActivitiesQuery = ({
@@ -35,12 +33,6 @@ export const usePrepareFindManyActivitiesQuery = ({
const cache = useApolloCoreClient().cache;
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const isNoteTargetMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
);
const isTaskTargetMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
);
const { upsertFindManyRecordsQueryInCache: upsertFindManyActivitiesInCache } =
useUpsertFindManyRecordsQueryInCache({
@@ -124,10 +116,6 @@ export const usePrepareFindManyActivitiesQuery = ({
findActivitiesOperationSignatureFactory({
objectNameSingular: activityObjectNameSingular,
objectMetadataItems,
isMorphRelation:
activityObjectNameSingular === CoreObjectNameSingular.Task
? isTaskTargetMigrated
: isNoteTargetMigrated,
});
upsertFindManyActivitiesInCache({
@@ -11,12 +11,10 @@ import { searchRecordStoreFamilyState } from '@/object-record/record-picker/mult
import { type RecordPickerPickableMorphItem } from '@/object-record/record-picker/types/RecordPickerPickableMorphItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useCallback } from 'react';
import { useStore } from 'jotai';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
type UpdateActivityTargetFromCellProps = {
recordPickerInstanceId: string;
@@ -54,17 +52,6 @@ export const useUpdateActivityTargetFromCell = ({
? activityObjectNameSingular
: joinObjectNameSingular,
});
const isNoteTargetMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
);
const isTaskTargetMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
);
const isMorphRelation =
activityObjectNameSingular === CoreObjectNameSingular.Task
? isTaskTargetMigrated
: isNoteTargetMigrated;
const store = useStore();
const setRecordStore = useSetAtomFamilyState(
recordStoreFamilyState,
@@ -96,7 +83,6 @@ export const useUpdateActivityTargetFromCell = ({
activityObjectNameSingular,
targetObjectMetadataId: morphItem.objectMetadataId,
objectMetadataItems,
isMorphRelation,
});
if (!isDefined(targetFieldName)) {
@@ -211,7 +197,6 @@ export const useUpdateActivityTargetFromCell = ({
activityObjectNameSingular,
createOneActivityTarget,
deleteOneActivityTarget,
isMorphRelation,
setRecordStore,
],
);
@@ -3,7 +3,6 @@ import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/Enriche
export const generateActivityTargetMorphFieldKeys = (
objectMetadataItems: EnrichedObjectMetadataItem[],
isMorphRelation: boolean,
) => {
const targetableObjectMetadataItems = objectMetadataItems.filter(
(objectMetadataItem) =>
@@ -14,7 +13,6 @@ export const generateActivityTargetMorphFieldKeys = (
targetableObjectMetadataItems.map((objectMetadataItem) => {
const targetFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: objectMetadataItem.nameSingular,
isMorphRelation,
});
return [targetFieldIdName.replace(/Id$/, ''), true];
@@ -25,7 +23,6 @@ export const generateActivityTargetMorphFieldKeys = (
targetableObjectMetadataItems.map((objectMetadataItem) => {
const targetFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: objectMetadataItem.nameSingular,
isMorphRelation,
});
return [targetFieldIdName, true];
@@ -9,14 +9,12 @@ type GetActivityTargetFieldNameForObjectArgs = {
| CoreObjectNameSingular.Task;
targetObjectMetadataId: string;
objectMetadataItems: EnrichedObjectMetadataItem[];
isMorphRelation?: boolean;
};
export const getActivityTargetFieldNameForObject = ({
activityObjectNameSingular,
targetObjectMetadataId,
objectMetadataItems,
isMorphRelation = false,
}: GetActivityTargetFieldNameForObjectArgs): string | undefined => {
const activityTargetObjectNameSingular =
activityObjectNameSingular === CoreObjectNameSingular.Task
@@ -36,17 +34,13 @@ export const getActivityTargetFieldNameForObject = ({
(objectMetadataItem) => objectMetadataItem.id === targetObjectMetadataId,
);
if (isMorphRelation && isDefined(targetObjectMetadataItem)) {
const fieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetObjectMetadataItem.nameSingular,
isMorphRelation: true,
});
return fieldIdName.replace(/Id$/, '');
if (!isDefined(targetObjectMetadataItem)) {
return undefined;
}
return activityTargetObjectMetadata.fields.find(
(field) =>
field.relation?.targetObjectMetadata.id === targetObjectMetadataId,
)?.name;
const fieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: targetObjectMetadataItem.nameSingular,
});
return fieldIdName.replace(/Id$/, '');
};
@@ -2,14 +2,8 @@ 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`;
return `target${capitalize(nameSingular)}Id`;
};
@@ -1,58 +1,21 @@
import { type ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { isDefined } from 'twenty-shared/utils';
export const getActivityTargetsFilter = ({
targetableObjects,
activityObjectNameSingular,
objectMetadataItems,
isMorphRelation = false,
}: {
targetableObjects: ActivityTargetableObject[];
activityObjectNameSingular:
| CoreObjectNameSingular.Note
| CoreObjectNameSingular.Task;
objectMetadataItems: EnrichedObjectMetadataItem[];
isMorphRelation?: boolean;
targetableObjects: Pick<
ActivityTargetableObject,
'id' | 'targetObjectNameSingular'
>[];
}) => {
const activityTargetObjectNameSingular =
activityObjectNameSingular === CoreObjectNameSingular.Task
? CoreObjectNameSingular.TaskTarget
: CoreObjectNameSingular.NoteTarget;
const activityTargetObjectMetadata = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === activityTargetObjectNameSingular,
);
const findManyActivityTargetsQueryFilter = Object.fromEntries(
targetableObjects
.map((targetableObject) => {
const targetObjectMetadataItem = objectMetadataItems.find(
(item) =>
item.nameSingular === targetableObject.targetObjectNameSingular,
);
if (!isDefined(targetObjectMetadataItem)) {
return undefined;
}
const joinColumnName = isMorphRelation
? getActivityTargetObjectFieldIdName({
nameSingular: targetObjectMetadataItem.nameSingular,
isMorphRelation: true,
})
: activityTargetObjectMetadata?.fields.find(
(field) =>
field.relation?.targetObjectMetadata.id ===
targetObjectMetadataItem.id,
)?.settings?.joinColumnName;
if (!isDefined(joinColumnName)) {
return undefined;
}
const joinColumnName = getActivityTargetObjectFieldIdName({
nameSingular: targetableObject.targetObjectNameSingular,
});
return [
joinColumnName,
@@ -23,7 +23,6 @@ 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 { t } from '@lingui/core/macro';
import '@blocknote/core/fonts/inter.css';
import '@blocknote/mantine/style.css';
@@ -32,7 +31,6 @@ import '@blocknote/react/style.css';
import { Key } from 'ts-key-enum';
import { isDefined } from 'twenty-shared/utils';
import { useDebouncedCallback } from 'use-debounce';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
type RichTextFieldEditorProps = {
recordId: string;
@@ -82,13 +80,8 @@ export const RichTextFieldEditor = ({
const focusId = `${recordId}-${fieldName}`;
const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);
const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: objectNameSingular,
isMorphRelation: isAttachmentMigrated,
});
const { records: attachments } = useFindManyRecords<Attachment>({
@@ -11,11 +11,9 @@ import { StandaloneRichTextEditorContent } from '@/page-layout/widgets/standalon
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { styled } from '@linaria/react';
import { isDefined } from 'twenty-shared/utils';
import {
FeatureFlagKey,
PageLayoutType,
type StandaloneRichTextConfiguration,
} from '~/generated-metadata/graphql';
@@ -47,15 +45,11 @@ export const StandaloneRichTextWidget = ({
);
const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext();
const isAttachmentMigrated = useIsFeatureEnabled(
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
);
const isDashboard = layoutType === PageLayoutType.DASHBOARD;
const dashboardId = isDashboard ? targetRecordIdentifier?.id : undefined;
const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({
nameSingular: CoreObjectNameSingular.Dashboard,
isMorphRelation: isAttachmentMigrated,
});
const configuration = widget.configuration as