Fix 'name' column wrongly added in standard objects (#7428)

## Context
Name shouldn't be added to all tables, especially standard objects
because they already have their own labelIdentifierFieldMetadata
specified in the workspace-entity schema. This PR removes this column
from the "base" list of columns to add when creating a new object/table
and moves it to the object-metadata service that is, as of today, only
used for custom objects. Also had to modify the migration-runner to
handle column creation in a table creation migration (this was available
in the migration definition already but was not doing anything)

This also fixes an issue in standard objects that already have a "name"
field defined with a different field type, this is even more important
when the said field is a composite field. For example people already has
a FULL_NAME name field which clashes with the default TEXT name field
meaning it was only creating 1 field metadata for 'name' but 3 columns
were created: `name, nameFirstName, nameLastName`. This inconsistency
with metadata (which is our source of truth everywhere) brought some
issues (lately, converting back typeorm response to gql (including
composition) was broken).
This commit is contained in:
Weiko
2024-10-04 18:31:19 +02:00
committed by GitHub
parent ebe28def02
commit 2f223f3294
10 changed files with 74 additions and 72 deletions

View File

@ -66,8 +66,7 @@ export class GraphqlQueryUpdateManyResolverService
const data = formatData(args.data, objectMetadataMapItem); const data = formatData(args.data, objectMetadataMapItem);
const result = await withFilterQueryBuilder const result = await withFilterQueryBuilder
.update() .update(data)
.set(data)
.returning('*') .returning('*')
.execute(); .execute();

View File

@ -61,13 +61,11 @@ export class GraphqlQueryUpdateOneResolverService
objectMetadataMapItem.nameSingular, objectMetadataMapItem.nameSingular,
); );
const withFilterQueryBuilder = queryBuilder.where({ id: args.id });
const data = formatData(args.data, objectMetadataMapItem); const data = formatData(args.data, objectMetadataMapItem);
const result = await withFilterQueryBuilder const result = await queryBuilder
.update() .update(data)
.set(data) .where({ id: args.id })
.returning('*') .returning('*')
.execute(); .execute();

View File

