Fix Tasks/Notes created with null position (#9068)

Fixes https://github.com/twentyhq/twenty/issues/8810
Fixes https://github.com/twentyhq/twenty/issues/5268
Fixes https://github.com/twentyhq/twenty/issues/8971

- Fixing Task/Note creation not sending position during creation
- Adding a command to backfill position being null, using existing
backfill command.
- Removed unused backfill job.
- Updated workspace entities to set position non-nullable and set a
default value to make it non-required on the API
- Updated position factory to set a default position for all objects
having a POSITION field instead of only company/people
- Moved the try/catch in each resolver factory calling
GraphqlQueryRunnerException handler, makes more sense to call it in the
actual graphql-query-runner and removing some duplicate codes
- Adding validations for input in QueryRunnerArgs factories
- Allow sync-metadata to override and sync defaultValues for certain
field types (that can't be updated by users)
- Removing health-check from sync-metadata command during force mode to
improve performances
This commit is contained in:
Weiko
2024-12-16 14:45:54 +01:00
committed by GitHub
parent 2ceb1c87b3
commit 5a27491bb2
55 changed files with 556 additions and 513 deletions

View File

@ -1,15 +1,17 @@
import { TestingModule, Test } from '@nestjs/testing';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
describe('RecordPositionBackfillService', () => {
let recordPositionQueryFactory;
let recordPositionFactory;
let objectMetadataService;
let objectMetadataRepository;
let workspaceDataSourceService;
let service: RecordPositionBackfillService;
@ -27,8 +29,8 @@ describe('RecordPositionBackfillService', () => {
]),
};
objectMetadataService = {
findManyWithinWorkspace: jest.fn().mockReturnValue([]),
objectMetadataRepository = {
find: jest.fn().mockReturnValue([]),
};
workspaceDataSourceService = {
@ -51,8 +53,8 @@ describe('RecordPositionBackfillService', () => {
useValue: workspaceDataSourceService,
},
{
provide: ObjectMetadataService,
useValue: objectMetadataService,
provide: getRepositoryToken(ObjectMetadataEntity, 'metadata'),
useValue: objectMetadataRepository,
},
],
}).compile();
@ -76,23 +78,23 @@ describe('RecordPositionBackfillService', () => {
});
it('when objectMetadata without position, should do nothing', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
{
id: '1',
nameSingular: 'name',
fields: [],
},
]);
objectMetadataRepository.find.mockReturnValue([]);
await service.backfill('workspaceId', false);
expect(workspaceDataSourceService.executeRawQuery).not.toHaveBeenCalled();
});
it('when objectMetadata but all record with position, should create and run query once', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
objectMetadataRepository.find.mockReturnValue([
{
id: '1',
nameSingular: 'company',
fields: [],
fields: [
{
type: FieldMetadataType.POSITION,
isCustom: true,
nameSingular: 'position',
},
],
},
]);
await service.backfill('workspaceId', false);
@ -100,11 +102,17 @@ describe('RecordPositionBackfillService', () => {
});
it('when record without position, should create and run query twice', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
objectMetadataRepository.find.mockReturnValue([
{
id: '1',
nameSingular: 'company',
fields: [],
fields: [
{
type: FieldMetadataType.POSITION,
isCustom: true,
nameSingular: 'position',
},
],
},
]);
workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([
@ -119,11 +127,17 @@ describe('RecordPositionBackfillService', () => {
});
it('when dryRun is true, should not update position', async () => {
objectMetadataService.findManyWithinWorkspace.mockReturnValue([
objectMetadataRepository.find.mockReturnValue([
{
id: '1',
nameSingular: 'company',
fields: [],
fields: [
{
type: FieldMetadataType.POSITION,
isCustom: true,
nameSingular: 'position',
},
],
},
]);
workspaceDataSourceService.executeRawQuery.mockResolvedValueOnce([

View File

@ -1,13 +1,17 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
import { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
@Module({
imports: [WorkspaceDataSourceModule, ObjectMetadataModule],
imports: [
WorkspaceDataSourceModule,
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
],
providers: [
RecordPositionFactory,
RecordPositionQueryFactory,

View File

@ -1,21 +1,24 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'class-validator';
import { Repository } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import {
RecordPositionQueryFactory,
RecordPositionQueryType,
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { hasPositionField } from 'src/engine/metadata-modules/object-metadata/utils/has-position-field.util';
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable()
export class RecordPositionBackfillService {
private readonly logger = new Logger(RecordPositionBackfillService.name);
constructor(
private readonly objectMetadataService: ObjectMetadataService,
@InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly recordPositionFactory: RecordPositionFactory,
private readonly recordPositionQueryFactory: RecordPositionQueryFactory,
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
@ -29,15 +32,20 @@ export class RecordPositionBackfillService {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const objectMetadataEntities =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId, {
where: { isSystem: false },
});
const objectMetadataCollection = await this.objectMetadataRepository.find({
where: {
workspaceId,
fields: {
name: 'position',
type: FieldMetadataType.POSITION,
},
},
relations: {
fields: true,
},
});
const objectMetadataWithPosition =
objectMetadataEntities.filter(hasPositionField);
for (const objectMetadata of objectMetadataWithPosition) {
for (const objectMetadata of objectMetadataCollection) {
const [recordsWithoutPositionQuery, recordsWithoutPositionQueryParams] =
this.recordPositionQueryFactory.create(
{