Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 86b24382cc |
@@ -1700,7 +1700,6 @@ export type FeatureFlag = {
|
||||
export enum FeatureFlagKey {
|
||||
IS_AI_ENABLED = 'IS_AI_ENABLED',
|
||||
IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED',
|
||||
IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
IS_CONNECTED_ACCOUNT_MIGRATED = 'IS_CONNECTED_ACCOUNT_MIGRATED',
|
||||
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
|
||||
@@ -1713,13 +1712,11 @@ export enum FeatureFlagKey {
|
||||
IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED',
|
||||
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED = 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED',
|
||||
IS_NAVIGATION_MENU_ITEM_ENABLED = 'IS_NAVIGATION_MENU_ITEM_ENABLED',
|
||||
IS_NOTE_TARGET_MIGRATED = 'IS_NOTE_TARGET_MIGRATED',
|
||||
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
|
||||
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
|
||||
IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED = 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED',
|
||||
IS_RICH_TEXT_V1_MIGRATED = 'IS_RICH_TEXT_V1_MIGRATED',
|
||||
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
|
||||
IS_TASK_TARGET_MIGRATED = 'IS_TASK_TARGET_MIGRATED',
|
||||
IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED',
|
||||
IS_USAGE_ANALYTICS_ENABLED = 'IS_USAGE_ANALYTICS_ENABLED'
|
||||
}
|
||||
|
||||
@@ -3,17 +3,11 @@ 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,
|
||||
isMorphRelation: true,
|
||||
});
|
||||
|
||||
const { records: attachments, loading } = useFindManyRecords<Attachment>({
|
||||
|
||||
+1
-7
@@ -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,7 @@ export const useUploadAttachmentFile = () => {
|
||||
|
||||
const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({
|
||||
nameSingular: targetableObject.targetObjectNameSingular,
|
||||
isMorphRelation: isAttachmentMigrated,
|
||||
isMorphRelation: true,
|
||||
});
|
||||
|
||||
const attachmentToCreate = {
|
||||
|
||||
+1
-14
@@ -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,12 @@ 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,
|
||||
isMorphRelation: true,
|
||||
});
|
||||
|
||||
const FIND_ACTIVITY_TARGETS_OPERATION_SIGNATURE =
|
||||
|
||||
+1
-12
@@ -13,9 +13,7 @@ import { useUpsertFindManyRecordsQueryInCache } from '@/object-record/cache/hook
|
||||
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,7 @@ export const usePrepareFindManyActivitiesQuery = ({
|
||||
findActivitiesOperationSignatureFactory({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
objectMetadataItems,
|
||||
isMorphRelation:
|
||||
activityObjectNameSingular === CoreObjectNameSingular.Task
|
||||
? isTaskTargetMigrated
|
||||
: isNoteTargetMigrated,
|
||||
isMorphRelation: true,
|
||||
});
|
||||
|
||||
upsertFindManyActivitiesInCache({
|
||||
|
||||
+1
-15
@@ -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,7 @@ export const useUpdateActivityTargetFromCell = ({
|
||||
activityObjectNameSingular,
|
||||
targetObjectMetadataId: morphItem.objectMetadataId,
|
||||
objectMetadataItems,
|
||||
isMorphRelation,
|
||||
isMorphRelation: true,
|
||||
});
|
||||
|
||||
if (!isDefined(targetFieldName)) {
|
||||
@@ -211,7 +198,6 @@ export const useUpdateActivityTargetFromCell = ({
|
||||
activityObjectNameSingular,
|
||||
createOneActivityTarget,
|
||||
deleteOneActivityTarget,
|
||||
isMorphRelation,
|
||||
setRecordStore,
|
||||
],
|
||||
);
|
||||
|
||||
+1
-8
@@ -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,8 +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;
|
||||
objectNameSingular: string;
|
||||
@@ -82,13 +79,9 @@ export const RichTextFieldEditor = ({
|
||||
|
||||
const focusId = `${recordId}-${fieldName}`;
|
||||
|
||||
const isAttachmentMigrated = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
|
||||
);
|
||||
|
||||
const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({
|
||||
nameSingular: objectNameSingular,
|
||||
isMorphRelation: isAttachmentMigrated,
|
||||
isMorphRelation: true,
|
||||
});
|
||||
|
||||
const { records: attachments } = useFindManyRecords<Attachment>({
|
||||
|
||||
+1
-6
@@ -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,12 @@ 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,
|
||||
isMorphRelation: true,
|
||||
});
|
||||
|
||||
const configuration = widget.configuration as
|
||||
|
||||
@@ -1544,9 +1544,6 @@ enum FeatureFlagKey {
|
||||
IS_PUBLIC_DOMAIN_ENABLED
|
||||
IS_EMAILING_DOMAIN_ENABLED
|
||||
IS_DASHBOARD_V2_ENABLED
|
||||
IS_ATTACHMENT_MIGRATED
|
||||
IS_NOTE_TARGET_MIGRATED
|
||||
IS_TASK_TARGET_MIGRATED
|
||||
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED
|
||||
IS_JUNCTION_RELATIONS_ENABLED
|
||||
IS_COMMAND_MENU_ITEM_ENABLED
|
||||
|
||||
@@ -1256,7 +1256,7 @@ export interface FeatureFlag {
|
||||
__typename: 'FeatureFlag'
|
||||
}
|
||||
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_APPLICATION_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_DASHBOARD_V2_ENABLED' | 'IS_ATTACHMENT_MIGRATED' | 'IS_NOTE_TARGET_MIGRATED' | 'IS_TASK_TARGET_MIGRATED' | 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_ENABLED' | 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED'
|
||||
export type FeatureFlagKey = 'IS_UNIQUE_INDEXES_ENABLED' | 'IS_JSON_FILTER_ENABLED' | 'IS_AI_ENABLED' | 'IS_APPLICATION_ENABLED' | 'IS_MARKETPLACE_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED' | 'IS_PUBLIC_DOMAIN_ENABLED' | 'IS_EMAILING_DOMAIN_ENABLED' | 'IS_DASHBOARD_V2_ENABLED' | 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED' | 'IS_JUNCTION_RELATIONS_ENABLED' | 'IS_COMMAND_MENU_ITEM_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_ENABLED' | 'IS_DATE_TIME_WHOLE_DAY_FILTER_ENABLED' | 'IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED' | 'IS_DRAFT_EMAIL_ENABLED' | 'IS_USAGE_ANALYTICS_ENABLED' | 'IS_RICH_TEXT_V1_MIGRATED' | 'IS_DIRECT_GRAPHQL_EXECUTION_ENABLED' | 'IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED' | 'IS_CONNECTED_ACCOUNT_MIGRATED'
|
||||
|
||||
export interface SSOIdentityProvider {
|
||||
id: Scalars['UUID']
|
||||
@@ -9067,9 +9067,6 @@ export const enumFeatureFlagKey = {
|
||||
IS_PUBLIC_DOMAIN_ENABLED: 'IS_PUBLIC_DOMAIN_ENABLED' as const,
|
||||
IS_EMAILING_DOMAIN_ENABLED: 'IS_EMAILING_DOMAIN_ENABLED' as const,
|
||||
IS_DASHBOARD_V2_ENABLED: 'IS_DASHBOARD_V2_ENABLED' as const,
|
||||
IS_ATTACHMENT_MIGRATED: 'IS_ATTACHMENT_MIGRATED' as const,
|
||||
IS_NOTE_TARGET_MIGRATED: 'IS_NOTE_TARGET_MIGRATED' as const,
|
||||
IS_TASK_TARGET_MIGRATED: 'IS_TASK_TARGET_MIGRATED' as const,
|
||||
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED' as const,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: 'IS_JUNCTION_RELATIONS_ENABLED' as const,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: 'IS_COMMAND_MENU_ITEM_ENABLED' as const,
|
||||
|
||||
+96
-114
@@ -3,7 +3,6 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
FieldMetadataType,
|
||||
type FieldMetadataSettings,
|
||||
} from 'twenty-shared/types';
|
||||
@@ -14,7 +13,6 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
@@ -39,13 +37,12 @@ type RelationFieldMetadataSettings =
|
||||
@Command({
|
||||
name: 'upgrade:1-17:migrate-attachment-to-morph-relations',
|
||||
description:
|
||||
'Migrate attachment relations to morph relation fields and set feature flag',
|
||||
'Migrate attachment relations to morph relation fields (idempotent)',
|
||||
})
|
||||
export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
@@ -61,19 +58,8 @@ export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(`Migrating attachments for workspace ${workspaceId}`);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(`Attachment migration already completed. Skipping...`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would have migrated attachments for workspace ${workspaceId}. Skipping...`,
|
||||
@@ -82,101 +68,107 @@ export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'attachment';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const attachmentTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.attachment.fields.targetTask.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetNote.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetOpportunity.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetDashboard.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetWorkflow.universalIdentifier,
|
||||
]);
|
||||
|
||||
const attachmentObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.attachment.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!attachmentObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 Attachment object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const attachmentFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
attachmentObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const attachmentTargetRelationFields = attachmentFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter(
|
||||
(field): field is FlatFieldMetadata<FieldMetadataType.RELATION> =>
|
||||
field.type === FieldMetadataType.RELATION,
|
||||
)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
attachmentTargetFieldUniversalIdentifiers.has(
|
||||
field.universalIdentifier,
|
||||
);
|
||||
const targetObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = attachmentTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
if (fieldMigrations.length === 0) {
|
||||
this.logger.log(
|
||||
`Attachment morph migration already applied or nothing to migrate. Skipping workspace ${workspaceId}...`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'attachment';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const attachmentTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.attachment.fields.targetTask.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetNote.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetOpportunity
|
||||
.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetDashboard.universalIdentifier,
|
||||
STANDARD_OBJECTS.attachment.fields.targetWorkflow.universalIdentifier,
|
||||
]);
|
||||
|
||||
const attachmentObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.attachment.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!attachmentObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 Attachment object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const attachmentFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
attachmentObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const attachmentTargetRelationFields = attachmentFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter(
|
||||
(field): field is FlatFieldMetadata<FieldMetadataType.RELATION> =>
|
||||
field.type === FieldMetadataType.RELATION,
|
||||
)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
attachmentTargetFieldUniversalIdentifiers.has(
|
||||
field.universalIdentifier,
|
||||
);
|
||||
const targetObjectMetadata =
|
||||
findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = attachmentTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
for (const { oldJoinColumnName, newJoinColumnName } of fieldMigrations) {
|
||||
if (oldJoinColumnName === newJoinColumnName) {
|
||||
this.logger.log(
|
||||
@@ -253,11 +245,6 @@ export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_ATTACHMENT_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const relatedMetadataNames =
|
||||
getMetadataRelatedMetadataNames('fieldMetadata');
|
||||
const relatedCacheKeysToInvalidate: WorkspaceCacheKeyName[] =
|
||||
@@ -265,7 +252,6 @@ export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
const cacheKeysToInvalidate: WorkspaceCacheKeyName[] = [
|
||||
'flatFieldMetadataMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
'featureFlagsMap',
|
||||
];
|
||||
|
||||
this.logger.log(
|
||||
@@ -282,10 +268,6 @@ export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
);
|
||||
this.logger.log(`Cache flushed`);
|
||||
|
||||
this.logger.log(
|
||||
`Set IS_ATTACHMENT_MIGRATED feature flag for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
this.logger.log(`Flush cache for workspace ${workspaceId}`);
|
||||
await this.workspaceCacheStorageService.flush(workspaceId);
|
||||
} catch (error) {
|
||||
|
||||
+91
-108
@@ -3,7 +3,6 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
FieldMetadataType,
|
||||
type FieldMetadataSettings,
|
||||
} from 'twenty-shared/types';
|
||||
@@ -14,7 +13,6 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
@@ -38,13 +36,12 @@ type RelationFieldMetadataSettings =
|
||||
@Command({
|
||||
name: 'upgrade:1-17:migrate-note-target-to-morph-relations',
|
||||
description:
|
||||
'Migrate noteTarget relations to morph relation fields and set feature flag',
|
||||
'Migrate noteTarget relations to morph relation fields (idempotent)',
|
||||
})
|
||||
export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
@@ -60,19 +57,8 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(`Migrating noteTarget for workspace ${workspaceId}`);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(`NoteTarget migration already completed. Skipping...`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would have migrated noteTarget for workspace ${workspaceId}. Skipping...`,
|
||||
@@ -81,95 +67,102 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'noteTarget';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const noteTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetOpportunity.universalIdentifier,
|
||||
]);
|
||||
|
||||
const noteTargetObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.noteTarget.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!noteTargetObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 ${tableName} object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const noteTargetFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
noteTargetObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const noteTargetRelationFields = noteTargetFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
noteTargetFieldUniversalIdentifiers.has(field.universalIdentifier);
|
||||
const targetObjectMetadata = field.relationTargetObjectMetadataId
|
||||
? findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
})
|
||||
: undefined;
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = noteTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const newFieldLabel = 'Target';
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
if (fieldMigrations.length === 0) {
|
||||
this.logger.log(
|
||||
`NoteTarget morph migration already applied or nothing to migrate. Skipping workspace ${workspaceId}...`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'noteTarget';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const noteTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.noteTarget.fields.targetOpportunity
|
||||
.universalIdentifier,
|
||||
]);
|
||||
|
||||
const noteTargetObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.noteTarget.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!noteTargetObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 ${tableName} object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const noteTargetFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
noteTargetObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const noteTargetRelationFields = noteTargetFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
noteTargetFieldUniversalIdentifiers.has(field.universalIdentifier);
|
||||
const targetObjectMetadata = field.relationTargetObjectMetadataId
|
||||
? findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
})
|
||||
: undefined;
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = noteTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const newFieldLabel = 'Target';
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
// Rename columns
|
||||
for (const { oldJoinColumnName, newJoinColumnName } of fieldMigrations) {
|
||||
if (oldJoinColumnName === newJoinColumnName) {
|
||||
@@ -250,11 +243,6 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_NOTE_TARGET_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const relatedMetadataNames =
|
||||
getMetadataRelatedMetadataNames('fieldMetadata');
|
||||
const relatedCacheKeysToInvalidate: WorkspaceCacheKeyName[] =
|
||||
@@ -262,7 +250,6 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
const cacheKeysToInvalidate: WorkspaceCacheKeyName[] = [
|
||||
'flatFieldMetadataMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
'featureFlagsMap',
|
||||
];
|
||||
|
||||
this.logger.log(
|
||||
@@ -279,10 +266,6 @@ export class MigrateNoteTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
);
|
||||
this.logger.log(`Cache flushed`);
|
||||
|
||||
this.logger.log(
|
||||
`Set IS_NOTE_TARGET_MIGRATED feature flag for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
this.logger.log(`Flush cache for workspace ${workspaceId}`);
|
||||
await this.workspaceCacheStorageService.flush(workspaceId);
|
||||
} catch (error) {
|
||||
|
||||
+91
-108
@@ -3,7 +3,6 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
FieldMetadataType,
|
||||
type FieldMetadataSettings,
|
||||
} from 'twenty-shared/types';
|
||||
@@ -14,7 +13,6 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util';
|
||||
import { type FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util';
|
||||
@@ -38,13 +36,12 @@ type RelationFieldMetadataSettings =
|
||||
@Command({
|
||||
name: 'upgrade:1-17:migrate-task-target-to-morph-relations',
|
||||
description:
|
||||
'Migrate taskTarget relations to morph relation fields and set feature flag',
|
||||
'Migrate taskTarget relations to morph relation fields (idempotent)',
|
||||
})
|
||||
export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
@@ -60,19 +57,8 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(`Migrating taskTarget for workspace ${workspaceId}`);
|
||||
|
||||
if (isMigrated) {
|
||||
this.logger.log(`TaskTarget migration already completed. Skipping...`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`Would have migrated taskTarget for workspace ${workspaceId}. Skipping...`,
|
||||
@@ -81,95 +67,102 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
return;
|
||||
}
|
||||
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'taskTarget';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const taskTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetOpportunity.universalIdentifier,
|
||||
]);
|
||||
|
||||
const taskTargetObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.taskTarget.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!taskTargetObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 ${tableName} object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const taskTargetFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
taskTargetObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const taskTargetRelationFields = taskTargetFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
taskTargetFieldUniversalIdentifiers.has(field.universalIdentifier);
|
||||
const targetObjectMetadata = field.relationTargetObjectMetadataId
|
||||
? findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
})
|
||||
: undefined;
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = taskTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const newFieldLabel = 'Target';
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
if (fieldMigrations.length === 0) {
|
||||
this.logger.log(
|
||||
`TaskTarget morph migration already applied or nothing to migrate. Skipping workspace ${workspaceId}...`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const schemaName = getWorkspaceSchemaName(workspaceId);
|
||||
const tableName = 'taskTarget';
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatApplicationMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatApplicationMaps',
|
||||
]);
|
||||
|
||||
const taskTargetFieldUniversalIdentifiers = new Set<string>([
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetPerson.universalIdentifier,
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetCompany.universalIdentifier,
|
||||
STANDARD_OBJECTS.taskTarget.fields.targetOpportunity
|
||||
.universalIdentifier,
|
||||
]);
|
||||
|
||||
const taskTargetObjectMetadata =
|
||||
findFlatEntityByUniversalIdentifier<FlatObjectMetadata>({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.taskTarget.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!taskTargetObjectMetadata) {
|
||||
this.logger.error(
|
||||
`🟥 ${tableName} object metadata not found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const taskTargetFieldMetadatas = getFlatFieldsFromFlatObjectMetadata(
|
||||
taskTargetObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const taskTargetRelationFields = taskTargetFieldMetadatas
|
||||
.filter(isMorphOrRelationFlatFieldMetadata)
|
||||
.filter((field) => field.type === FieldMetadataType.RELATION)
|
||||
.filter((field) => {
|
||||
const isStandardAppField = this.isTwentyStandardApplicationField({
|
||||
field,
|
||||
flatApplicationMaps,
|
||||
workspaceId,
|
||||
});
|
||||
const isStandardTarget =
|
||||
isStandardAppField &&
|
||||
taskTargetFieldUniversalIdentifiers.has(field.universalIdentifier);
|
||||
const targetObjectMetadata = field.relationTargetObjectMetadataId
|
||||
? findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: field.relationTargetObjectMetadataId,
|
||||
})
|
||||
: undefined;
|
||||
const isCustomTarget =
|
||||
!isStandardAppField && targetObjectMetadata?.isCustom === true;
|
||||
|
||||
return isStandardTarget || isCustomTarget;
|
||||
});
|
||||
|
||||
const fieldMigrations = taskTargetRelationFields.map((field) => {
|
||||
const newFieldName = `target${capitalize(field.name)}`;
|
||||
const newFieldLabel = 'Target';
|
||||
const relationSettings: RelationFieldMetadataSettings = field.settings;
|
||||
const oldJoinColumnName =
|
||||
relationSettings?.joinColumnName ??
|
||||
computeMorphOrRelationFieldJoinColumnName({ name: field.name });
|
||||
const newJoinColumnName = computeMorphOrRelationFieldJoinColumnName({
|
||||
name: newFieldName,
|
||||
});
|
||||
|
||||
return {
|
||||
field,
|
||||
newFieldName,
|
||||
newFieldLabel,
|
||||
oldJoinColumnName,
|
||||
newJoinColumnName,
|
||||
};
|
||||
});
|
||||
|
||||
// Rename columns
|
||||
for (const { oldJoinColumnName, newJoinColumnName } of fieldMigrations) {
|
||||
if (oldJoinColumnName === newJoinColumnName) {
|
||||
@@ -250,11 +243,6 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_TASK_TARGET_MIGRATED],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const relatedMetadataNames =
|
||||
getMetadataRelatedMetadataNames('fieldMetadata');
|
||||
const relatedCacheKeysToInvalidate: WorkspaceCacheKeyName[] =
|
||||
@@ -262,7 +250,6 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
const cacheKeysToInvalidate: WorkspaceCacheKeyName[] = [
|
||||
'flatFieldMetadataMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
'featureFlagsMap',
|
||||
];
|
||||
|
||||
this.logger.log(
|
||||
@@ -279,10 +266,6 @@ export class MigrateTaskTargetToMorphRelationsCommand extends ActiveOrSuspendedW
|
||||
);
|
||||
this.logger.log(`Cache flushed`);
|
||||
|
||||
this.logger.log(
|
||||
`Set IS_TASK_TARGET_MIGRATED feature flag for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
this.logger.log(`Flush cache for workspace ${workspaceId}`);
|
||||
await this.workspaceCacheStorageService.flush(workspaceId);
|
||||
} catch (error) {
|
||||
|
||||
-5
@@ -4,8 +4,6 @@ import {
|
||||
} from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
|
||||
import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
@@ -20,14 +18,12 @@ type FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs
|
||||
{
|
||||
createObjectInput: CreateObjectInput;
|
||||
flatApplication: FlatApplication;
|
||||
existingFeatureFlagsMap: FeatureFlagMap;
|
||||
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
|
||||
export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate =
|
||||
({
|
||||
createObjectInput: rawCreateObjectInput,
|
||||
flatApplication,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
existingFeatureFlagsMap,
|
||||
}: FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs): {
|
||||
flatObjectMetadataToCreate: UniversalFlatObjectMetadata & { id: string };
|
||||
relationTargetFlatFieldMetadataToCreate: UniversalFlatFieldMetadata[];
|
||||
@@ -108,7 +104,6 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
|
||||
existingFlatObjectMetadataMaps,
|
||||
sourceFlatObjectMetadata: universalFlatObjectMetadataToCreate,
|
||||
flatApplication,
|
||||
existingFeatureFlagsMap,
|
||||
});
|
||||
|
||||
const objectFlatFieldMetadatas: UniversalFlatFieldMetadata[] = [
|
||||
|
||||
-1
@@ -406,7 +406,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
createObjectInput,
|
||||
flatApplication: resolvedOwnerFlatApplication,
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
existingFeatureFlagsMap,
|
||||
});
|
||||
|
||||
const flatDefaultViewToCreate = this.computeFlatViewToCreate({
|
||||
|
||||
+14
-18
@@ -2,10 +2,8 @@ import {
|
||||
STANDARD_OBJECTS,
|
||||
DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS,
|
||||
} from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
@@ -34,14 +32,18 @@ const morphIdByRelationObjectNameSingular = {
|
||||
string | null
|
||||
>;
|
||||
|
||||
// TODO: once we have finished migrating, we can delete custom code
|
||||
// once we migrate timeline activity to morph relations, we can add it.
|
||||
// another way to check if an object is migrated to morph relations is to check if the feature flag is enabled
|
||||
const DEFAULT_MORPH_RELATIONS_OBJECTS_STANDARD_IDS =
|
||||
[] as const satisfies (keyof typeof STANDARD_OBJECTS)[];
|
||||
|
||||
const STANDARD_OBJECT_NAME_SINGULARS_USING_MORPH_ON_CUSTOM_TARGETS =
|
||||
new Set<string>([
|
||||
'timelineActivity',
|
||||
'attachment',
|
||||
'noteTarget',
|
||||
'taskTarget',
|
||||
]);
|
||||
|
||||
export type BuildDefaultRelationFieldsForCustomObjectArgs = {
|
||||
existingFeatureFlagsMap: FeatureFlagMap;
|
||||
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
sourceFlatObjectMetadata: UniversalFlatObjectMetadata;
|
||||
flatApplication: FlatApplication;
|
||||
@@ -58,7 +60,6 @@ const EMPTY_SOURCE_AND_TARGET_FLAT_FIELD_METADATAS_RECORD: SourceAndTargetFlatFi
|
||||
};
|
||||
|
||||
export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({
|
||||
existingFeatureFlagsMap,
|
||||
existingFlatObjectMetadataMaps,
|
||||
sourceFlatObjectMetadata,
|
||||
flatApplication,
|
||||
@@ -83,17 +84,12 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({
|
||||
DEFAULT_MORPH_RELATIONS_OBJECTS_STANDARD_IDS.map(toString).includes(
|
||||
objectMetadataNameSingular,
|
||||
);
|
||||
const isFeatureFlagEnabled =
|
||||
objectMetadataNameSingular === 'timelineActivity' ||
|
||||
(objectMetadataNameSingular === 'attachment' &&
|
||||
existingFeatureFlagsMap[FeatureFlagKey.IS_ATTACHMENT_MIGRATED]) ||
|
||||
(objectMetadataNameSingular === 'noteTarget' &&
|
||||
existingFeatureFlagsMap[FeatureFlagKey.IS_NOTE_TARGET_MIGRATED]) ||
|
||||
(objectMetadataNameSingular === 'taskTarget' &&
|
||||
existingFeatureFlagsMap[FeatureFlagKey.IS_TASK_TARGET_MIGRATED]) ||
|
||||
false;
|
||||
const usesMorphRelationsForStandardTarget =
|
||||
STANDARD_OBJECT_NAME_SINGULARS_USING_MORPH_ON_CUSTOM_TARGETS.has(
|
||||
objectMetadataNameSingular,
|
||||
);
|
||||
const isObjectMigratedToMorphRelations =
|
||||
isObjectMigratedFromOlderReleases || isFeatureFlagEnabled;
|
||||
isObjectMigratedFromOlderReleases || usesMorphRelationsForStandardTarget;
|
||||
|
||||
const targetFlatObjectMetadataId =
|
||||
objectIdByNameSingular[objectMetadataNameSingular];
|
||||
|
||||
-3
@@ -235,9 +235,6 @@ describe('WorkspaceEntityManager', () => {
|
||||
IS_PUBLIC_DOMAIN_ENABLED: false,
|
||||
IS_EMAILING_DOMAIN_ENABLED: false,
|
||||
IS_DASHBOARD_V2_ENABLED: false,
|
||||
IS_ATTACHMENT_MIGRATED: false,
|
||||
IS_NOTE_TARGET_MIGRATED: false,
|
||||
IS_TASK_TARGET_MIGRATED: false,
|
||||
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false,
|
||||
IS_JUNCTION_RELATIONS_ENABLED: false,
|
||||
IS_COMMAND_MENU_ITEM_ENABLED: false,
|
||||
|
||||
-15
@@ -50,21 +50,6 @@ export const seedFeatureFlags = async ({
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
|
||||
workspaceId: workspaceId,
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
key: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED,
|
||||
workspaceId: workspaceId,
|
||||
|
||||
-3
@@ -1,8 +1,5 @@
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
|
||||
export const DEFAULT_FEATURE_FLAGS = [
|
||||
FeatureFlagKey.IS_ATTACHMENT_MIGRATED,
|
||||
FeatureFlagKey.IS_NOTE_TARGET_MIGRATED,
|
||||
FeatureFlagKey.IS_TASK_TARGET_MIGRATED,
|
||||
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
|
||||
] as const satisfies FeatureFlagKey[];
|
||||
|
||||
@@ -8,9 +8,6 @@ export enum FeatureFlagKey {
|
||||
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
|
||||
IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED',
|
||||
IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED',
|
||||
IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED',
|
||||
IS_NOTE_TARGET_MIGRATED = 'IS_NOTE_TARGET_MIGRATED',
|
||||
IS_TASK_TARGET_MIGRATED = 'IS_TASK_TARGET_MIGRATED',
|
||||
IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED',
|
||||
IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED',
|
||||
IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED',
|
||||
|
||||
Reference in New Issue
Block a user