@ -49,8 +49,10 @@ import { generateMigrationName } from 'src/engine/metadata-modules/workspace-mig
import { import {
WorkspaceMigrationColumnActionType, WorkspaceMigrationColumnActionType,
WorkspaceMigrationColumnDrop, WorkspaceMigrationColumnDrop,
WorkspaceMigrationTableAction,
WorkspaceMigrationTableActionType, WorkspaceMigrationTableActionType,
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service'; import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
@ -103,6 +105,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager, private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspaceMigrationFactory: WorkspaceMigrationFactory,
) { ) {
super(objectMetadataRepository); super(objectMetadataRepository);
} }
@ -563,9 +566,39 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
objectMetadataInput.primaryKeyFieldMetadataSettings, objectMetadataInput.primaryKeyFieldMetadataSettings,
); );
return this.workspaceMigrationService.createCustomMigration( await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`), generateMigrationName(`create-${createdObjectMetadata.nameSingular}`),
createdObjectMetadata.workspaceId, createdObjectMetadata.workspaceId,
[
{
name: computeObjectTargetTable(createdObjectMetadata),
action: WorkspaceMigrationTableActionType.CREATE,
} satisfies WorkspaceMigrationTableAction,
],
);
for (const fieldMetadata of createdObjectMetadata.fields) {
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`create-${fieldMetadata.name}`),
createdObjectMetadata.workspaceId,
[
{
name: computeObjectTargetTable(createdObjectMetadata),
action: WorkspaceMigrationTableActionType.ALTER,
columns: this.workspaceMigrationFactory.createColumnActions(
WorkspaceMigrationColumnActionType.CREATE,
fieldMetadata,
),
},
] satisfies WorkspaceMigrationTableAction[],
);
}
await this.workspaceMigrationService.createCustomMigration(
generateMigrationName(
`create-${createdObjectMetadata.nameSingular}-relations`,
),
createdObjectMetadata.workspaceId,
buildMigrationsForCustomObjectRelations( buildMigrationsForCustomObjectRelations(
createdObjectMetadata, createdObjectMetadata,
activityTargetObjectMetadata, activityTargetObjectMetadata,
@ -611,7 +644,9 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
} }
this.workspaceMigrationService.createCustomMigration( this.workspaceMigrationService.createCustomMigration(
generateMigrationName(`create-${createdObjectMetadata.nameSingular}`), generateMigrationName(
`update-${createdObjectMetadata.nameSingular}-add-searchVector`,
),
createdObjectMetadata.workspaceId, createdObjectMetadata.workspaceId,
[ [
{ {

View File

@ -18,10 +18,6 @@ export const buildMigrationsForCustomObjectRelations = (
noteTargetObjectMetadata: ObjectMetadataEntity, noteTargetObjectMetadata: ObjectMetadataEntity,
taskTargetObjectMetadata: ObjectMetadataEntity, taskTargetObjectMetadata: ObjectMetadataEntity,
): WorkspaceMigrationTableAction[] => [ ): WorkspaceMigrationTableAction[] => [
{
name: computeObjectTargetTable(createdObjectMetadata),
action: WorkspaceMigrationTableActionType.CREATE,
} satisfies WorkspaceMigrationTableAction,
// Add activity target relation // Add activity target relation
{ {
name: computeObjectTargetTable(activityTargetObjectMetadata), name: computeObjectTargetTable(activityTargetObjectMetadata),

View File

@ -55,5 +55,5 @@ export abstract class BaseWorkspaceEntity {
}, },
}) })
@WorkspaceIsNullable() @WorkspaceIsNullable()
deletedAt?: string | null; deletedAt: string | null;
} }

View File

@ -1,55 +0,0 @@
import { TableColumnOptions } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
export const customTableDefaultColumns = (
tableName: string,
): TableColumnOptions[] => [
{
name: 'id',
type: 'uuid',
isPrimary: true,
default: 'public.uuid_generate_v4()',
},
{
name: 'createdAt',
type: 'timestamptz',
default: 'now()',
},
{
name: 'updatedAt',
type: 'timestamptz',
default: 'now()',
},
{
name: 'deletedAt',
type: 'timestamptz',
isNullable: true,
},
{
name: 'position',
type: 'float',
isNullable: true,
},
{
name: 'name',
type: 'text',
isNullable: false,
default: "'Untitled'",
},
{
name: 'createdBySource',
type: 'enum',
enumName: `${tableName}_createdBySource_enum`,
enum: Object.values(FieldActorSource),
isNullable: false,
default: `'${FieldActorSource.MANUAL}'`,
},
{ name: 'createdByWorkspaceMemberId', type: 'uuid', isNullable: true },
{
name: 'createdByName',
type: 'text',
isNullable: false,
default: "''",
},
];

View File

@ -0,0 +1,10 @@
import { TableColumnOptions } from 'typeorm';
export const tableDefaultColumns = (): TableColumnOptions[] => [
{
name: 'id',
type: 'uuid',
isPrimary: true,
default: 'public.uuid_generate_v4()',
},
];

View File

@ -26,10 +26,10 @@ import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service'; import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service';
import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util'; import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util';
import { tableDefaultColumns } from 'src/engine/workspace-manager/workspace-migration-runner/utils/table-default-column.util';
import { isDefined } from 'src/utils/is-defined'; import { isDefined } from 'src/utils/is-defined';
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service'; import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
@Injectable() @Injectable()
export class WorkspaceMigrationRunnerService { export class WorkspaceMigrationRunnerService {
@ -121,7 +121,12 @@ export class WorkspaceMigrationRunnerService {
) { ) {
switch (tableMigration.action) { switch (tableMigration.action) {
case WorkspaceMigrationTableActionType.CREATE: case WorkspaceMigrationTableActionType.CREATE:
await this.createTable(queryRunner, schemaName, tableMigration.name); await this.createTable(
queryRunner,
schemaName,
tableMigration.name,
tableMigration.columns,
);
break; break;
case WorkspaceMigrationTableActionType.ALTER: { case WorkspaceMigrationTableActionType.ALTER: {
if (tableMigration.newName) { if (tableMigration.newName) {
@ -244,16 +249,26 @@ export class WorkspaceMigrationRunnerService {
queryRunner: QueryRunner, queryRunner: QueryRunner,
schemaName: string, schemaName: string,
tableName: string, tableName: string,
columns?: WorkspaceMigrationColumnAction[],
) { ) {
await queryRunner.createTable( await queryRunner.createTable(
new Table({ new Table({
name: tableName, name: tableName,
schema: schemaName, schema: schemaName,
columns: customTableDefaultColumns(tableName), columns: tableDefaultColumns(),
}), }),
true, true,
); );
if (columns && columns.length > 0) {
await this.handleColumnChanges(
queryRunner,
schemaName,
tableName,
columns,
);
}
// Enable totalCount for the table // Enable totalCount for the table
await queryRunner.query(` await queryRunner.query(`
COMMENT ON TABLE "${schemaName}"."${tableName}" IS '@graphql({"totalCount": {"enabled": true}})'; COMMENT ON TABLE "${schemaName}"."${tableName}" IS '@graphql({"totalCount": {"enabled": true}})';

View File

@ -5,6 +5,7 @@ export type CalendarEvent = Omit<
CalendarEventWorkspaceEntity, CalendarEventWorkspaceEntity,
| 'createdAt' | 'createdAt'
| 'updatedAt' | 'updatedAt'
| 'deletedAt'
| 'calendarChannelEventAssociations' | 'calendarChannelEventAssociations'
| 'calendarEventParticipants' | 'calendarEventParticipants'
| 'conferenceLink' | 'conferenceLink'
@ -19,6 +20,7 @@ export type CalendarEventParticipant = Omit<
| 'id' | 'id'
| 'createdAt' | 'createdAt'
| 'updatedAt' | 'updatedAt'
| 'deletedAt'
| 'personId' | 'personId'
| 'workspaceMemberId' | 'workspaceMemberId'
| 'person' | 'person'

View File

@ -6,6 +6,7 @@ export type Message = Omit<
MessageWorkspaceEntity, MessageWorkspaceEntity,
| 'createdAt' | 'createdAt'
| 'updatedAt' | 'updatedAt'
| 'deletedAt'
| 'messageChannelMessageAssociations' | 'messageChannelMessageAssociations'
| 'messageParticipants' | 'messageParticipants'
| 'messageThread' | 'messageThread'
@ -25,6 +26,7 @@ export type MessageParticipant = Omit<
| 'id' | 'id'
| 'createdAt' | 'createdAt'
| 'updatedAt' | 'updatedAt'
| 'deletedAt'
| 'personId' | 'personId'
| 'workspaceMemberId' | 'workspaceMemberId'
| 'person' | 'person'