feat: migration can be applied on a specific schema & some enhancements (#2998)

* fix: remove old metadata seed files

* feat: wip standard to core relation

* fix: lint

* fix: merge

* fix: remove debug files

* feat: add feature flag for core object metadata

* fix: remove debug

* feat: always disable the standard core relation

* fix: missing feature flag

* fix: remove debug

* fix: feature flag doesn't seems to disable relation

* fix: delete .vscode folder, change this in another PR

* Update packages/twenty-server/src/workspace/workspace-sync-metadata/reflective-metadata.factory.ts

Co-authored-by: Weiko <corentin@twenty.com>

* Update packages/twenty-server/src/workspace/workspace-sync-metadata/reflective-metadata.factory.ts

Co-authored-by: Weiko <corentin@twenty.com>

* Update packages/twenty-server/src/workspace/workspace-sync-metadata/workspace-sync.metadata.service.ts

Co-authored-by: Weiko <corentin@twenty.com>

* fix: remove optional fields from metadata entities

* fix: renamed variable

* fix: put back CursorScalarType

* fix: delete test command

* fix: remove unused workspace standard migration command

* fix: drop core object metadata declaration

* fix: rename variable

* fix: drop creation of core datasource

* fix: remove feature flag

* fix: drop support of standard to core relations

* feat: add user email field on workspace-member standard object

* fix: update seed accordingly

* fix: missing remove command file

* fix: datasource label should remain nullable

* fix: better asserts

* Remove unused code

* Remove unused code

---------

Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2023-12-21 19:15:05 +01:00
committed by GitHub
parent 3234134a30
commit d532f22fbb
112 changed files with 714 additions and 6381 deletions

View File

@ -1,45 +0,0 @@
import { Command, CommandRunner, Option } from 'nest-commander';
import { WorkspaceMigrationService } from 'src/metadata/workspace-migration/workspace-migration.service';
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
// TODO: implement dry-run
interface RunWorkspaceMigrationsOptions {
workspaceId: string;
}
@Command({
name: 'workspace:migrate',
description: 'Run workspace migrations',
})
export class RunWorkspaceMigrationsCommand extends CommandRunner {
constructor(
private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
) {
super();
}
async run(
_passedParam: string[],
options: RunWorkspaceMigrationsOptions,
): Promise<void> {
// TODO: run in a dedicated job + run queries in a transaction.
await this.workspaceMigrationService.insertStandardMigrations(
options.workspaceId,
);
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
options.workspaceId,
);
}
// TODO: workspaceId should be optional and we should run migrations for all workspaces
@Option({
flags: '-w, --workspace-id [workspace_id]',
description: 'workspace id',
required: true,
})
parseWorkspaceId(value: string): string {
return value;
}
}

View File

@ -1,12 +0,0 @@
import { Module } from '@nestjs/common';
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module';
import { RunWorkspaceMigrationsCommand } from './run-workspace-migrations.command';
@Module({
imports: [WorkspaceMigrationModule, WorkspaceMigrationRunnerModule],
providers: [RunWorkspaceMigrationsCommand],
})
export class WorkspaceMigrationRunnerCommandsModule {}

View File

