feat: populate relation join column (#10212)

Fix
https://github.com/twentyhq/core-team-issues/issues/241#issue-2793030259
This commit is contained in:
Jérémy M
2025-02-25 11:24:05 +01:00
committed by GitHub
parent dde70ee3b0
commit a1eea40cf7
49 changed files with 677 additions and 496 deletions

View File

@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { FieldMetadataType } from 'twenty-shared';
import {
DataSource,
FindOptionsRelations,
@ -22,7 +23,7 @@ import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/typ
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.util';
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
@Injectable()
export class ProcessNestedRelationsV2Helper {
@ -96,7 +97,9 @@ export class ProcessNestedRelationsV2Helper {
const sourceFieldMetadata =
parentObjectMetadataItem.fieldsByName[sourceFieldName];
if (!isRelationFieldMetadata(sourceFieldMetadata)) {
if (
!isFieldMetadataOfType(sourceFieldMetadata, FieldMetadataType.RELATION)
) {
// TODO: Maybe we should throw an error here ?
return;
}

View File

@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import { isDefined } from 'twenty-shared';
import { FieldMetadataType, isDefined } from 'twenty-shared';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value';
@ -21,7 +21,7 @@ import { CompositeInputTypeDefinitionFactory } from 'src/engine/api/graphql/work
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.util';
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
// TODO: find a way to prevent conflict between handlers executing logic on object relations
// And this factory that is also executing logic on object relations
@ -151,7 +151,9 @@ export class QueryResultGettersFactory {
objectMetadataMapItem.fieldsByName[recordFieldName],
)
.filter(isDefined)
.filter((fieldMetadata) => isRelationFieldMetadata(fieldMetadata));
.filter((fieldMetadata) =>
isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION),
);
const relationFieldsProcessedMap = {} as Record<
string,

View File

@ -5,6 +5,7 @@ import {
GraphQLFieldConfigMap,
GraphQLObjectType,
} from 'graphql';
import { FieldMetadataType } from 'twenty-shared';
import { WorkspaceBuildSchemaOptions } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-build-schema-optionts.interface';
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
@ -14,7 +15,7 @@ import { RelationTypeV2Factory } from 'src/engine/api/graphql/workspace-schema-b
import { TypeDefinitionsStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/type-definitions.storage';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
import { objectContainsRelationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/object-contains-relation-field';
import { isRelationFieldMetadata } from 'src/engine/utils/is-relation-field-metadata.util';
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
import { ArgsFactory } from './args.factory';
@ -107,7 +108,7 @@ export class ExtendObjectTypeDefinitionV2Factory {
for (const fieldMetadata of objectMetadata.fields) {
// Ignore non-relation fields as they are already defined
if (!isRelationFieldMetadata(fieldMetadata)) {
if (!isFieldMetadataOfType(fieldMetadata, FieldMetadataType.RELATION)) {
continue;
}

View File

@ -45,7 +45,7 @@ export interface TypeOptions<T = any> {
isArray?: boolean;
arrayDepth?: number;
defaultValue?: T;
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>;
settings?: FieldMetadataSettings<FieldMetadataType>;
isIdField?: boolean;
}
@ -55,7 +55,7 @@ const StringArrayScalarType = new GraphQLList(GraphQLString);
export class TypeMapperService {
mapToScalarType(
fieldMetadataType: FieldMetadataType,
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>,
settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean,
): GraphQLScalarType | undefined {
if (isIdField || settings?.isForeignKey) {
@ -90,7 +90,7 @@ export class TypeMapperService {
mapToFilterType(
fieldMetadataType: FieldMetadataType,
settings?: FieldMetadataSettings<FieldMetadataType | 'default'>,
settings?: FieldMetadataSettings<FieldMetadataType>,
isIdField?: boolean,
): GraphQLInputObjectType | GraphQLScalarType | undefined {
if (isIdField || settings?.isForeignKey) {

View File

@ -7,22 +7,22 @@ import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import {
ActiveWorkspacesCommandOptions,
ActiveWorkspacesCommandRunner,
} from 'src/database/commands/active-workspaces.command';
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { BillingCustomer } from 'src/engine/core-modules/billing/entities/billing-customer.entity';
import { StripeSubscriptionService } from 'src/engine/core-modules/billing/stripe/services/stripe-subscription.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
interface SyncCustomerDataCommandOptions
extends ActiveWorkspacesCommandOptions {}
extends ActiveWorkspacesMigrationCommandOptions {}
@Command({
name: 'billing:sync-customer-data',
description: 'Sync customer data from Stripe for all active workspaces',
})
export class BillingSyncCustomerDataCommand extends ActiveWorkspacesCommandRunner {
export class BillingSyncCustomerDataCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@ -34,7 +34,7 @@ export class BillingSyncCustomerDataCommand extends ActiveWorkspacesCommandRunne
super(workspaceRepository, twentyORMGlobalManager);
}
async executeActiveWorkspacesCommand(
async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[],
options: SyncCustomerDataCommandOptions,
workspaceIds: string[],

View File

@ -7,9 +7,9 @@ import Stripe from 'stripe';
import { Repository } from 'typeorm';
import {
BaseCommandOptions,
BaseCommandRunner,
} from 'src/database/commands/base.command';
MigrationCommandOptions,
MigrationCommandRunner,
} from 'src/database/commands/migration-command/migration-command.runner';
import { BillingMeter } from 'src/engine/core-modules/billing/entities/billing-meter.entity';
import { BillingPrice } from 'src/engine/core-modules/billing/entities/billing-price.entity';
import { BillingProduct } from 'src/engine/core-modules/billing/entities/billing-product.entity';
@ -25,7 +25,7 @@ import { transformStripeProductToDatabaseProduct } from 'src/engine/core-modules
description:
'Fetches from stripe the plans data (meter, product and price) and upserts it into the database',
})
export class BillingSyncPlansDataCommand extends BaseCommandRunner {
export class BillingSyncPlansDataCommand extends MigrationCommandRunner {
private readonly batchSize = 5;
constructor(
@InjectRepository(BillingPrice, 'core')
@ -43,7 +43,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async upsertMetersRepositoryData(
meters: Stripe.Billing.Meter[],
options: BaseCommandOptions,
options: MigrationCommandOptions,
) {
meters.map(async (meter) => {
try {
@ -64,7 +64,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async upsertProductRepositoryData(
product: Stripe.Product,
options: BaseCommandOptions,
options: MigrationCommandOptions,
) {
try {
if (!options.dryRun) {
@ -83,7 +83,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async getBillingPrices(
products: Stripe.Product[],
options: BaseCommandOptions,
options: MigrationCommandOptions,
): Promise<Stripe.Price[][]> {
return await Promise.all(
products.map(async (product) => {
@ -113,7 +113,7 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
private async processBillingPricesByProductBatches(
products: Stripe.Product[],
options: BaseCommandOptions,
options: MigrationCommandOptions,
) {
const prices: Stripe.Price[][] = [];
@ -135,9 +135,9 @@ export class BillingSyncPlansDataCommand extends BaseCommandRunner {
return prices;
}
override async executeBaseCommand(
override async runMigrationCommand(
passedParams: string[],
options: BaseCommandOptions,
options: MigrationCommandOptions,
): Promise<void> {
const billingMeters = await this.stripeBillingMeterService.getAllMeters();

View File

@ -64,9 +64,7 @@ registerEnumType(FieldMetadataType, {
@Relation('object', () => ObjectMetadataDTO, {
nullable: true,
})
export class FieldMetadataDTO<
T extends FieldMetadataType | 'default' = 'default',
> {
export class FieldMetadataDTO<T extends FieldMetadataType = FieldMetadataType> {
@IsUUID()
@IsNotEmpty()
@IDField(() => UUIDScalarType)
@ -75,7 +73,7 @@ export class FieldMetadataDTO<
@IsEnum(FieldMetadataType)
@IsNotEmpty()
@Field(() => FieldMetadataType)
type: FieldMetadataType;
type: T;
@IsString()
@IsNotEmpty()

View File

@ -44,7 +44,7 @@ class TextSettingsValidation {
@Injectable()
export class FieldMetadataValidationService<
T extends FieldMetadataType | 'default' = 'default',
T extends FieldMetadataType = FieldMetadataType,
> {
constructor() {}

View File

@ -36,7 +36,7 @@ import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-met
'relationTargetObjectMetadataId',
])
export class FieldMetadataEntity<
T extends FieldMetadataType | 'default' = 'default',
T extends FieldMetadataType = FieldMetadataType,
> implements FieldMetadataInterface<T>
{
@PrimaryGeneratedColumn('uuid')
@ -59,7 +59,7 @@ export class FieldMetadataEntity<
nullable: false,
type: 'varchar',
})
type: FieldMetadataType;
type: T;
@Column({ nullable: false })
name: string;

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataType, IsExactly } from 'twenty-shared';
import {
FieldMetadataDefaultActor,
@ -60,17 +60,14 @@ export type FieldMetadataFunctionDefaultValue = ExtractValueType<
FieldMetadataDefaultValueUuidFunction | FieldMetadataDefaultValueNowFunction
>;
type DefaultValueByFieldMetadata<T extends FieldMetadataType | 'default'> = [
T,
] extends [keyof FieldMetadataDefaultValueMapping]
? ExtractValueType<FieldMetadataDefaultValueMapping[T]> | null
: T extends 'default'
? ExtractValueType<UnionOfValues<FieldMetadataDefaultValueMapping>> | null
: never;
export type FieldMetadataDefaultValue<
T extends FieldMetadataType | 'default' = 'default',
> = DefaultValueByFieldMetadata<T>;
T extends FieldMetadataType = FieldMetadataType,
> =
IsExactly<T, FieldMetadataType> extends true
? ExtractValueType<UnionOfValues<FieldMetadataDefaultValueMapping>> | null
: T extends keyof FieldMetadataDefaultValueMapping
? ExtractValueType<FieldMetadataDefaultValueMapping[T]> | null
: never;
type FieldMetadataDefaultValueExtractedTypes = {
[K in keyof FieldMetadataDefaultValueMapping]: ExtractValueType<

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataType, IsExactly } from 'twenty-shared';
import {
FieldMetadataComplexOption,
@ -11,13 +11,11 @@ type FieldMetadataOptionsMapping = {
[FieldMetadataType.MULTI_SELECT]: FieldMetadataComplexOption[];
};
type OptionsByFieldMetadata<T extends FieldMetadataType | 'default'> =
T extends keyof FieldMetadataOptionsMapping
? FieldMetadataOptionsMapping[T]
: T extends 'default'
? FieldMetadataDefaultOption[] | FieldMetadataComplexOption[]
: never;
export type FieldMetadataOptions<
T extends FieldMetadataType | 'default' = 'default',
> = OptionsByFieldMetadata<T>;
T extends FieldMetadataType = FieldMetadataType,
> =
IsExactly<T, FieldMetadataType> extends true
? FieldMetadataDefaultOption[] | FieldMetadataComplexOption[]
: T extends keyof FieldMetadataOptionsMapping
? FieldMetadataOptionsMapping[T]
: never;

View File

@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataType, IsExactly } from 'twenty-shared';
import { RelationOnDeleteAction } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-on-delete-action.interface';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
@ -36,6 +36,7 @@ export type FieldMetadataDateTimeSettings = {
export type FieldMetadataRelationSettings = {
relationType: RelationType;
onDelete?: RelationOnDeleteAction;
joinColumnName?: string;
};
type FieldMetadataSettingsMapping = {
@ -46,13 +47,11 @@ type FieldMetadataSettingsMapping = {
[FieldMetadataType.RELATION]: FieldMetadataRelationSettings;
};
type SettingsByFieldMetadata<T extends FieldMetadataType | 'default'> =
T extends keyof FieldMetadataSettingsMapping
? FieldMetadataSettingsMapping[T] & FieldMetadataDefaultSettings
: T extends 'default'
? FieldMetadataDefaultSettings
: never;
export type FieldMetadataSettings<
T extends FieldMetadataType | 'default' = 'default',
> = SettingsByFieldMetadata<T>;
T extends FieldMetadataType = FieldMetadataType,
> =
IsExactly<T, FieldMetadataType> extends true
? FieldMetadataDefaultSettings
: T extends keyof FieldMetadataSettingsMapping
? FieldMetadataSettingsMapping[T] & FieldMetadataDefaultSettings
: never;

View File

@ -9,10 +9,10 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
export interface FieldMetadataInterface<
T extends FieldMetadataType | 'default' = 'default',
T extends FieldMetadataType = FieldMetadataType,
> {
id: string;
type: FieldMetadataType;
type: T;
name: string;
label: string;
defaultValue?: FieldMetadataDefaultValue<T>;

View File

@ -21,12 +21,12 @@ export function computeColumnName(
fieldName: string,
options?: ComputeColumnNameOptions,
): string;
export function computeColumnName<T extends FieldMetadataType | 'default'>(
export function computeColumnName<T extends FieldMetadataType>(
fieldMetadata: FieldMetadataInterface<T>,
ioptions?: ComputeColumnNameOptions,
): string;
// TODO: If we need to implement custom name logic for columns, we can do it here
export function computeColumnName<T extends FieldMetadataType | 'default'>(
export function computeColumnName<T extends FieldMetadataType>(
fieldMetadataOrFieldName: FieldMetadataInterface<T> | string,
options?: ComputeColumnNameOptions,
): string {
@ -51,15 +51,11 @@ export function computeCompositeColumnName(
fieldName: string,
compositeProperty: CompositeProperty,
): string;
export function computeCompositeColumnName<
T extends FieldMetadataType | 'default',
>(
export function computeCompositeColumnName<T extends FieldMetadataType>(
fieldMetadata: FieldTypeAndNameMetadata | FieldMetadataInterface<T>,
compositeProperty: CompositeProperty,
): string;
export function computeCompositeColumnName<
T extends FieldMetadataType | 'default',
>(
export function computeCompositeColumnName<T extends FieldMetadataType>(
fieldMetadataOrFieldName:
| FieldTypeAndNameMetadata
| FieldMetadataInterface<T>

View File

@ -67,9 +67,7 @@ export class CreateObjectInput {
@IsOptional()
@Field(() => GraphQLJSON, { nullable: true })
primaryKeyFieldMetadataSettings?: FieldMetadataSettings<
FieldMetadataType | 'default'
>;
primaryKeyFieldMetadataSettings?: FieldMetadataSettings<FieldMetadataType>;
@IsBoolean()
@IsOptional()

View File

@ -73,7 +73,7 @@ export class ObjectMetadataRelationService {
createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| FieldMetadataSettings<FieldMetadataType>
| undefined,
relationObjectMetadataStandardId: string,
) {
@ -109,7 +109,7 @@ export class ObjectMetadataRelationService {
relatedObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| FieldMetadataSettings<FieldMetadataType>
| undefined,
) {
return this.fieldMetadataRepository.save([
@ -340,7 +340,7 @@ export class ObjectMetadataRelationService {
relatedObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| FieldMetadataSettings<FieldMetadataType>
| undefined,
isUpdate = false,
) {

View File

@ -147,7 +147,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
toObjectMetadata,
[
foreignKeyFieldMetadata,
deletedAtFieldMetadata as FieldMetadataEntity<'default'>,
deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
],
false,
false,
@ -451,7 +451,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
relationMetadata.toObjectMetadata,
[
foreignKeyFieldMetadata,
deletedAtFieldMetadata as FieldMetadataEntity<'default'>,
deletedAtFieldMetadata as FieldMetadataEntity<FieldMetadataType>,
],
);
@ -570,7 +570,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
}
private throwIfDeletedAtFieldMetadataNotFound(
deletedAtFieldMetadata?: FieldMetadataEntity<'default'> | null,
deletedAtFieldMetadata?: FieldMetadataEntity<FieldMetadataType> | null,
) {
if (!isDefined(deletedAtFieldMetadata)) {
throw new RelationMetadataException(

View File

@ -36,7 +36,7 @@ export class RemoteTableRelationsService {
workspaceId: string,
remoteObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| FieldMetadataSettings<FieldMetadataType>
| undefined,
objectPrimaryKeyColumnType?: string,
) {
@ -150,7 +150,7 @@ export class RemoteTableRelationsService {
createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| FieldMetadataSettings<FieldMetadataType>
| undefined,
) {
const attachmentObjectMetadata =
@ -190,7 +190,7 @@ export class RemoteTableRelationsService {
createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| FieldMetadataSettings<FieldMetadataType>
| undefined,
) {
const timelineActivityObjectMetadata =
@ -230,7 +230,7 @@ export class RemoteTableRelationsService {
createdObjectMetadata: ObjectMetadataEntity,
objectPrimaryKeyType: FieldMetadataType,
objectPrimaryKeyFieldSettings:
| FieldMetadataSettings<FieldMetadataType | 'default'>
| FieldMetadataSettings<FieldMetadataType>
| undefined,
) {
const favoriteObjectMetadata =

View File

@ -37,12 +37,12 @@ export const mapUdtNameToFieldSettings = (
case 'int4':
return {
dataType: NumberDataType.INT,
} satisfies FieldMetadataSettings<FieldMetadataType.NUMBER>;
} as FieldMetadataSettings<FieldMetadataType.NUMBER>;
case 'int8':
case 'bigint':
return {
dataType: NumberDataType.BIGINT,
} satisfies FieldMetadataSettings<FieldMetadataType.NUMBER>;
} as FieldMetadataSettings<FieldMetadataType.NUMBER>;
default:
return undefined;
}

View File

@ -18,9 +18,8 @@ import {
WorkspaceMigrationExceptionCode,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception';
export class ColumnActionAbstractFactory<
T extends FieldMetadataType | 'default',
> implements WorkspaceColumnActionFactory<T>
export class ColumnActionAbstractFactory<T extends FieldMetadataType>
implements WorkspaceColumnActionFactory<T>
{
protected readonly logger = new Logger(ColumnActionAbstractFactory.name);

View File

@ -8,9 +8,7 @@ import {
WorkspaceMigrationColumnActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
export interface WorkspaceColumnActionFactory<
T extends FieldMetadataType | 'default',
> {
export interface WorkspaceColumnActionFactory<T extends FieldMetadataType> {
create(
action:
| WorkspaceMigrationColumnActionType.CREATE

View File

@ -1,7 +1,6 @@
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import {
FieldMetadataNumberSettings,
FieldMetadataTextSettings,
NumberDataType,
NumberDataType
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
@ -22,8 +21,8 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
dataType: NumberDataType.FLOAT,
decimals: 3,
type: 'number',
} as FieldMetadataNumberSettings,
},
},
} as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{
type: FieldMetadataType.NUMBER,
label: 'Percentage of completion (Float 3 decimals + percentage)',
@ -32,8 +31,8 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
dataType: NumberDataType.FLOAT,
decimals: 6,
type: 'percentage',
} as FieldMetadataNumberSettings,
},
},
} as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{
type: FieldMetadataType.NUMBER,
label: 'Participants (Int)',
@ -41,8 +40,8 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
settings: {
dataType: NumberDataType.INT,
type: 'number',
} as FieldMetadataNumberSettings,
},
},
} as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{
type: FieldMetadataType.NUMBER,
label: 'Average estimated number of atoms in the universe (BigInt)',
@ -50,23 +49,23 @@ export const SURVEY_RESULTS_METADATA_SEEDS: ObjectMetadataSeed = {
settings: {
dataType: NumberDataType.BIGINT,
type: 'number',
} as FieldMetadataNumberSettings,
},
},
} as FieldMetadataDTO<FieldMetadataType.NUMBER>,
{
type: FieldMetadataType.TEXT,
label: 'Comments (Max 5 rows)',
name: 'comments',
settings: {
displayedMaxRows: 5,
} as FieldMetadataTextSettings,
},
},
} as FieldMetadataDTO<FieldMetadataType.TEXT>,
{
type: FieldMetadataType.TEXT,
label: 'Short notes (Max 1 row)',
name: 'shortNotes',
settings: {
displayedMaxRows: 1,
} as FieldMetadataTextSettings,
},
},
} as FieldMetadataDTO<FieldMetadataType.TEXT>,
],
};

View File

@ -11,7 +11,7 @@ import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args
import { TypedReflect } from 'src/utils/typed-reflect';
export interface WorkspaceFieldOptions<
T extends FieldMetadataType | 'default',
T extends FieldMetadataType = FieldMetadataType,
> {
standardId: string;
type: T;

View File

@ -0,0 +1,28 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
export function isFieldMetadataOfType<
Field extends FieldMetadataInterface<FieldMetadataType>,
Type extends FieldMetadataType,
>(
fieldMetadata: Field,
type: Type,
): fieldMetadata is Field & FieldMetadataInterface<Type>;
export function isFieldMetadataOfType<
Field extends FieldMetadataEntity<FieldMetadataType>,
Type extends FieldMetadataType,
>(
fieldMetadata: Field,
type: Type,
): fieldMetadata is Field & FieldMetadataEntity<Type>;
export function isFieldMetadataOfType<
Field extends
| FieldMetadataInterface<FieldMetadataType>
| FieldMetadataEntity<FieldMetadataType>,
Type extends FieldMetadataType,
>(fieldMetadata: Field, type: Type): boolean {
return fieldMetadata.type === type;
}

View File

@ -1,9 +0,0 @@
import { FieldMetadataType } from 'twenty-shared';
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
export const isRelationFieldMetadata = (
fieldMetadata: FieldMetadataInterface<'default' | FieldMetadataType.RELATION>,
): fieldMetadata is FieldMetadataInterface<FieldMetadataType.RELATION> => {
return fieldMetadata.type === FieldMetadataType.RELATION;
};

View File

@ -5,9 +5,9 @@ import { WorkspaceActivationStatus } from 'twenty-shared';
import { In, Repository } from 'typeorm';
import {
BaseCommandOptions,
BaseCommandRunner,
} from 'src/database/commands/base.command';
MigrationCommandOptions,
MigrationCommandRunner,
} from 'src/database/commands/migration-command/migration-command.runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service';
@ -15,7 +15,7 @@ import { CleanerWorkspaceService } from 'src/engine/workspace-manager/workspace-
name: 'workspace:clean',
description: 'Clean suspended workspace',
})
export class CleanSuspendedWorkspacesCommand extends BaseCommandRunner {
export class CleanSuspendedWorkspacesCommand extends MigrationCommandRunner {
private workspaceIds: string[] = [];
constructor(
@ -50,9 +50,9 @@ export class CleanSuspendedWorkspacesCommand extends BaseCommandRunner {
return suspendedWorkspaces.map((workspace) => workspace.id);
}
override async executeBaseCommand(
override async runMigrationCommand(
_passedParams: string[],
options: BaseCommandOptions,
options: MigrationCommandOptions,
): Promise<void> {
const { dryRun } = options;

View File

@ -3,7 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Command, Option } from 'nest-commander';
import { Repository } from 'typeorm';
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
import {
ActiveWorkspacesMigrationCommandOptions,
ActiveWorkspacesMigrationCommandRunner,
} from 'src/database/commands/migration-command/active-workspaces-migration-command.runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@ -12,18 +15,16 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
import { SyncWorkspaceLoggerService } from './services/sync-workspace-logger.service';
// TODO: implement dry-run
interface RunWorkspaceMigrationsOptions {
dryRun?: boolean;
interface RunWorkspaceMigrationsOptions
extends ActiveWorkspacesMigrationCommandOptions {
force?: boolean;
workspaceId?: string;
}
@Command({
name: 'workspace:sync-metadata',
description: 'Sync metadata',
})
export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner {
export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(Workspace, 'core')
protected readonly workspaceRepository: Repository<Workspace>,
@ -36,7 +37,7 @@ export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner
super(workspaceRepository, twentyORMGlobalManager);
}
async executeActiveWorkspacesCommand(
async runMigrationCommandOnActiveWorkspaces(
_passedParam: string[],
options: RunWorkspaceMigrationsOptions,
workspaceIds: string[],
@ -133,15 +134,6 @@ export class SyncWorkspaceMetadataCommand extends ActiveWorkspacesCommandRunner
);
}
@Option({
flags: '-d, --dry-run',
description: 'Dry run without applying changes',
required: false,
})
dryRun(): boolean {
return true;
}
@Option({
flags: '-f, --force',
description: 'Force migration',