Add featureFlag gateDecorator for sync-metadata (#2956)

* Add featureFlag gateDecorator for sync-metadata

* remove gate exampels

* gate messaging objects

* gate messaging recipient object

* add missing gate
This commit is contained in:
Weiko
2023-12-12 17:34:59 +01:00
committed by GitHub
parent 6977fd4ce2
commit f126bd95d6
15 changed files with 201 additions and 77 deletions

View File

@ -10,7 +10,7 @@ import { WorkspaceModule } from 'src/core/workspace/workspace.module';
import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command'; import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace.command'; import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace.command';
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module'; import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module'; import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/workspace-sync-metadata.module';
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module'; import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
@Module({ @Module({

View File

@ -4,7 +4,7 @@ import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module'; import { ObjectMetadataModule } from 'src/metadata/object-metadata/object-metadata.module';
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module'; import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module'; import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module'; import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/workspace-sync-metadata.module';
import { WorkspaceManagerService } from './workspace-manager.service'; import { WorkspaceManagerService } from './workspace-manager.service';

View File

@ -29,6 +29,7 @@ export class SyncWorkspaceMetadataCommand extends CommandRunner {
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
options.workspaceId, options.workspaceId,
); );
await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata( await this.workspaceSyncMetadataService.syncStandardObjectsAndFieldsMetadata(
dataSourceMetadata.id, dataSourceMetadata.id,
options.workspaceId, options.workspaceId,

View File

@ -1,7 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module'; import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/worksapce-sync-metadata.module'; import { WorkspaceSyncMetadataModule } from 'src/workspace/workspace-sync-metadata/workspace-sync-metadata.module';
import { SyncWorkspaceMetadataCommand } from './sync-workspace-metadata.command'; import { SyncWorkspaceMetadataCommand } from './sync-workspace-metadata.command';

View File

@ -31,6 +31,10 @@ export type RelationMetadataDecorator = {
inverseSideFieldName?: string; inverseSideFieldName?: string;
}; };
export type GateDecorator = {
featureFlag: string;
};
function convertClassNameToObjectMetadataName(name: string): string { function convertClassNameToObjectMetadataName(name: string): string {
const classSuffix = 'ObjectMetadata'; const classSuffix = 'ObjectMetadata';
let objectName = camelCase(name); let objectName = camelCase(name);
@ -48,6 +52,8 @@ export function ObjectMetadata(
return (target) => { return (target) => {
const isSystem = Reflect.getMetadata('isSystem', target) || false; const isSystem = Reflect.getMetadata('isSystem', target) || false;
const gate = Reflect.getMetadata('gate', target) || undefined;
const objectName = convertClassNameToObjectMetadataName(target.name); const objectName = convertClassNameToObjectMetadataName(target.name);
Reflect.defineMetadata( Reflect.defineMetadata(
@ -55,6 +61,7 @@ export function ObjectMetadata(
{ {
nameSingular: objectName, nameSingular: objectName,
...metadata, ...metadata,
gate,
targetTableName: objectName, targetTableName: objectName,
isSystem, isSystem,
isCustom: false, isCustom: false,
@ -82,6 +89,16 @@ export function IsSystem() {
}; };
} }
export function Gate(metadata: GateDecorator) {
return function (target: object, fieldKey?: string) {
if (fieldKey) {
Reflect.defineMetadata('gate', metadata, target, fieldKey);
} else {
Reflect.defineMetadata('gate', metadata, target);
}
};
}
export function FieldMetadata<T extends FieldMetadataType>( export function FieldMetadata<T extends FieldMetadataType>(
metadata: FieldMetadataDecorator<T>, metadata: FieldMetadataDecorator<T>,
): PropertyDecorator { ): PropertyDecorator {
@ -94,6 +111,8 @@ export function FieldMetadata<T extends FieldMetadataType>(
const isSystem = Reflect.getMetadata('isSystem', target, fieldKey) || false; const isSystem = Reflect.getMetadata('isSystem', target, fieldKey) || false;
const gate = Reflect.getMetadata('gate', target, fieldKey) || undefined;
const { joinColumn, ...fieldMetadata } = metadata; const { joinColumn, ...fieldMetadata } = metadata;
Reflect.defineMetadata( Reflect.defineMetadata(
@ -105,6 +124,7 @@ export function FieldMetadata<T extends FieldMetadataType>(
fieldKey, fieldKey,
isNullable, isNullable,
isSystem, isSystem,
gate,
), ),
...(joinColumn && fieldMetadata.type === FieldMetadataType.RELATION ...(joinColumn && fieldMetadata.type === FieldMetadataType.RELATION
? { ? {
@ -119,6 +139,7 @@ export function FieldMetadata<T extends FieldMetadataType>(
joinColumn, joinColumn,
isNullable, isNullable,
true, true,
gate,
), ),
} }
: {}), : {}),
@ -133,6 +154,7 @@ function generateFieldMetadata<T extends FieldMetadataType>(
fieldKey: string, fieldKey: string,
isNullable: boolean, isNullable: boolean,
isSystem: boolean, isSystem: boolean,
gate: GateDecorator | undefined = undefined,
) { ) {
const targetColumnMap = JSON.stringify( const targetColumnMap = JSON.stringify(
generateTargetColumnMap(metadata.type, false, fieldKey), generateTargetColumnMap(metadata.type, false, fieldKey),
@ -152,6 +174,7 @@ function generateFieldMetadata<T extends FieldMetadataType>(
description: metadata.description ?? null, description: metadata.description ?? null,
icon: metadata.icon ?? null, icon: metadata.icon ?? null,
defaultValue: defaultValue ? JSON.stringify(defaultValue) : null, defaultValue: defaultValue ? JSON.stringify(defaultValue) : null,
gate,
}; };
} }
@ -162,6 +185,8 @@ export function RelationMetadata(
const existingRelationMetadata = const existingRelationMetadata =
Reflect.getMetadata('relationMetadata', target.constructor) || []; Reflect.getMetadata('relationMetadata', target.constructor) || [];
const gate = Reflect.getMetadata('gate', target, fieldKey) || undefined;
const objectName = convertClassNameToObjectMetadataName( const objectName = convertClassNameToObjectMetadataName(
target.constructor.name, target.constructor.name,
); );
@ -176,6 +201,7 @@ export function RelationMetadata(
toObjectNameSingular: metadata.objectName, toObjectNameSingular: metadata.objectName,
fromFieldMetadataName: fieldKey, fromFieldMetadataName: fieldKey,
toFieldMetadataName: metadata.inverseSideFieldName ?? objectName, toFieldMetadataName: metadata.inverseSideFieldName ?? objectName,
gate,
}, },
], ],
target.constructor, target.constructor,

View File

@ -6,6 +6,7 @@ import {
FieldMetadata, FieldMetadata,
IsNullable, IsNullable,
RelationMetadata, RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; } from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata'; import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
@ -18,6 +19,9 @@ import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-meta
description: 'A connected account', description: 'A connected account',
icon: 'IconAt', icon: 'IconAt',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem() @IsSystem()
export class ConnectedAccountObjectMetadata extends BaseObjectMetadata { export class ConnectedAccountObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({

View File

@ -6,6 +6,7 @@ import {
FieldMetadata, FieldMetadata,
IsNullable, IsNullable,
RelationMetadata, RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; } from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata'; import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata';
@ -18,6 +19,9 @@ import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metada
description: 'Message Channels', description: 'Message Channels',
icon: 'IconMessage', icon: 'IconMessage',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem() @IsSystem()
export class MessageChannelObjectMetadata extends BaseObjectMetadata { export class MessageChannelObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({

View File

@ -4,6 +4,7 @@ import {
IsSystem, IsSystem,
FieldMetadata, FieldMetadata,
IsNullable, IsNullable,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; } from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata'; import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata';
@ -17,6 +18,9 @@ import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-meta
description: 'Message Recipients', description: 'Message Recipients',
icon: 'IconUserCircle', icon: 'IconUserCircle',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem() @IsSystem()
export class MessageRecipientObjectMetadata extends BaseObjectMetadata { export class MessageRecipientObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({

View File

@ -6,6 +6,7 @@ import {
FieldMetadata, FieldMetadata,
IsNullable, IsNullable,
RelationMetadata, RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; } from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata'; import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
@ -18,6 +19,9 @@ import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/sta
description: 'Message Thread', description: 'Message Thread',
icon: 'IconMessage', icon: 'IconMessage',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem() @IsSystem()
export class MessageThreadObjectMetadata extends BaseObjectMetadata { export class MessageThreadObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({

View File

@ -6,6 +6,7 @@ import {
FieldMetadata, FieldMetadata,
IsNullable, IsNullable,
RelationMetadata, RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; } from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { MessageRecipientObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-recipient.object-metadata'; import { MessageRecipientObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-recipient.object-metadata';
@ -18,6 +19,9 @@ import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metada
description: 'Message', description: 'Message',
icon: 'IconMessage', icon: 'IconMessage',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem() @IsSystem()
export class MessageObjectMetadata extends BaseObjectMetadata { export class MessageObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({

View File

@ -8,6 +8,7 @@ import {
IsNullable, IsNullable,
RelationMetadata, RelationMetadata,
IsSystem, IsSystem,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; } from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { ActivityTargetObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata'; import { ActivityTargetObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity-target.object-metadata';
import { AttachmentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata'; import { AttachmentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata';
@ -186,6 +187,9 @@ export class PersonObjectMetadata extends BaseObjectMetadata {
objectName: 'messageRecipient', objectName: 'messageRecipient',
inverseSideFieldName: 'person', inverseSideFieldName: 'person',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsNullable() @IsNullable()
messageRecipients: MessageRecipientObjectMetadata[]; messageRecipients: MessageRecipientObjectMetadata[];
} }

View File

@ -7,6 +7,7 @@ import {
FieldMetadata, FieldMetadata,
IsNullable, IsNullable,
RelationMetadata, RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; } from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { ActivityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata'; import { ActivityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata';
import { AttachmentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata'; import { AttachmentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata';
@ -163,6 +164,9 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
objectName: 'connectedAccount', objectName: 'connectedAccount',
inverseSideFieldName: 'accountOwner', inverseSideFieldName: 'accountOwner',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsNullable() @IsNullable()
connectedAccounts: ConnectedAccountObjectMetadata[]; connectedAccounts: ConnectedAccountObjectMetadata[];
@ -177,6 +181,9 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
objectName: 'messageRecipient', objectName: 'messageRecipient',
inverseSideFieldName: 'workspaceMember', inverseSideFieldName: 'workspaceMember',
}) })
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsNullable() @IsNullable()
messageRecipients: MessageRecipientObjectMetadata[]; messageRecipients: MessageRecipientObjectMetadata[];
} }

View File

@ -9,109 +9,156 @@ export class MetadataParser {
metadata: typeof BaseObjectMetadata, metadata: typeof BaseObjectMetadata,
workspaceId: string, workspaceId: string,
dataSourceId: string, dataSourceId: string,
) { workspaceFeatureFlagsMap: Record<string, boolean>,
): ObjectMetadataEntity | undefined {
const objectMetadata = Reflect.getMetadata('objectMetadata', metadata); const objectMetadata = Reflect.getMetadata('objectMetadata', metadata);
const fieldMetadata = Reflect.getMetadata('fieldMetadata', metadata); const fieldMetadata = Reflect.getMetadata('fieldMetadata', metadata);
if (objectMetadata) { if (!objectMetadata) {
const fields = Object.values(fieldMetadata); throw new Error(
`Object metadata decorator not found, can\'t parse ${metadata.name}`,
return { );
...objectMetadata,
workspaceId,
dataSourceId,
fields: fields.map((field: FieldMetadataEntity) => ({
...field,
workspaceId,
isSystem: objectMetadata.isSystem || field.isSystem,
defaultValue: field.defaultValue || null,
options: field.options || null,
})),
};
} }
return undefined; if (isGatedAndNotEnabled(objectMetadata, workspaceFeatureFlagsMap)) {
return undefined;
}
const fields = Object.values(fieldMetadata).filter(
(field) => !isGatedAndNotEnabled(field, workspaceFeatureFlagsMap),
);
return {
...objectMetadata,
workspaceId,
dataSourceId,
fields: fields.map((field: FieldMetadataEntity) => ({
...field,
workspaceId,
isSystem: objectMetadata.isSystem || field.isSystem,
defaultValue: field.defaultValue || null,
options: field.options || null,
})),
};
} }
static parseAllMetadata( static parseAllMetadata(
metadataCollection: (typeof BaseObjectMetadata)[], metadataCollection: (typeof BaseObjectMetadata)[],
workspaceId: string, workspaceId: string,
dataSourceId: string, dataSourceId: string,
) { workspaceFeatureFlagsMap: Record<string, boolean>,
return metadataCollection.map((metadata) => ): ObjectMetadataEntity[] {
MetadataParser.parseMetadata(metadata, workspaceId, dataSourceId), return metadataCollection
); .map((metadata) =>
MetadataParser.parseMetadata(
metadata,
workspaceId,
dataSourceId,
workspaceFeatureFlagsMap,
),
)
.filter(
(metadata): metadata is ObjectMetadataEntity => metadata !== undefined,
);
} }
static parseRelationMetadata( static parseRelationMetadata(
metadata: typeof BaseObjectMetadata, metadata: typeof BaseObjectMetadata,
workspaceId: string, workspaceId: string,
objectMetadataFromDB: Record<string, ObjectMetadataEntity>, objectMetadataFromDB: Record<string, ObjectMetadataEntity>,
workspaceFeatureFlagsMap: Record<string, boolean>,
) { ) {
const objectMetadata = Reflect.getMetadata('objectMetadata', metadata); const objectMetadata = Reflect.getMetadata('objectMetadata', metadata);
const relationMetadata = Reflect.getMetadata('relationMetadata', metadata); const relationMetadata = Reflect.getMetadata('relationMetadata', metadata);
if (!relationMetadata) return []; if (!relationMetadata) return [];
return relationMetadata.map((relation) => { if (!objectMetadata) {
const fromObjectMetadata = throw new Error(
objectMetadataFromDB[relation.fromObjectNameSingular]; `Object metadata decorator not found, can\'t parse ${metadata.name}`,
assert(
fromObjectMetadata,
`Object ${relation.fromObjectNameSingular} not found in DB
for relation defined in class ${objectMetadata.nameSingular}`,
); );
}
const toObjectMetadata = if (isGatedAndNotEnabled(objectMetadata, workspaceFeatureFlagsMap)) {
objectMetadataFromDB[relation.toObjectNameSingular]; return [];
}
assert( return relationMetadata
toObjectMetadata, .filter(
`Object ${relation.toObjectNameSingular} not found in DB (relation) => !isGatedAndNotEnabled(relation, workspaceFeatureFlagsMap),
for relation defined in class ${objectMetadata.nameSingular}`, )
); .map((relation) => {
const fromObjectMetadata =
objectMetadataFromDB[relation.fromObjectNameSingular];
const fromFieldMetadata = assert(
fromObjectMetadata?.fields[relation.fromFieldMetadataName]; fromObjectMetadata,
`Object ${relation.fromObjectNameSingular} not found in DB
for fromRelation defined in class ${objectMetadata.nameSingular}`,
);
assert( const toObjectMetadata =
fromFieldMetadata, objectMetadataFromDB[relation.toObjectNameSingular];
`Field ${relation.fromFieldMetadataName} not found in object ${relation.fromObjectNameSingular}
for relation defined in class ${objectMetadata.nameSingular}`,
);
const toFieldMetadata = assert(
toObjectMetadata?.fields[relation.toFieldMetadataName]; toObjectMetadata,
`Object ${relation.toObjectNameSingular} not found in DB
for toRelation defined in class ${objectMetadata.nameSingular}`,
);
assert( const fromFieldMetadata =
toFieldMetadata, fromObjectMetadata?.fields[relation.fromFieldMetadataName];
`Field ${relation.toFieldMetadataName} not found in object ${relation.toObjectNameSingular}
for relation defined in class ${objectMetadata.nameSingular}`,
);
return { assert(
relationType: relation.type, fromFieldMetadata,
fromObjectMetadataId: fromObjectMetadata?.id, `Field ${relation.fromFieldMetadataName} not found in object ${relation.fromObjectNameSingular}
toObjectMetadataId: toObjectMetadata?.id, for fromRelation defined in class ${objectMetadata.nameSingular}`,
fromFieldMetadataId: fromFieldMetadata?.id, );
toFieldMetadataId: toFieldMetadata?.id,
workspaceId, const toFieldMetadata =
}; toObjectMetadata?.fields[relation.toFieldMetadataName];
});
assert(
toFieldMetadata,
`Field ${relation.toFieldMetadataName} not found in object ${relation.toObjectNameSingular}
for toRelation defined in class ${objectMetadata.nameSingular}`,
);
return {
relationType: relation.type,
fromObjectMetadataId: fromObjectMetadata?.id,
toObjectMetadataId: toObjectMetadata?.id,
fromFieldMetadataId: fromFieldMetadata?.id,
toFieldMetadataId: toFieldMetadata?.id,
workspaceId,
};
});
} }
static parseAllRelations( static parseAllRelations(
metadataCollection: (typeof BaseObjectMetadata)[], metadataCollection: (typeof BaseObjectMetadata)[],
workspaceId: string, workspaceId: string,
objectMetadataFromDB: Record<string, ObjectMetadataEntity>, objectMetadataFromDB: Record<string, ObjectMetadataEntity>,
workspaceFeatureFlagsMap: Record<string, boolean>,
) { ) {
return metadataCollection.flatMap((metadata) => return metadataCollection.flatMap((metadata) =>
MetadataParser.parseRelationMetadata( MetadataParser.parseRelationMetadata(
metadata, metadata,
workspaceId, workspaceId,
objectMetadataFromDB, objectMetadataFromDB,
workspaceFeatureFlagsMap,
), ),
); );
} }
} }
function isGatedAndNotEnabled(
metadata,
workspaceFeatureFlagsMap: Record<string, boolean>,
): boolean {
const featureFlagValue =
metadata.gate?.featureFlag &&
workspaceFeatureFlagsMap[metadata.gate.featureFlag];
return metadata.gate?.featureFlag !== undefined && !featureFlagValue;
}

View File

@ -1,6 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity'; import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-metadata.entity';
@ -22,6 +23,7 @@ import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metad
], ],
'metadata', 'metadata',
), ),
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
], ],
exports: [WorkspaceSyncMetadataService], exports: [WorkspaceSyncMetadataService],
providers: [WorkspaceSyncMetadataService], providers: [WorkspaceSyncMetadataService],

View File

@ -16,9 +16,9 @@ import {
} from 'src/metadata/relation-metadata/relation-metadata.entity'; } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { MetadataParser } from 'src/workspace/workspace-sync-metadata/utils/metadata.parser'; import { MetadataParser } from 'src/workspace/workspace-sync-metadata/utils/metadata.parser';
import { import {
mapObjectMetadataByUniqueIdentifier,
filterIgnoredProperties, filterIgnoredProperties,
convertStringifiedFieldsToJSON, convertStringifiedFieldsToJSON,
mapObjectMetadataByUniqueIdentifier,
} from 'src/workspace/workspace-sync-metadata/utils/sync-metadata.util'; } from 'src/workspace/workspace-sync-metadata/utils/sync-metadata.util';
import { standardObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects'; import { standardObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects';
import { import {
@ -29,6 +29,7 @@ import {
} from 'src/metadata/workspace-migration/workspace-migration.entity'; } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory'; import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service'; import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
@Injectable() @Injectable()
export class WorkspaceSyncMetadataService { export class WorkspaceSyncMetadataService {
@ -44,6 +45,8 @@ export class WorkspaceSyncMetadataService {
private readonly relationMetadataRepository: Repository<RelationMetadataEntity>, private readonly relationMetadataRepository: Repository<RelationMetadataEntity>,
@InjectRepository(WorkspaceMigrationEntity, 'metadata') @InjectRepository(WorkspaceMigrationEntity, 'metadata')
private readonly workspaceMigrationRepository: Repository<WorkspaceMigrationEntity>, private readonly workspaceMigrationRepository: Repository<WorkspaceMigrationEntity>,
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
) {} ) {}
/** /**
@ -58,13 +61,27 @@ export class WorkspaceSyncMetadataService {
dataSourceId: string, dataSourceId: string,
workspaceId: string, workspaceId: string,
) { ) {
const standardObjects = MetadataParser.parseAllMetadata(
standardObjectMetadata,
workspaceId,
dataSourceId,
);
try { try {
const workspaceFeatureFlags = await this.featureFlagRepository.find({
where: { workspaceId },
});
const workspaceFeatureFlagsMap = workspaceFeatureFlags.reduce(
(result, currentFeatureFlag) => {
result[currentFeatureFlag.key] = currentFeatureFlag.value;
return result;
},
{},
);
const standardObjects = MetadataParser.parseAllMetadata(
standardObjectMetadata,
workspaceId,
dataSourceId,
workspaceFeatureFlagsMap,
);
const objectsInDB = await this.objectMetadataRepository.find({ const objectsInDB = await this.objectMetadataRepository.find({
where: { workspaceId, dataSourceId, isCustom: false }, where: { workspaceId, dataSourceId, isCustom: false },
relations: ['fields'], relations: ['fields'],
@ -244,7 +261,11 @@ export class WorkspaceSyncMetadataService {
// We run syncRelationMetadata after everything to ensure that all objects and fields are // We run syncRelationMetadata after everything to ensure that all objects and fields are
// in the DB before creating relations. // in the DB before creating relations.
await this.syncRelationMetadata(workspaceId, dataSourceId); await this.syncRelationMetadata(
workspaceId,
dataSourceId,
workspaceFeatureFlagsMap,
);
// Execute migrations // Execute migrations
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
@ -258,6 +279,7 @@ export class WorkspaceSyncMetadataService {
private async syncRelationMetadata( private async syncRelationMetadata(
workspaceId: string, workspaceId: string,
dataSourceId: string, dataSourceId: string,
workspaceFeatureFlagsMap: Record<string, boolean>,
) { ) {
const objectsInDB = await this.objectMetadataRepository.find({ const objectsInDB = await this.objectMetadataRepository.find({
where: { workspaceId, dataSourceId, isCustom: false }, where: { workspaceId, dataSourceId, isCustom: false },
@ -268,13 +290,8 @@ export class WorkspaceSyncMetadataService {
standardObjectMetadata, standardObjectMetadata,
workspaceId, workspaceId,
objectsInDBByName, objectsInDBByName,
).reduce((result, currentObject) => { workspaceFeatureFlagsMap,
const key = `${currentObject.fromObjectMetadataId}->${currentObject.fromFieldMetadataId}`; );
result[key] = currentObject;
return result;
}, {});
// TODO: filter out custom relations once isCustom has been added to relationMetadata table // TODO: filter out custom relations once isCustom has been added to relationMetadata table
const relationsInDB = await this.relationMetadataRepository.find({ const relationsInDB = await this.relationMetadataRepository.find({