@ -63,13 +63,11 @@ export class WorkspaceMigrationRunnerService {
}, []);
const queryRunner = workspaceDataSource?.createQueryRunner();
const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId);
// Loop over each migration and create or update the table
// TODO: Should be done in a transaction
for (const migration of flattenedPendingMigrations) {
await this.handleTableChanges(queryRunner, schemaName, migration);
await this.handleTableChanges(queryRunner, migration);
}
// Update appliedAt date for each migration
@ -98,17 +96,20 @@ export class WorkspaceMigrationRunnerService {
*/
private async handleTableChanges(
queryRunner: QueryRunner,
schemaName: string,
tableMigration: WorkspaceMigrationTableAction,
) {
switch (tableMigration.action) {
case 'create':
await this.createTable(queryRunner, schemaName, tableMigration.name);
await this.createTable(
queryRunner,
tableMigration.schemaName,
tableMigration.name,
);
break;
case 'alter':
await this.handleColumnChanges(
queryRunner,
schemaName,
tableMigration.schemaName,
tableMigration.name,
tableMigration?.columns,
);
@ -180,7 +181,7 @@ export class WorkspaceMigrationRunnerService {
);
break;
case WorkspaceMigrationColumnActionType.RELATION:
await this.createForeignKey(
await this.createRelation(
queryRunner,
schemaName,
tableName,
@ -279,10 +280,9 @@ export class WorkspaceMigrationRunnerService {
isNullable: migrationColumn.alteredColumnDefinition.isNullable,
}),
);
// }
}
private async createForeignKey(
private async createRelation(
queryRunner: QueryRunner,
schemaName: string,
tableName: string,
@ -293,6 +293,7 @@ export class WorkspaceMigrationRunnerService {
new TableForeignKey({
columnNames: [migrationColumn.columnName],
referencedColumnNames: [migrationColumn.referencedTableColumnName],
referencedSchema: migrationColumn.referencedSchema,
referencedTableName: migrationColumn.referencedTableName,
onDelete: 'CASCADE',
}),

View File

@ -104,12 +104,12 @@ export class RelationFieldAliasFactory {
);
return `
${fieldKey}: ${referencedObjectMetadata.targetTableName}Collection${
${fieldKey}: ${referencedObjectMetadata.targetTableName}Collection${
argsString ? `(${argsString})` : ''
} {
${fieldsString}
}
`;
${fieldsString}
}
`;
}
let relationAlias = fieldMetadata.isCustom
? `${fieldKey}: ${fieldMetadata.targetColumnMap.value}`

View File

@ -1,6 +1,6 @@
import { CursorScalarType } from './cursor.scalar';
import { BigFloatScalarType } from './big-float.scalar';
import { BigIntScalarType } from './big-int.scalar';
import { CursorScalarType } from './cursor.scalar';
import { DateScalarType } from './date.scalar';
import { DateTimeScalarType } from './date-time.scalar';
import { TimeScalarType } from './time.scalar';

View File

@ -0,0 +1,82 @@
import {
FieldMetadataDecoratorParams,
ReflectFieldMetadata,
} from 'src/workspace/workspace-sync-metadata/interfaces/reflect-field-metadata.interface';
import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { generateTargetColumnMap } from 'src/metadata/field-metadata/utils/generate-target-column-map.util';
import { generateDefaultValue } from 'src/metadata/field-metadata/utils/generate-default-value';
import { TypedReflect } from 'src/utils/typed-reflect';
export function FieldMetadata<T extends FieldMetadataType>(
params: FieldMetadataDecoratorParams<T>,
): PropertyDecorator {
return (target: object, fieldKey: string) => {
const existingFieldMetadata =
TypedReflect.getMetadata('fieldMetadata', target.constructor) ?? {};
const isNullable =
TypedReflect.getMetadata('isNullable', target, fieldKey) ?? false;
const isSystem =
TypedReflect.getMetadata('isSystem', target, fieldKey) ?? false;
const gate = TypedReflect.getMetadata('gate', target, fieldKey);
const { joinColumn, ...restParams } = params;
TypedReflect.defineMetadata(
'fieldMetadata',
{
...existingFieldMetadata,
[fieldKey]: generateFieldMetadata<T>(
restParams,
fieldKey,
isNullable,
isSystem,
gate,
),
...(joinColumn && restParams.type === FieldMetadataType.RELATION
? {
[joinColumn]: generateFieldMetadata<FieldMetadataType.UUID>(
{
...restParams,
type: FieldMetadataType.UUID,
label: `${restParams.label} id (foreign key)`,
description: `${restParams.description} id foreign key`,
defaultValue: null,
},
joinColumn,
isNullable,
true,
gate,
),
}
: {}),
},
target.constructor,
);
};
}
function generateFieldMetadata<T extends FieldMetadataType>(
params: FieldMetadataDecoratorParams<T>,
fieldKey: string,
isNullable: boolean,
isSystem: boolean,
gate: GateDecoratorParams | undefined = undefined,
): ReflectFieldMetadata[string] {
const targetColumnMap = generateTargetColumnMap(params.type, false, fieldKey);
const defaultValue = params.defaultValue ?? generateDefaultValue(params.type);
return {
name: fieldKey,
...params,
targetColumnMap: JSON.stringify(targetColumnMap),
isNullable: params.type === FieldMetadataType.RELATION ? true : isNullable,
isSystem,
isCustom: false,
// TODO: handle options + stringify for the diff.
description: params.description,
icon: params.icon,
defaultValue: defaultValue ? JSON.stringify(defaultValue) : null,
gate,
};
}

View File

@ -0,0 +1,13 @@
import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface';
import { TypedReflect } from 'src/utils/typed-reflect';
export function Gate(metadata: GateDecoratorParams) {
return function (target: object, fieldKey?: string) {
if (fieldKey) {
TypedReflect.defineMetadata('gate', metadata, target, fieldKey);
} else {
TypedReflect.defineMetadata('gate', metadata, target);
}
};
}

View File

@ -0,0 +1,7 @@
import { TypedReflect } from 'src/utils/typed-reflect';
export function IsNullable() {
return function (target: object, fieldKey: string) {
TypedReflect.defineMetadata('isNullable', true, target, fieldKey);
};
}

View File

@ -0,0 +1,11 @@
import { TypedReflect } from 'src/utils/typed-reflect';
export function IsSystem() {
return function (target: object, fieldKey?: string) {
if (fieldKey) {
TypedReflect.defineMetadata('isSystem', true, target, fieldKey);
} else {
TypedReflect.defineMetadata('isSystem', true, target);
}
};
}

View File

@ -1,210 +0,0 @@
import camelCase from 'lodash.camelcase';
import 'reflect-metadata';
import { FieldMetadataDefaultValue } from 'src/metadata/field-metadata/interfaces/field-metadata-default-value.interface';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { generateTargetColumnMap } from 'src/metadata/field-metadata/utils/generate-target-column-map.util';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { generateDefaultValue } from 'src/metadata/field-metadata/utils/generate-default-value';
export interface FieldMetadataDecorator<T extends FieldMetadataType> {
type: T;
label: string;
description?: string | null;
icon?: string | null;
defaultValue?: FieldMetadataDefaultValue<T> | null;
joinColumn?: string;
}
export type ObjectMetadataDecorator = {
namePlural: string;
labelSingular: string;
labelPlural: string;
description?: string | null;
icon?: string | null;
};
export type RelationMetadataDecorator = {
type: RelationMetadataType;
objectName: string;
inverseSideFieldName?: string;
};
export type GateDecorator = {
featureFlag: string;
};
function convertClassNameToObjectMetadataName(name: string): string {
const classSuffix = 'ObjectMetadata';
let objectName = camelCase(name);
if (objectName.endsWith(classSuffix)) {
objectName = objectName.slice(0, -classSuffix.length);
}
return objectName;
}
export function ObjectMetadata(
metadata: ObjectMetadataDecorator,
): ClassDecorator {
return (target) => {
const isSystem = Reflect.getMetadata('isSystem', target) || false;
const gate = Reflect.getMetadata('gate', target) || undefined;
const objectName = convertClassNameToObjectMetadataName(target.name);
Reflect.defineMetadata(
'objectMetadata',
{
nameSingular: objectName,
...metadata,
gate,
targetTableName: objectName,
isSystem,
isCustom: false,
description: metadata.description ?? null,
icon: metadata.icon ?? null,
},
target,
);
};
}
export function IsNullable() {
return function (target: object, fieldKey: string) {
Reflect.defineMetadata('isNullable', true, target, fieldKey);
};
}
export function IsSystem() {
return function (target: object, fieldKey?: string) {
if (fieldKey) {
Reflect.defineMetadata('isSystem', true, target, fieldKey);
} else {
Reflect.defineMetadata('isSystem', true, target);
}
};
}
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>(
metadata: FieldMetadataDecorator<T>,
): PropertyDecorator {
return (target: object, fieldKey: string) => {
const existingFieldMetadata =
Reflect.getMetadata('fieldMetadata', target.constructor) || {};
const isNullable =
Reflect.getMetadata('isNullable', target, fieldKey) || false;
const isSystem = Reflect.getMetadata('isSystem', target, fieldKey) || false;
const gate = Reflect.getMetadata('gate', target, fieldKey) || undefined;
const { joinColumn, ...fieldMetadata } = metadata;
Reflect.defineMetadata(
'fieldMetadata',
{
...existingFieldMetadata,
[fieldKey]: generateFieldMetadata<T>(
fieldMetadata,
fieldKey,
isNullable,
isSystem,
gate,
),
...(joinColumn && fieldMetadata.type === FieldMetadataType.RELATION
? {
[joinColumn]: generateFieldMetadata<FieldMetadataType.UUID>(
{
...fieldMetadata,
type: FieldMetadataType.UUID,
label: `${fieldMetadata.label} id (foreign key)`,
description: `${fieldMetadata.description} id foreign key`,
defaultValue: null,
},
joinColumn,
isNullable,
true,
gate,
),
}
: {}),
},
target.constructor,
);
};
}
function generateFieldMetadata<T extends FieldMetadataType>(
metadata: FieldMetadataDecorator<T>,
fieldKey: string,
isNullable: boolean,
isSystem: boolean,
gate: GateDecorator | undefined = undefined,
) {
const targetColumnMap = JSON.stringify(
generateTargetColumnMap(metadata.type, false, fieldKey),
);
const defaultValue =
metadata.defaultValue ?? generateDefaultValue(metadata.type);
return {
name: fieldKey,
...metadata,
targetColumnMap: targetColumnMap,
isNullable:
metadata.type === FieldMetadataType.RELATION ? true : isNullable,
isSystem,
isCustom: false,
options: null, // TODO: handle options + stringify for the diff.
description: metadata.description ?? null,
icon: metadata.icon ?? null,
defaultValue: defaultValue ? JSON.stringify(defaultValue) : null,
gate,
};
}
export function RelationMetadata(
metadata: RelationMetadataDecorator,
): PropertyDecorator {
return (target: object, fieldKey: string) => {
const existingRelationMetadata =
Reflect.getMetadata('relationMetadata', target.constructor) || [];
const gate = Reflect.getMetadata('gate', target, fieldKey) || undefined;
const objectName = convertClassNameToObjectMetadataName(
target.constructor.name,
);
Reflect.defineMetadata(
'relationMetadata',
[
...existingRelationMetadata,
{
type: metadata.type,
fromObjectNameSingular: objectName,
toObjectNameSingular: metadata.objectName,
fromFieldMetadataName: fieldKey,
toFieldMetadataName: metadata.inverseSideFieldName ?? objectName,
gate,
},
],
target.constructor,
);
};
}

View File

@ -0,0 +1,29 @@
import { ObjectMetadataDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-object-metadata.interface';
import { TypedReflect } from 'src/utils/typed-reflect';
import { convertClassNameToObjectMetadataName } from 'src/workspace/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
export function ObjectMetadata(
params: ObjectMetadataDecoratorParams,
): ClassDecorator {
return (target) => {
const isSystem = TypedReflect.getMetadata('isSystem', target) ?? false;
const gate = TypedReflect.getMetadata('gate', target);
const objectName = convertClassNameToObjectMetadataName(target.name);
TypedReflect.defineMetadata(
'objectMetadata',
{
nameSingular: objectName,
...params,
targetTableName: objectName,
isSystem,
isCustom: false,
description: params.description,
icon: params.icon,
gate,
},
target,
);
};
}

View File

@ -0,0 +1,35 @@
import 'reflect-metadata';
import { RelationMetadataDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-relation-metadata.interface';
import { TypedReflect } from 'src/utils/typed-reflect';
import { convertClassNameToObjectMetadataName } from 'src/workspace/workspace-sync-metadata/utils/convert-class-to-object-metadata-name.util';
export function RelationMetadata(
params: RelationMetadataDecoratorParams,
): PropertyDecorator {
return (target: object, fieldKey: string) => {
const existingRelationMetadata =
TypedReflect.getMetadata('relationMetadata', target.constructor) ?? [];
const gate = TypedReflect.getMetadata('gate', target, fieldKey);
const objectName = convertClassNameToObjectMetadataName(
target.constructor.name,
);
Reflect.defineMetadata(
'relationMetadata',
[
...existingRelationMetadata,
{
type: params.type,
fromObjectNameSingular: objectName,
toObjectNameSingular: params.objectName,
fromFieldMetadataName: fieldKey,
toFieldMetadataName: params.inverseSideFieldName ?? objectName,
gate,
},
],
target.constructor,
);
};
}

View File

@ -0,0 +1,3 @@
export interface GateDecoratorParams {
featureFlag: string;
}

View File

@ -0,0 +1,19 @@
import { PartialFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
import { PartialObjectMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
export type MappedFieldMetadata = Record<string, PartialFieldMetadata>;
export interface MappedObjectMetadata
extends Omit<PartialObjectMetadata, 'fields'> {
fields: MappedFieldMetadata;
}
export type MappedFieldMetadataEntity = Record<string, FieldMetadataEntity>;
export interface MappedObjectMetadataEntity
extends Omit<ObjectMetadataEntity, 'fields'> {
fields: MappedFieldMetadataEntity;
}

View File

@ -0,0 +1,6 @@
import { ReflectFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-field-metadata.interface';
export type PartialFieldMetadata = ReflectFieldMetadata[string] & {
workspaceId: string;
objectMetadataId?: string;
};

View File

@ -0,0 +1,8 @@
import { PartialFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
import { ReflectObjectMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/reflect-object-metadata.interface';
export type PartialObjectMetadata = ReflectObjectMetadata & {
workspaceId: string;
dataSourceId: string;
fields: PartialFieldMetadata[];
};

View File

@ -0,0 +1,32 @@
import { FieldMetadataDefaultValue } from 'src/metadata/field-metadata/interfaces/field-metadata-default-value.interface';
import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
export interface FieldMetadataDecoratorParams<
T extends FieldMetadataType | 'default',
> {
type: T;
label: string;
description?: string;
icon?: string;
defaultValue?: FieldMetadataDefaultValue<T>;
joinColumn?: string;
}
export interface ReflectFieldMetadata {
[key: string]: Omit<
FieldMetadataDecoratorParams<'default'>,
'defaultValue' | 'type'
> & {
name: string;
type: FieldMetadataType;
targetColumnMap: string;
isNullable: boolean;
isSystem: boolean;
isCustom: boolean;
description?: string;
defaultValue: string | null;
gate?: GateDecoratorParams;
};
}

View File

@ -0,0 +1,17 @@
import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface';
export interface ObjectMetadataDecoratorParams {
namePlural: string;
labelSingular: string;
labelPlural: string;
description?: string;
icon?: string;
}
export interface ReflectObjectMetadata extends ObjectMetadataDecoratorParams {
nameSingular: string;
targetTableName: string;
isSystem: boolean;
isCustom: boolean;
gate?: GateDecoratorParams;
}

View File

@ -0,0 +1,18 @@
import { GateDecoratorParams } from 'src/workspace/workspace-sync-metadata/interfaces/gate-decorator.interface';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
export interface RelationMetadataDecoratorParams {
type: RelationMetadataType;
objectName: string;
inverseSideFieldName?: string;
}
export interface ReflectRelationMetadata {
type: RelationMetadataType;
fromObjectNameSingular: string;
toObjectNameSingular: string;
fromFieldMetadataName: string;
toFieldMetadataName: string;
gate?: GateDecoratorParams;
}

View File

@ -1,18 +1,25 @@
import { Injectable } from '@nestjs/common';
import assert from 'assert';
import { FieldMetadataEntity } from 'src/metadata/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { PartialObjectMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import { MappedObjectMetadataEntity } from 'src/workspace/workspace-sync-metadata/interfaces/mapped-metadata.interface';
export class MetadataParser {
static parseMetadata(
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { TypedReflect } from 'src/utils/typed-reflect';
import { isGatedAndNotEnabled } from 'src/workspace/workspace-sync-metadata/utils/is-gate-and-not-enabled.util';
@Injectable()
export class ReflectiveMetadataFactory {
async createObjectMetadata(
metadata: typeof BaseObjectMetadata,
workspaceId: string,
dataSourceId: string,
defaultDataSourceId: string,
workspaceFeatureFlagsMap: Record<string, boolean>,
): ObjectMetadataEntity | undefined {
const objectMetadata = Reflect.getMetadata('objectMetadata', metadata);
const fieldMetadata = Reflect.getMetadata('fieldMetadata', metadata);
): Promise<PartialObjectMetadata | undefined> {
const objectMetadata = TypedReflect.getMetadata('objectMetadata', metadata);
const fieldMetadata =
TypedReflect.getMetadata('fieldMetadata', metadata) ?? {};
if (!objectMetadata) {
throw new Error(
@ -31,47 +38,48 @@ export class MetadataParser {
return {
...objectMetadata,
workspaceId,
dataSourceId,
fields: fields.map((field: FieldMetadataEntity) => ({
dataSourceId: defaultDataSourceId,
fields: fields.map((field) => ({
...field,
workspaceId,
isSystem: objectMetadata.isSystem || field.isSystem,
defaultValue: field.defaultValue || null,
options: field.options || null,
defaultValue: field.defaultValue,
})),
};
}
static parseAllMetadata(
async createObjectMetadataCollection(
metadataCollection: (typeof BaseObjectMetadata)[],
workspaceId: string,
dataSourceId: string,
workspaceFeatureFlagsMap: Record<string, boolean>,
): ObjectMetadataEntity[] {
return metadataCollection
.map((metadata) =>
MetadataParser.parseMetadata(
metadata,
workspaceId,
dataSourceId,
workspaceFeatureFlagsMap,
),
)
.filter(
(metadata): metadata is ObjectMetadataEntity => metadata !== undefined,
);
) {
const metadataPromises = metadataCollection.map((metadata) =>
this.createObjectMetadata(
metadata,
workspaceId,
dataSourceId,
workspaceFeatureFlagsMap,
),
);
const resolvedMetadata = await Promise.all(metadataPromises);
return resolvedMetadata.filter(
(metadata): metadata is PartialObjectMetadata => !!metadata,
);
}
static parseRelationMetadata(
createRelationMetadata(
metadata: typeof BaseObjectMetadata,
workspaceId: string,
objectMetadataFromDB: Record<string, ObjectMetadataEntity>,
objectMetadataFromDB: Record<string, MappedObjectMetadataEntity>,
workspaceFeatureFlagsMap: Record<string, boolean>,
) {
const objectMetadata = Reflect.getMetadata('objectMetadata', metadata);
const relationMetadata = Reflect.getMetadata('relationMetadata', metadata);
if (!relationMetadata) return [];
const objectMetadata = TypedReflect.getMetadata('objectMetadata', metadata);
const relationMetadata = TypedReflect.getMetadata(
'relationMetadata',
metadata,
);
if (!objectMetadata) {
throw new Error(
@ -79,7 +87,10 @@ export class MetadataParser {
);
}
if (isGatedAndNotEnabled(objectMetadata, workspaceFeatureFlagsMap)) {
if (
!relationMetadata ||
isGatedAndNotEnabled(objectMetadata, workspaceFeatureFlagsMap)
) {
return [];
}
@ -94,7 +105,7 @@ export class MetadataParser {
assert(
fromObjectMetadata,
`Object ${relation.fromObjectNameSingular} not found in DB
for fromRelation defined in class ${objectMetadata.nameSingular}`,
for relation FROM defined in class ${objectMetadata.nameSingular}`,
);
const toObjectMetadata =
@ -103,7 +114,7 @@ export class MetadataParser {
assert(
toObjectMetadata,
`Object ${relation.toObjectNameSingular} not found in DB
for toRelation defined in class ${objectMetadata.nameSingular}`,
for relation TO defined in class ${objectMetadata.nameSingular}`,
);
const fromFieldMetadata =
@ -112,7 +123,7 @@ export class MetadataParser {
assert(
fromFieldMetadata,
`Field ${relation.fromFieldMetadataName} not found in object ${relation.fromObjectNameSingular}
for fromRelation defined in class ${objectMetadata.nameSingular}`,
for relation FROM defined in class ${objectMetadata.nameSingular}`,
);
const toFieldMetadata =
@ -121,7 +132,7 @@ export class MetadataParser {
assert(
toFieldMetadata,
`Field ${relation.toFieldMetadataName} not found in object ${relation.toObjectNameSingular}
for toRelation defined in class ${objectMetadata.nameSingular}`,
for relation TO defined in class ${objectMetadata.nameSingular}`,
);
return {
@ -135,14 +146,14 @@ export class MetadataParser {
});
}
static parseAllRelations(
createRelationMetadataCollection(
metadataCollection: (typeof BaseObjectMetadata)[],
workspaceId: string,
objectMetadataFromDB: Record<string, ObjectMetadataEntity>,
objectMetadataFromDB: Record<string, MappedObjectMetadataEntity>,
workspaceFeatureFlagsMap: Record<string, boolean>,
) {
return metadataCollection.flatMap((metadata) =>
MetadataParser.parseRelationMetadata(
this.createRelationMetadata(
metadata,
workspaceId,
objectMetadataFromDB,
@ -151,14 +162,3 @@ export class MetadataParser {
);
}
}
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,10 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
FieldMetadata,
IsSystem,
IsNullable,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { ActivityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata';

View File

@ -1,12 +1,10 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
IsSystem,
IsNullable,
FieldMetadata,
RelationMetadata,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';

View File

@ -1,10 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
FieldMetadata,
IsNullable,
IsSystem,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
@ObjectMetadata({

View File

@ -1,10 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { ActivityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata';

View File

@ -1,15 +1,11 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
FieldMetadata,
IsSystem,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
export abstract class BaseObjectMetadata {
@FieldMetadata({
type: FieldMetadataType.UUID,
label: 'Id',
icon: null,
description: null,
defaultValue: { type: 'uuid' },
})
@IsSystem()
@ -18,7 +14,6 @@ export abstract class BaseObjectMetadata {
@FieldMetadata({
type: FieldMetadataType.DATE_TIME,
label: 'Creation date',
description: null,
icon: 'IconCalendar',
defaultValue: { type: 'now' },
})
@ -27,7 +22,6 @@ export abstract class BaseObjectMetadata {
@FieldMetadata({
type: FieldMetadataType.DATE_TIME,
label: 'Update date',
description: null,
icon: 'IconCalendar',
defaultValue: { type: 'now' },
})

View File

@ -1,9 +1,7 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { ActivityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/activity.object-metadata';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata';

View File

@ -2,12 +2,10 @@ import { CurrencyMetadata } from 'src/metadata/field-metadata/composite-types/cu
import { LinkMetadata } from 'src/metadata/field-metadata/composite-types/link.composite-type';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
FieldMetadata,
IsNullable,
RelationMetadata,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';

View File

@ -1,13 +1,11 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata';

View File

@ -1,10 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata';
import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata';

View File

@ -1,13 +1,11 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';

View File

@ -1,11 +1,9 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
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 { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata';

View File

@ -1,13 +1,11 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata';

View File

@ -1,13 +1,11 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';

View File

@ -1,10 +1,8 @@
import { CurrencyMetadata } from 'src/metadata/field-metadata/composite-types/currency.composite-type';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
FieldMetadata,
IsNullable,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata';
import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata';

View File

@ -2,14 +2,12 @@ import { FullNameMetadata } from 'src/metadata/field-metadata/composite-types/fu
import { LinkMetadata } from 'src/metadata/field-metadata/composite-types/link.composite-type';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
FieldMetadata,
IsNullable,
RelationMetadata,
IsSystem,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';

View File

@ -1,12 +1,10 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
FieldMetadata,
IsNullable,
IsSystem,
RelationMetadata,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { OpportunityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata';

View File

@ -1,10 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { ViewObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata';

View File

@ -1,10 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { ViewObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata';
@ -21,7 +19,6 @@ export class ViewFilterObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.UUID,
label: 'Field Metadata Id',
description: 'View Filter target field',
icon: null,
})
fieldMetadataId: string;
@ -29,7 +26,6 @@ export class ViewFilterObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.TEXT,
label: 'Operand',
description: 'View Filter operand',
icon: null,
defaultValue: { value: 'Contains' },
})
operand: string;
@ -38,7 +34,6 @@ export class ViewFilterObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.TEXT,
label: 'Value',
description: 'View Filter value',
icon: null,
defaultValue: { value: '' },
})
value: string;
@ -47,7 +42,6 @@ export class ViewFilterObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.TEXT,
label: 'Display Value',
description: 'View Filter Display Value',
icon: null,
defaultValue: { value: '' },
})
displayValue: string;

View File

@ -1,10 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
FieldMetadata,
IsNullable,
IsSystem,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { ViewObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata';
@ -29,7 +27,6 @@ export class ViewSortObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.TEXT,
label: 'Direction',
description: 'View Sort direction',
icon: null,
defaultValue: { value: 'asc' },
})
direction: string;

View File

@ -1,12 +1,10 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
RelationMetadata,
IsNullable,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
import { ViewFieldObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view-field.object-metadata';
import { ViewFilterObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view-filter.object-metadata';
@ -25,7 +23,6 @@ export class ViewObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.TEXT,
label: 'Name',
description: 'View name',
icon: null,
defaultValue: { value: '' },
})
name: string;
@ -34,7 +31,6 @@ export class ViewObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.UUID,
label: 'Object Metadata Id',
description: 'View target object',
icon: null,
})
objectMetadataId: string;
@ -42,7 +38,6 @@ export class ViewObjectMetadata extends BaseObjectMetadata {
type: FieldMetadataType.TEXT,
label: 'Type',
description: 'View type',
icon: null,
defaultValue: { value: 'table' },
})
type: string;

View File

@ -1,9 +1,7 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
@ObjectMetadata({

View File

@ -1,14 +1,12 @@
import { FullNameMetadata } from 'src/metadata/field-metadata/composite-types/full-name.composite-type';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
ObjectMetadata,
IsSystem,
FieldMetadata,
IsNullable,
RelationMetadata,
Gate,
} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator';
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 { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
@ -61,6 +59,14 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
})
avatarUrl: string;
@FieldMetadata({
type: FieldMetadataType.TEXT,
label: 'User Email',
description: 'Related user email address',
icon: 'IconMail',
})
userEmail: string;
@FieldMetadata({
type: FieldMetadataType.UUID,
label: 'User Id',

View File

@ -0,0 +1,12 @@
import { camelCase } from 'src/utils/camel-case';
export const convertClassNameToObjectMetadataName = (name: string): string => {
const classSuffix = 'ObjectMetadata';
let objectName = camelCase(name);
if (objectName.endsWith(classSuffix)) {
objectName = objectName.slice(0, -classSuffix.length);
}
return objectName;
};

View File

@ -0,0 +1,10 @@
export const 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,4 +1,6 @@
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { FieldMetadataDefaultValue } from 'src/metadata/field-metadata/interfaces/field-metadata-default-value.interface';
import { FieldMetadataOptions } from 'src/metadata/field-metadata/interfaces/field-metadata-options.interface';
import { FieldMetadataTargetColumnMap } from 'src/metadata/field-metadata/interfaces/field-metadata-target-column-map.interface';
/**
* This utility function filters out properties from an object based on a list of properties to ignore.
@ -28,9 +30,12 @@ export const filterIgnoredProperties = (
* @param arr - The array of ObjectMetadataEntity objects to convert.
* @returns A map of object metadata, with nameSingular as the key and the object as the value.
*/
export const mapObjectMetadataByUniqueIdentifier = (
arr: ObjectMetadataEntity[],
) => {
export const mapObjectMetadataByUniqueIdentifier = <
T extends { nameSingular: string; fields: U[] },
U extends { name: string },
>(
arr: T[],
): Record<string, Omit<T, 'fields'> & { fields: Record<string, U> }> => {
return arr.reduce((acc, curr) => {
acc[curr.nameSingular] = {
...curr,
@ -38,29 +43,41 @@ export const mapObjectMetadataByUniqueIdentifier = (
acc[curr.name] = curr;
return acc;
}, {}),
}, {} as Record<string, U>),
};
return acc;
}, {});
}, {} as Record<string, Omit<T, 'fields'> & { fields: Record<string, U> }>);
};
export const convertStringifiedFieldsToJSON = (fieldMetadata) => {
export const convertStringifiedFieldsToJSON = <
T extends {
targetColumnMap?: string | null;
defaultValue?: string | null;
options?: string | null;
},
>(
fieldMetadata: T,
): T & {
targetColumnMap?: FieldMetadataTargetColumnMap;
defaultValue?: FieldMetadataDefaultValue;
options?: FieldMetadataOptions;
} => {
if (fieldMetadata.targetColumnMap) {
fieldMetadata.targetColumnMap = JSON.parse(
fieldMetadata.targetColumnMap as unknown as string,
);
}
if (fieldMetadata.defaultValue) {
fieldMetadata.defaultValue = JSON.parse(
fieldMetadata.defaultValue as unknown as string,
);
}
if (fieldMetadata.options) {
fieldMetadata.options = JSON.parse(
fieldMetadata.options as unknown as string,
);
fieldMetadata.targetColumnMap = JSON.parse(fieldMetadata.targetColumnMap);
}
return fieldMetadata;
if (fieldMetadata.defaultValue) {
fieldMetadata.defaultValue = JSON.parse(fieldMetadata.defaultValue);
}
if (fieldMetadata.options) {
fieldMetadata.options = JSON.parse(fieldMetadata.options);
}
return fieldMetadata as T & {
targetColumnMap?: FieldMetadataTargetColumnMap;
defaultValue?: FieldMetadataDefaultValue;
options?: FieldMetadataOptions;
};
};

View File

@ -8,6 +8,7 @@ import { RelationMetadataEntity } from 'src/metadata/relation-metadata/relation-
import { WorkspaceMigrationEntity } from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationModule } from 'src/metadata/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.module';
import { ReflectiveMetadataFactory } from 'src/workspace/workspace-sync-metadata/reflective-metadata.factory';
import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metadata/workspace-sync.metadata.service';
@Module({
@ -25,7 +26,7 @@ import { WorkspaceSyncMetadataService } from 'src/workspace/workspace-sync-metad
),
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
],
providers: [WorkspaceSyncMetadataService, ReflectiveMetadataFactory],
exports: [WorkspaceSyncMetadataService],
providers: [WorkspaceSyncMetadataService],
})
export class WorkspaceSyncMetadataModule {}

View File

@ -2,9 +2,16 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import diff from 'microdiff';
import { Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import camelCase from 'lodash.camelcase';
import { PartialFieldMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-field-metadata.interface';
import { PartialObjectMetadata } from 'src/workspace/workspace-sync-metadata/interfaces/partial-object-metadata.interface';
import {
MappedFieldMetadataEntity,
MappedObjectMetadata,
} from 'src/workspace/workspace-sync-metadata/interfaces/mapped-metadata.interface';
import {
FieldMetadataEntity,
FieldMetadataType,
@ -14,7 +21,6 @@ import {
RelationMetadataEntity,
RelationMetadataType,
} from 'src/metadata/relation-metadata/relation-metadata.entity';
import { MetadataParser } from 'src/workspace/workspace-sync-metadata/utils/metadata.parser';
import {
filterIgnoredProperties,
convertStringifiedFieldsToJSON,
@ -29,6 +35,7 @@ import {
} from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/metadata/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationRunnerService } from 'src/workspace/workspace-migration-runner/workspace-migration-runner.service';
import { ReflectiveMetadataFactory } from 'src/workspace/workspace-sync-metadata/reflective-metadata.factory';
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
@Injectable()
@ -36,6 +43,7 @@ export class WorkspaceSyncMetadataService {
constructor(
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly reflectiveMetadataFactory: ReflectiveMetadataFactory,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@ -75,32 +83,37 @@ export class WorkspaceSyncMetadataService {
{},
);
const standardObjects = MetadataParser.parseAllMetadata(
standardObjectMetadata,
workspaceId,
dataSourceId,
workspaceFeatureFlagsMap,
);
const standardObjects =
await this.reflectiveMetadataFactory.createObjectMetadataCollection(
standardObjectMetadata,
workspaceId,
dataSourceId,
workspaceFeatureFlagsMap,
);
const objectsInDB = await this.objectMetadataRepository.find({
where: { workspaceId, dataSourceId, isCustom: false },
relations: ['fields'],
where: { workspaceId, isCustom: false },
relations: ['dataSource', 'fields'],
});
const objectsInDBByName =
mapObjectMetadataByUniqueIdentifier(objectsInDB);
const standardObjectsByName =
mapObjectMetadataByUniqueIdentifier(standardObjects);
const objectsInDBByName = mapObjectMetadataByUniqueIdentifier<
ObjectMetadataEntity,
FieldMetadataEntity
>(objectsInDB);
const standardObjectsByName = mapObjectMetadataByUniqueIdentifier<
PartialObjectMetadata,
PartialFieldMetadata
>(standardObjects);
const objectsToCreate: ObjectMetadataEntity[] = [];
const objectsToCreate: MappedObjectMetadata[] = [];
const objectsToDelete = objectsInDB.filter(
(objectInDB) => !standardObjectsByName[objectInDB.nameSingular],
);
const objectsToUpdate: Record<string, ObjectMetadataEntity> = {};
const fieldsToCreate: FieldMetadataEntity[] = [];
const fieldsToCreate: PartialFieldMetadata[] = [];
const fieldsToDelete: FieldMetadataEntity[] = [];
const fieldsToUpdate: Record<string, FieldMetadataEntity> = {};
const fieldsToUpdate: Record<string, MappedFieldMetadataEntity> = {};
for (const standardObjectName in standardObjectsByName) {
const standardObject = standardObjectsByName[standardObjectName];
@ -187,13 +200,15 @@ export class WorkspaceSyncMetadataService {
for (const diff of fieldsDiff) {
const fieldName = diff.path[0];
if (diff.type === 'CREATE')
if (diff.type === 'CREATE') {
fieldsToCreate.push({
...standardObjectFields[fieldName],
objectMetadataId: objectInDB.id,
});
if (diff.type === 'REMOVE' && diff.path.length === 1)
}
if (diff.type === 'REMOVE' && diff.path.length === 1) {
fieldsToDelete.push(objectInDBFields[fieldName]);
}
if (diff.type === 'CHANGE') {
const property = diff.path[diff.path.length - 1];
@ -206,16 +221,25 @@ export class WorkspaceSyncMetadataService {
}
// CREATE OBJECTS
await this.objectMetadataRepository.save(
objectsToCreate.map((object) => ({
...object,
isActive: true,
fields: Object.values(object.fields).map((field) => ({
...convertStringifiedFieldsToJSON(field),
const createdObjectMetadataCollection =
await this.objectMetadataRepository.save(
objectsToCreate.map((object) => ({
...object,
isActive: true,
fields: Object.values(object.fields).map((field) => ({
...convertStringifiedFieldsToJSON(field),
isActive: true,
})),
})),
})),
);
const identifiers = createdObjectMetadataCollection.map(
(object) => object.id,
);
const createdObjects = await this.objectMetadataRepository.find({
where: { id: In(identifiers) },
relations: ['dataSource', 'fields'],
});
// UPDATE OBJECTS, this is not optimal as we are running n queries here.
for (const [key, value] of Object.entries(objectsToUpdate)) {
await this.objectMetadataRepository.update(key, value);
@ -228,9 +252,10 @@ export class WorkspaceSyncMetadataService {
}
// CREATE FIELDS
await this.fieldMetadataRepository.save(
const createdFields = await this.fieldMetadataRepository.save(
fieldsToCreate.map((field) => convertStringifiedFieldsToJSON(field)),
);
// UPDATE FIELDS
for (const [key, value] of Object.entries(fieldsToUpdate)) {
await this.fieldMetadataRepository.update(
@ -252,9 +277,9 @@ export class WorkspaceSyncMetadataService {
// Generate migrations
await this.generateMigrationsFromSync(
objectsToCreate,
createdObjects,
objectsToDelete,
fieldsToCreate,
createdFields,
fieldsToDelete,
objectsInDB,
);
@ -282,16 +307,20 @@ export class WorkspaceSyncMetadataService {
workspaceFeatureFlagsMap: Record<string, boolean>,
) {
const objectsInDB = await this.objectMetadataRepository.find({
where: { workspaceId, dataSourceId, isCustom: false },
relations: ['fields'],
where: { workspaceId, isCustom: false },
relations: ['dataSource', 'fields'],
});
const objectsInDBByName = mapObjectMetadataByUniqueIdentifier(objectsInDB);
const standardRelations = MetadataParser.parseAllRelations(
standardObjectMetadata,
workspaceId,
objectsInDBByName,
workspaceFeatureFlagsMap,
);
const objectsInDBByName = mapObjectMetadataByUniqueIdentifier<
ObjectMetadataEntity,
FieldMetadataEntity
>(objectsInDB);
const standardRelations =
this.reflectiveMetadataFactory.createRelationMetadataCollection(
standardObjectMetadata,
workspaceId,
objectsInDBByName,
workspaceFeatureFlagsMap,
);
// TODO: filter out custom relations once isCustom has been added to relationMetadata table
const relationsInDB = await this.relationMetadataRepository.find({
@ -364,6 +393,7 @@ export class WorkspaceSyncMetadataService {
{
name: object.targetTableName,
action: 'create',
schemaName: object.dataSource.schema,
} satisfies WorkspaceMigrationTableAction,
...Object.values(object.fields)
.filter((field) => field.type !== FieldMetadataType.RELATION)
@ -372,6 +402,7 @@ export class WorkspaceSyncMetadataService {
({
name: object.targetTableName,
action: 'alter',
schemaName: object.dataSource.schema,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
field,
@ -395,7 +426,7 @@ export class WorkspaceSyncMetadataService {
result[currentObject.id] = currentObject;
return result;
}, {});
}, {} as Record<string, ObjectMetadataEntity>);
if (fieldsToCreate.length > 0) {
fieldsToCreate.map((field) => {
@ -403,6 +434,8 @@ export class WorkspaceSyncMetadataService {
{
name: objectsInDbById[field.objectMetadataId].targetTableName,
action: 'alter',
schemaName:
objectsInDbById[field.objectMetadataId].dataSource.schema,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
field,
@ -424,6 +457,8 @@ export class WorkspaceSyncMetadataService {
{
name: objectsInDbById[field.objectMetadataId].targetTableName,
action: 'alter',
schemaName:
objectsInDbById[field.objectMetadataId].dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.DROP,
@ -489,11 +524,13 @@ export class WorkspaceSyncMetadataService {
{
name: toObjectMetadata.targetTableName,
action: 'alter',
schemaName: toObjectMetadata.dataSource.schema,
columns: [
{
action: WorkspaceMigrationColumnActionType.RELATION,
columnName: `${camelCase(toFieldMetadata.name)}Id`,
referencedTableName: fromObjectMetadata.targetTableName,
referencedSchema: fromObjectMetadata.dataSource.schema,
referencedTableColumnName: 'id',
isUnique:
relation.relationType === RelationMetadataType.ONE_TO_ONE,