Set record position on workflow creation (#11308)
- Migrate record position factory to core-modules - set position on record creation
This commit is contained in:
@ -3,8 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
import { ServerBlockNoteEditor } from '@blocknote/server-util';
|
import { ServerBlockNoteEditor } from '@blocknote/server-util';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { Command } from 'nest-commander';
|
import { Command } from 'nest-commander';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
ActiveOrSuspendedWorkspacesMigrationCommandOptions,
|
||||||
|
|||||||
@ -1,81 +0,0 @@
|
|||||||
import {
|
|
||||||
RecordPositionQueryFactory,
|
|
||||||
RecordPositionQueryType,
|
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
|
||||||
|
|
||||||
describe('RecordPositionQueryFactory', () => {
|
|
||||||
const objectMetadataItem = {
|
|
||||||
isCustom: false,
|
|
||||||
nameSingular: 'company',
|
|
||||||
};
|
|
||||||
const dataSourceSchema = 'workspace_test';
|
|
||||||
const factory: RecordPositionQueryFactory = new RecordPositionQueryFactory();
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(factory).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('create', () => {
|
|
||||||
it('should return query and params for FIND_BY_POSITION', async () => {
|
|
||||||
const positionValue = 1;
|
|
||||||
const queryType = RecordPositionQueryType.FIND_BY_POSITION;
|
|
||||||
const [query, params] = factory.create(
|
|
||||||
{ positionValue, recordPositionQueryType: queryType },
|
|
||||||
objectMetadataItem,
|
|
||||||
dataSourceSchema,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(query).toEqual(
|
|
||||||
`SELECT id, position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"
|
|
||||||
WHERE "position" = $1`,
|
|
||||||
);
|
|
||||||
expect(params).toEqual([positionValue]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return query and params for FIND_MIN_POSITION', async () => {
|
|
||||||
const queryType = RecordPositionQueryType.FIND_MIN_POSITION;
|
|
||||||
const [query, params] = factory.create(
|
|
||||||
{ recordPositionQueryType: queryType },
|
|
||||||
objectMetadataItem,
|
|
||||||
dataSourceSchema,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(query).toEqual(
|
|
||||||
`SELECT MIN(position) as position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"`,
|
|
||||||
);
|
|
||||||
expect(params).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return query and params for FIND_MAX_POSITION', async () => {
|
|
||||||
const queryType = RecordPositionQueryType.FIND_MAX_POSITION;
|
|
||||||
const [query, params] = factory.create(
|
|
||||||
{ recordPositionQueryType: queryType },
|
|
||||||
objectMetadataItem,
|
|
||||||
dataSourceSchema,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(query).toEqual(
|
|
||||||
`SELECT MAX(position) as position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"`,
|
|
||||||
);
|
|
||||||
expect(params).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return query and params for UPDATE_POSITION', async () => {
|
|
||||||
const positionValue = 1;
|
|
||||||
const recordId = '1';
|
|
||||||
const queryType = RecordPositionQueryType.UPDATE_POSITION;
|
|
||||||
const [query, params] = factory.create(
|
|
||||||
{ positionValue, recordId, recordPositionQueryType: queryType },
|
|
||||||
objectMetadataItem,
|
|
||||||
dataSourceSchema,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(query).toEqual(
|
|
||||||
`UPDATE ${dataSourceSchema}."${objectMetadataItem.nameSingular}"
|
|
||||||
SET "position" = $1
|
|
||||||
WHERE "id" = $2`,
|
|
||||||
);
|
|
||||||
expect(params).toEqual([positionValue, recordId]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,8 +1,5 @@
|
|||||||
import { ForeignDataWrapperServerQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/foreign-data-wrapper-server-query.factory';
|
import { ForeignDataWrapperServerQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/foreign-data-wrapper-server-query.factory';
|
||||||
|
|
||||||
import { RecordPositionQueryFactory } from './record-position-query.factory';
|
|
||||||
|
|
||||||
export const workspaceQueryBuilderFactories = [
|
export const workspaceQueryBuilderFactories = [
|
||||||
RecordPositionQueryFactory,
|
|
||||||
ForeignDataWrapperServerQueryFactory,
|
ForeignDataWrapperServerQueryFactory,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,121 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
|
||||||
|
|
||||||
export enum RecordPositionQueryType {
|
|
||||||
FIND_MIN_POSITION = 'FIND_MIN_POSITION',
|
|
||||||
FIND_MAX_POSITION = 'FIND_MAX_POSITION',
|
|
||||||
FIND_BY_POSITION = 'FIND_BY_POSITION',
|
|
||||||
UPDATE_POSITION = 'UPDATE_POSITION',
|
|
||||||
}
|
|
||||||
|
|
||||||
type FindByPositionQueryArgs = {
|
|
||||||
positionValue: number | null;
|
|
||||||
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION;
|
|
||||||
};
|
|
||||||
|
|
||||||
type FindMinPositionQueryArgs = {
|
|
||||||
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION;
|
|
||||||
};
|
|
||||||
|
|
||||||
type FindMaxPositionQueryArgs = {
|
|
||||||
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UpdatePositionQueryArgs = {
|
|
||||||
recordId: string;
|
|
||||||
positionValue: number;
|
|
||||||
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RecordPositionQuery = string;
|
|
||||||
|
|
||||||
type RecordPositionQueryParams = any[];
|
|
||||||
|
|
||||||
export type RecordPositionQueryArgs =
|
|
||||||
| FindByPositionQueryArgs
|
|
||||||
| FindMinPositionQueryArgs
|
|
||||||
| FindMaxPositionQueryArgs
|
|
||||||
| UpdatePositionQueryArgs;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class RecordPositionQueryFactory {
|
|
||||||
create(
|
|
||||||
recordPositionQueryArgs: RecordPositionQueryArgs,
|
|
||||||
objectMetadata: { isCustom: boolean; nameSingular: string },
|
|
||||||
dataSourceSchema: string,
|
|
||||||
): [RecordPositionQuery, RecordPositionQueryParams] {
|
|
||||||
const tableName = computeTableName(
|
|
||||||
objectMetadata.nameSingular,
|
|
||||||
objectMetadata.isCustom,
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (recordPositionQueryArgs.recordPositionQueryType) {
|
|
||||||
case RecordPositionQueryType.FIND_BY_POSITION:
|
|
||||||
return this.buildFindByPositionQuery(
|
|
||||||
recordPositionQueryArgs satisfies FindByPositionQueryArgs,
|
|
||||||
tableName,
|
|
||||||
dataSourceSchema,
|
|
||||||
);
|
|
||||||
case RecordPositionQueryType.FIND_MIN_POSITION:
|
|
||||||
return this.buildFindMinPositionQuery(tableName, dataSourceSchema);
|
|
||||||
case RecordPositionQueryType.FIND_MAX_POSITION:
|
|
||||||
return this.buildFindMaxPositionQuery(tableName, dataSourceSchema);
|
|
||||||
case RecordPositionQueryType.UPDATE_POSITION:
|
|
||||||
return this.buildUpdatePositionQuery(
|
|
||||||
recordPositionQueryArgs satisfies UpdatePositionQueryArgs,
|
|
||||||
tableName,
|
|
||||||
dataSourceSchema,
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
throw new Error('Invalid RecordPositionQueryType');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildFindByPositionQuery(
|
|
||||||
{ positionValue }: FindByPositionQueryArgs,
|
|
||||||
name: string,
|
|
||||||
dataSourceSchema: string,
|
|
||||||
): [RecordPositionQuery, RecordPositionQueryParams] {
|
|
||||||
const positionStringParam = positionValue ? '= $1' : 'IS NULL';
|
|
||||||
|
|
||||||
return [
|
|
||||||
`SELECT id, position FROM ${dataSourceSchema}."${name}"
|
|
||||||
WHERE "position" ${positionStringParam}`,
|
|
||||||
positionValue ? [positionValue] : [],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildFindMaxPositionQuery(
|
|
||||||
name: string,
|
|
||||||
dataSourceSchema: string,
|
|
||||||
): [RecordPositionQuery, RecordPositionQueryParams] {
|
|
||||||
return [
|
|
||||||
`SELECT MAX(position) as position FROM ${dataSourceSchema}."${name}"`,
|
|
||||||
[],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildFindMinPositionQuery(
|
|
||||||
name: string,
|
|
||||||
dataSourceSchema: string,
|
|
||||||
): [RecordPositionQuery, RecordPositionQueryParams] {
|
|
||||||
return [
|
|
||||||
`SELECT MIN(position) as position FROM ${dataSourceSchema}."${name}"`,
|
|
||||||
[],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildUpdatePositionQuery(
|
|
||||||
{ recordId, positionValue }: UpdatePositionQueryArgs,
|
|
||||||
name: string,
|
|
||||||
dataSourceSchema: string,
|
|
||||||
): [RecordPositionQuery, RecordPositionQueryParams] {
|
|
||||||
return [
|
|
||||||
`UPDATE ${dataSourceSchema}."${name}"
|
|
||||||
SET "position" = $1
|
|
||||||
WHERE "id" = $2`,
|
|
||||||
[positionValue, recordId],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
|
||||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||||
|
|
||||||
import { workspaceQueryBuilderFactories } from './factories/factories';
|
import { workspaceQueryBuilderFactories } from './factories/factories';
|
||||||
@ -8,6 +7,6 @@ import { workspaceQueryBuilderFactories } from './factories/factories';
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [ObjectMetadataModule],
|
imports: [ObjectMetadataModule],
|
||||||
providers: [...workspaceQueryBuilderFactories],
|
providers: [...workspaceQueryBuilderFactories],
|
||||||
exports: [RecordPositionQueryFactory],
|
exports: [],
|
||||||
})
|
})
|
||||||
export class WorkspaceQueryBuilderModule {}
|
export class WorkspaceQueryBuilderModule {}
|
||||||
|
|||||||
@ -7,14 +7,14 @@ import { ResolverArgsType } from 'src/engine/api/graphql/workspace-resolver-buil
|
|||||||
|
|
||||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||||
import {
|
import {
|
||||||
RecordPositionFactory,
|
RecordPositionService,
|
||||||
RecordPositionFactoryCreateArgs,
|
RecordPositionServiceCreateArgs,
|
||||||
} from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
|
} from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||||
|
|
||||||
describe('QueryRunnerArgsFactory', () => {
|
describe('QueryRunnerArgsFactory', () => {
|
||||||
const recordPositionFactory = {
|
const recordPositionService = {
|
||||||
create: jest.fn().mockResolvedValue(2),
|
buildRecordPosition: jest.fn().mockResolvedValue(2),
|
||||||
};
|
};
|
||||||
const workspaceId = 'workspaceId';
|
const workspaceId = 'workspaceId';
|
||||||
const options = {
|
const options = {
|
||||||
@ -66,10 +66,8 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
QueryRunnerArgsFactory,
|
QueryRunnerArgsFactory,
|
||||||
{
|
{
|
||||||
provide: RecordPositionFactory,
|
provide: RecordPositionService,
|
||||||
useValue: {
|
useValue: recordPositionService,
|
||||||
create: recordPositionFactory.create,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
@ -107,14 +105,16 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
ResolverArgsType.CreateMany,
|
ResolverArgsType.CreateMany,
|
||||||
);
|
);
|
||||||
|
|
||||||
const expectedArgs: RecordPositionFactoryCreateArgs = {
|
const expectedArgs: RecordPositionServiceCreateArgs = {
|
||||||
value: 'last',
|
value: 'last',
|
||||||
objectMetadata: { isCustom: true, nameSingular: 'testNumber' },
|
objectMetadata: { isCustom: true, nameSingular: 'testNumber' },
|
||||||
workspaceId,
|
workspaceId,
|
||||||
index: 0,
|
index: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(recordPositionFactory.create).toHaveBeenCalledWith(expectedArgs);
|
expect(recordPositionService.buildRecordPosition).toHaveBeenCalledWith(
|
||||||
|
expectedArgs,
|
||||||
|
);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
id: 'uuid',
|
id: 'uuid',
|
||||||
data: [{ position: 2, testNumber: 1 }],
|
data: [{ position: 2, testNumber: 1 }],
|
||||||
@ -133,14 +133,16 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
ResolverArgsType.CreateMany,
|
ResolverArgsType.CreateMany,
|
||||||
);
|
);
|
||||||
|
|
||||||
const expectedArgs: RecordPositionFactoryCreateArgs = {
|
const expectedArgs: RecordPositionServiceCreateArgs = {
|
||||||
value: 'first',
|
value: 'first',
|
||||||
objectMetadata: { isCustom: true, nameSingular: 'testNumber' },
|
objectMetadata: { isCustom: true, nameSingular: 'testNumber' },
|
||||||
workspaceId,
|
workspaceId,
|
||||||
index: 0,
|
index: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(recordPositionFactory.create).toHaveBeenCalledWith(expectedArgs);
|
expect(recordPositionService.buildRecordPosition).toHaveBeenCalledWith(
|
||||||
|
expectedArgs,
|
||||||
|
);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
id: 'uuid',
|
id: 'uuid',
|
||||||
data: [{ position: 2, testNumber: 1 }],
|
data: [{ position: 2, testNumber: 1 }],
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { QueryRunnerArgsFactory } from './query-runner-args.factory';
|
import { QueryRunnerArgsFactory } from './query-runner-args.factory';
|
||||||
import { RecordPositionFactory } from './record-position.factory';
|
|
||||||
|
|
||||||
import { QueryResultGettersFactory } from './query-result-getters/query-result-getters.factory';
|
import { QueryResultGettersFactory } from './query-result-getters/query-result-getters.factory';
|
||||||
|
|
||||||
export const workspaceQueryRunnerFactories = [
|
export const workspaceQueryRunnerFactories = [
|
||||||
QueryRunnerArgsFactory,
|
QueryRunnerArgsFactory,
|
||||||
RecordPositionFactory,
|
|
||||||
QueryResultGettersFactory,
|
QueryResultGettersFactory,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -22,14 +22,13 @@ import {
|
|||||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||||
|
|
||||||
import { lowercaseDomain } from 'src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util';
|
import { lowercaseDomain } from 'src/engine/api/graphql/workspace-query-runner/utils/query-runner-links.util';
|
||||||
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import {
|
import {
|
||||||
RichTextV2Metadata,
|
RichTextV2Metadata,
|
||||||
richTextV2ValueSchema,
|
richTextV2ValueSchema,
|
||||||
} from 'src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type';
|
} from 'src/engine/metadata-modules/field-metadata/composite-types/rich-text-v2.composite-type';
|
||||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||||
|
|
||||||
import { RecordPositionFactory } from './record-position.factory';
|
|
||||||
|
|
||||||
type ArgPositionBackfillInput = {
|
type ArgPositionBackfillInput = {
|
||||||
argIndex?: number;
|
argIndex?: number;
|
||||||
shouldBackfillPosition: boolean;
|
shouldBackfillPosition: boolean;
|
||||||
@ -37,7 +36,7 @@ type ArgPositionBackfillInput = {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryRunnerArgsFactory {
|
export class QueryRunnerArgsFactory {
|
||||||
constructor(private readonly recordPositionFactory: RecordPositionFactory) {}
|
constructor(private readonly recordPositionService: RecordPositionService) {}
|
||||||
|
|
||||||
async create(
|
async create(
|
||||||
args: ResolverArgs,
|
args: ResolverArgs,
|
||||||
@ -190,16 +189,18 @@ export class QueryRunnerArgsFactory {
|
|||||||
case FieldMetadataType.POSITION: {
|
case FieldMetadataType.POSITION: {
|
||||||
isFieldPositionPresent = true;
|
isFieldPositionPresent = true;
|
||||||
|
|
||||||
const newValue = await this.recordPositionFactory.create({
|
const newValue = await this.recordPositionService.buildRecordPosition(
|
||||||
value,
|
{
|
||||||
workspaceId,
|
value,
|
||||||
objectMetadata: {
|
workspaceId,
|
||||||
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
|
objectMetadata: {
|
||||||
nameSingular:
|
isCustom: options.objectMetadataItemWithFieldMaps.isCustom,
|
||||||
options.objectMetadataItemWithFieldMaps.nameSingular,
|
nameSingular:
|
||||||
|
options.objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
},
|
||||||
|
index: argPositionBackfillInput.argIndex,
|
||||||
},
|
},
|
||||||
index: argPositionBackfillInput.argIndex,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
return [key, newValue];
|
return [key, newValue];
|
||||||
}
|
}
|
||||||
@ -307,7 +308,7 @@ export class QueryRunnerArgsFactory {
|
|||||||
...newArgEntries,
|
...newArgEntries,
|
||||||
[
|
[
|
||||||
'position',
|
'position',
|
||||||
await this.recordPositionFactory.create({
|
await this.recordPositionService.buildRecordPosition({
|
||||||
value: 'first',
|
value: 'first',
|
||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadata: {
|
objectMetadata: {
|
||||||
|
|||||||
@ -3,27 +3,21 @@ import { getRepositoryToken } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
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 { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
|
||||||
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
|
|
||||||
describe('RecordPositionBackfillService', () => {
|
describe('RecordPositionBackfillService', () => {
|
||||||
let recordPositionQueryFactory;
|
let recordPositionService;
|
||||||
let recordPositionFactory;
|
|
||||||
let objectMetadataRepository;
|
let objectMetadataRepository;
|
||||||
let workspaceDataSourceService;
|
let workspaceDataSourceService;
|
||||||
|
|
||||||
let service: RecordPositionBackfillService;
|
let service: RecordPositionBackfillService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
recordPositionQueryFactory = {
|
recordPositionService = {
|
||||||
create: jest.fn().mockReturnValue(['query', []]),
|
buildRecordPosition: jest.fn().mockResolvedValue([
|
||||||
};
|
|
||||||
|
|
||||||
recordPositionFactory = {
|
|
||||||
create: jest.fn().mockResolvedValue([
|
|
||||||
{
|
{
|
||||||
position: 1,
|
position: 1,
|
||||||
},
|
},
|
||||||
@ -42,12 +36,8 @@ describe('RecordPositionBackfillService', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
RecordPositionBackfillService,
|
RecordPositionBackfillService,
|
||||||
{
|
{
|
||||||
provide: RecordPositionQueryFactory,
|
provide: RecordPositionService,
|
||||||
useValue: recordPositionQueryFactory,
|
useValue: recordPositionService,
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: RecordPositionFactory,
|
|
||||||
useValue: recordPositionFactory,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: WorkspaceDataSourceService,
|
provide: WorkspaceDataSourceService,
|
||||||
@ -123,8 +113,7 @@ describe('RecordPositionBackfillService', () => {
|
|||||||
]);
|
]);
|
||||||
await service.backfill('workspaceId', false);
|
await service.backfill('workspaceId', false);
|
||||||
expect(workspaceDataSourceService.executeRawQuery).toHaveBeenCalledTimes(2);
|
expect(workspaceDataSourceService.executeRawQuery).toHaveBeenCalledTimes(2);
|
||||||
expect(recordPositionFactory.create).toHaveBeenCalledTimes(1);
|
expect(recordPositionService.buildRecordPosition).toHaveBeenCalledTimes(1);
|
||||||
expect(recordPositionQueryFactory.create).toHaveBeenCalledTimes(2);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when dryRun is true, should not update position', async () => {
|
it('when dryRun is true, should not update position', async () => {
|
||||||
@ -148,7 +137,6 @@ describe('RecordPositionBackfillService', () => {
|
|||||||
]);
|
]);
|
||||||
await service.backfill('workspaceId', true);
|
await service.backfill('workspaceId', true);
|
||||||
expect(workspaceDataSourceService.executeRawQuery).toHaveBeenCalledTimes(1);
|
expect(workspaceDataSourceService.executeRawQuery).toHaveBeenCalledTimes(1);
|
||||||
expect(recordPositionFactory.create).toHaveBeenCalledTimes(1);
|
expect(recordPositionService.buildRecordPosition).toHaveBeenCalledTimes(1);
|
||||||
expect(recordPositionQueryFactory.create).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } 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 { RecordPositionBackfillService } from 'src/engine/api/graphql/workspace-query-runner/services/record-position-backfill-service';
|
||||||
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
|
|
||||||
@ -12,11 +11,7 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
|
|||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [RecordPositionService, RecordPositionBackfillService],
|
||||||
RecordPositionFactory,
|
|
||||||
RecordPositionQueryFactory,
|
|
||||||
RecordPositionBackfillService,
|
|
||||||
],
|
|
||||||
exports: [RecordPositionBackfillService],
|
exports: [RecordPositionBackfillService],
|
||||||
})
|
})
|
||||||
export class RecordPositionBackfillModule {}
|
export class RecordPositionBackfillModule {}
|
||||||
|
|||||||
@ -2,14 +2,12 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { isDefined } from 'class-validator';
|
import { isDefined } from 'class-validator';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import {
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
RecordPositionQueryFactory,
|
import { RecordPositionQueryType } from 'src/engine/core-modules/record-position/types/record-position-query.type';
|
||||||
RecordPositionQueryType,
|
import { buildRecordPositionQuery } from 'src/engine/core-modules/record-position/utils/build-record-position-query.util';
|
||||||
} 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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
|
|
||||||
@ -19,9 +17,8 @@ export class RecordPositionBackfillService {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly recordPositionFactory: RecordPositionFactory,
|
|
||||||
private readonly recordPositionQueryFactory: RecordPositionQueryFactory,
|
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
|
private readonly recordPositionService: RecordPositionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async backfill(workspaceId: string, dryRun: boolean) {
|
async backfill(workspaceId: string, dryRun: boolean) {
|
||||||
@ -47,7 +44,7 @@ export class RecordPositionBackfillService {
|
|||||||
|
|
||||||
for (const objectMetadata of objectMetadataCollection) {
|
for (const objectMetadata of objectMetadataCollection) {
|
||||||
const [recordsWithoutPositionQuery, recordsWithoutPositionQueryParams] =
|
const [recordsWithoutPositionQuery, recordsWithoutPositionQueryParams] =
|
||||||
this.recordPositionQueryFactory.create(
|
buildRecordPositionQuery(
|
||||||
{
|
{
|
||||||
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION,
|
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION,
|
||||||
positionValue: null,
|
positionValue: null,
|
||||||
@ -73,7 +70,7 @@ export class RecordPositionBackfillService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position = await this.recordPositionFactory.create({
|
const position = await this.recordPositionService.buildRecordPosition({
|
||||||
objectMetadata: {
|
objectMetadata: {
|
||||||
isCustom: objectMetadata.isCustom,
|
isCustom: objectMetadata.isCustom,
|
||||||
nameSingular: objectMetadata.nameSingular,
|
nameSingular: objectMetadata.nameSingular,
|
||||||
@ -106,7 +103,7 @@ export class RecordPositionBackfillService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [query, params] = this.recordPositionQueryFactory.create(
|
const [query, params] = buildRecordPositionQuery(
|
||||||
{
|
{
|
||||||
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION,
|
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION,
|
||||||
recordId: recordsWithoutPosition[recordIndex].id,
|
recordId: recordsWithoutPosition[recordIndex].id,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
|||||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||||
|
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
|
||||||
import { TelemetryModule } from 'src/engine/core-modules/telemetry/telemetry.module';
|
import { TelemetryModule } from 'src/engine/core-modules/telemetry/telemetry.module';
|
||||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
@ -29,6 +30,7 @@ import { EntityEventsToDbListener } from './listeners/entity-events-to-db.listen
|
|||||||
TelemetryModule,
|
TelemetryModule,
|
||||||
FileModule,
|
FileModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
|
RecordPositionModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...workspaceQueryRunnerFactories,
|
...workspaceQueryRunnerFactories,
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
|
|
||||||
|
import { RecordPositionService } from './services/record-position.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [WorkspaceDataSourceModule],
|
||||||
|
providers: [RecordPositionService],
|
||||||
|
exports: [RecordPositionService],
|
||||||
|
})
|
||||||
|
export class RecordPositionModule {}
|
||||||
@ -1,17 +1,12 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
import { RecordPositionQueryFactory } from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { RecordPositionFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/record-position.factory';
|
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
|
|
||||||
describe('RecordPositionFactory', () => {
|
describe('RecordPositionService', () => {
|
||||||
const recordPositionQueryFactory = {
|
|
||||||
create: jest.fn().mockReturnValue(['query', []]),
|
|
||||||
};
|
|
||||||
|
|
||||||
let workspaceDataSourceService;
|
let workspaceDataSourceService;
|
||||||
|
|
||||||
let factory: RecordPositionFactory;
|
let service: RecordPositionService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
workspaceDataSourceService = {
|
workspaceDataSourceService = {
|
||||||
@ -20,11 +15,7 @@ describe('RecordPositionFactory', () => {
|
|||||||
};
|
};
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
RecordPositionFactory,
|
RecordPositionService,
|
||||||
{
|
|
||||||
provide: RecordPositionQueryFactory,
|
|
||||||
useValue: recordPositionQueryFactory,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: WorkspaceDataSourceService,
|
provide: WorkspaceDataSourceService,
|
||||||
useValue: workspaceDataSourceService,
|
useValue: workspaceDataSourceService,
|
||||||
@ -32,11 +23,11 @@ describe('RecordPositionFactory', () => {
|
|||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
factory = module.get<RecordPositionFactory>(RecordPositionFactory);
|
service = module.get<RecordPositionService>(RecordPositionService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(factory).toBeDefined();
|
expect(service).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
@ -46,7 +37,7 @@ describe('RecordPositionFactory', () => {
|
|||||||
it('should return the value when value is a number', async () => {
|
it('should return the value when value is a number', async () => {
|
||||||
const value = 1;
|
const value = 1;
|
||||||
|
|
||||||
const result = await factory.create({
|
const result = await service.buildRecordPosition({
|
||||||
value,
|
value,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -57,7 +48,7 @@ describe('RecordPositionFactory', () => {
|
|||||||
|
|
||||||
it('should return the existing position -1 when value is first', async () => {
|
it('should return the existing position -1 when value is first', async () => {
|
||||||
const value = 'first';
|
const value = 'first';
|
||||||
const result = await factory.create({
|
const result = await service.buildRecordPosition({
|
||||||
value,
|
value,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -68,7 +59,7 @@ describe('RecordPositionFactory', () => {
|
|||||||
|
|
||||||
it('should return the existing position + 1 when value is last', async () => {
|
it('should return the existing position + 1 when value is last', async () => {
|
||||||
const value = 'last';
|
const value = 'last';
|
||||||
const result = await factory.create({
|
const result = await service.buildRecordPosition({
|
||||||
value,
|
value,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -4,30 +4,30 @@ import { isDefined } from 'class-validator';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
RecordPositionQueryArgs,
|
RecordPositionQueryArgs,
|
||||||
RecordPositionQueryFactory,
|
|
||||||
RecordPositionQueryType,
|
RecordPositionQueryType,
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
} from 'src/engine/core-modules/record-position/types/record-position-query.type';
|
||||||
|
import { buildRecordPositionQuery } from 'src/engine/core-modules/record-position/utils/build-record-position-query.util';
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
|
|
||||||
export type RecordPositionFactoryCreateArgs = {
|
export type RecordPositionServiceCreateArgs = {
|
||||||
value: number | 'first' | 'last';
|
value: number | 'first' | 'last';
|
||||||
objectMetadata: { isCustom: boolean; nameSingular: string };
|
objectMetadata: { isCustom: boolean; nameSingular: string };
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
index?: number;
|
index?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RecordPositionFactory {
|
export class RecordPositionService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||||
private readonly recordPositionQueryFactory: RecordPositionQueryFactory,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async create({
|
async buildRecordPosition({
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
value,
|
value,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
index = 0,
|
index = 0,
|
||||||
}: RecordPositionFactoryCreateArgs): Promise<number> {
|
}: RecordPositionServiceCreateArgs): Promise<number> {
|
||||||
const dataSourceSchema =
|
const dataSourceSchema =
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
@ -36,41 +36,43 @@ export class RecordPositionFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (value === 'first') {
|
if (value === 'first') {
|
||||||
const recordWithMinPosition = await this.findRecordPosition(
|
const recordWithMinPosition =
|
||||||
{
|
await this.createAndExecuteRecordPositionQuery(
|
||||||
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION,
|
{
|
||||||
},
|
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION,
|
||||||
objectMetadata,
|
},
|
||||||
dataSourceSchema,
|
objectMetadata,
|
||||||
workspaceId,
|
dataSourceSchema,
|
||||||
);
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
return isDefined(recordWithMinPosition?.position)
|
return isDefined(recordWithMinPosition?.position)
|
||||||
? recordWithMinPosition.position - index - 1
|
? recordWithMinPosition.position - index - 1
|
||||||
: 1;
|
: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const recordWithMaxPosition = await this.findRecordPosition(
|
const recordWithMaxPosition =
|
||||||
{
|
await this.createAndExecuteRecordPositionQuery(
|
||||||
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION,
|
{
|
||||||
},
|
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION,
|
||||||
objectMetadata,
|
},
|
||||||
dataSourceSchema,
|
objectMetadata,
|
||||||
workspaceId,
|
dataSourceSchema,
|
||||||
);
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
return isDefined(recordWithMaxPosition?.position)
|
return isDefined(recordWithMaxPosition?.position)
|
||||||
? recordWithMaxPosition.position + index + 1
|
? recordWithMaxPosition.position + index + 1
|
||||||
: 1;
|
: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findRecordPosition(
|
private async createAndExecuteRecordPositionQuery(
|
||||||
recordPositionQueryArgs: RecordPositionQueryArgs,
|
recordPositionQueryArgs: RecordPositionQueryArgs,
|
||||||
objectMetadata: { isCustom: boolean; nameSingular: string },
|
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||||
dataSourceSchema: string,
|
dataSourceSchema: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
) {
|
) {
|
||||||
const [query, params] = this.recordPositionQueryFactory.create(
|
const [query, params] = buildRecordPositionQuery(
|
||||||
recordPositionQueryArgs,
|
recordPositionQueryArgs,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
dataSourceSchema,
|
dataSourceSchema,
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
export type FindByPositionQueryArgs = {
|
||||||
|
positionValue: number | null;
|
||||||
|
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FindMinPositionQueryArgs = {
|
||||||
|
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FindMaxPositionQueryArgs = {
|
||||||
|
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdatePositionQueryArgs = {
|
||||||
|
recordId: string;
|
||||||
|
positionValue: number;
|
||||||
|
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum RecordPositionQueryType {
|
||||||
|
FIND_MIN_POSITION = 'FIND_MIN_POSITION',
|
||||||
|
FIND_MAX_POSITION = 'FIND_MAX_POSITION',
|
||||||
|
FIND_BY_POSITION = 'FIND_BY_POSITION',
|
||||||
|
UPDATE_POSITION = 'UPDATE_POSITION',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RecordPositionQueryArgs =
|
||||||
|
| FindByPositionQueryArgs
|
||||||
|
| FindMinPositionQueryArgs
|
||||||
|
| FindMaxPositionQueryArgs
|
||||||
|
| UpdatePositionQueryArgs;
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
import { RecordPositionQueryType } from 'src/engine/core-modules/record-position/types/record-position-query.type';
|
||||||
|
import { buildRecordPositionQuery } from 'src/engine/core-modules/record-position/utils/build-record-position-query.util';
|
||||||
|
|
||||||
|
describe('buildRecordPositionQuery', () => {
|
||||||
|
const objectMetadataItem = {
|
||||||
|
isCustom: false,
|
||||||
|
nameSingular: 'company',
|
||||||
|
};
|
||||||
|
const dataSourceSchema = 'workspace_test';
|
||||||
|
|
||||||
|
it('should return query and params for FIND_BY_POSITION', async () => {
|
||||||
|
const positionValue = 1;
|
||||||
|
const queryType = RecordPositionQueryType.FIND_BY_POSITION;
|
||||||
|
const [query, params] = buildRecordPositionQuery(
|
||||||
|
{ positionValue, recordPositionQueryType: queryType },
|
||||||
|
objectMetadataItem,
|
||||||
|
dataSourceSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(query).toEqual(
|
||||||
|
`SELECT id, position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"
|
||||||
|
WHERE "position" = $1`,
|
||||||
|
);
|
||||||
|
expect(params).toEqual([positionValue]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return query and params for FIND_MIN_POSITION', async () => {
|
||||||
|
const queryType = RecordPositionQueryType.FIND_MIN_POSITION;
|
||||||
|
const [query, params] = buildRecordPositionQuery(
|
||||||
|
{ recordPositionQueryType: queryType },
|
||||||
|
objectMetadataItem,
|
||||||
|
dataSourceSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(query).toEqual(
|
||||||
|
`SELECT MIN(position) as position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"`,
|
||||||
|
);
|
||||||
|
expect(params).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return query and params for FIND_MAX_POSITION', async () => {
|
||||||
|
const queryType = RecordPositionQueryType.FIND_MAX_POSITION;
|
||||||
|
const [query, params] = buildRecordPositionQuery(
|
||||||
|
{ recordPositionQueryType: queryType },
|
||||||
|
objectMetadataItem,
|
||||||
|
dataSourceSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(query).toEqual(
|
||||||
|
`SELECT MAX(position) as position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"`,
|
||||||
|
);
|
||||||
|
expect(params).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return query and params for UPDATE_POSITION', async () => {
|
||||||
|
const positionValue = 1;
|
||||||
|
const recordId = '1';
|
||||||
|
const queryType = RecordPositionQueryType.UPDATE_POSITION;
|
||||||
|
const [query, params] = buildRecordPositionQuery(
|
||||||
|
{ positionValue, recordId, recordPositionQueryType: queryType },
|
||||||
|
objectMetadataItem,
|
||||||
|
dataSourceSchema,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(query).toEqual(
|
||||||
|
`UPDATE ${dataSourceSchema}."${objectMetadataItem.nameSingular}"
|
||||||
|
SET "position" = $1
|
||||||
|
WHERE "id" = $2`,
|
||||||
|
);
|
||||||
|
expect(params).toEqual([positionValue, recordId]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
import {
|
||||||
|
FindByPositionQueryArgs,
|
||||||
|
RecordPositionQueryArgs,
|
||||||
|
RecordPositionQueryType,
|
||||||
|
UpdatePositionQueryArgs,
|
||||||
|
} from 'src/engine/core-modules/record-position/types/record-position-query.type';
|
||||||
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
|
type RecordPositionQuery = string;
|
||||||
|
|
||||||
|
type RecordPositionQueryParams = any[];
|
||||||
|
|
||||||
|
export const buildRecordPositionQuery = (
|
||||||
|
recordPositionQueryArgs: RecordPositionQueryArgs,
|
||||||
|
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||||
|
dataSourceSchema: string,
|
||||||
|
): [RecordPositionQuery, RecordPositionQueryParams] => {
|
||||||
|
const tableName = computeTableName(
|
||||||
|
objectMetadata.nameSingular,
|
||||||
|
objectMetadata.isCustom,
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (recordPositionQueryArgs.recordPositionQueryType) {
|
||||||
|
case RecordPositionQueryType.FIND_BY_POSITION:
|
||||||
|
return buildFindByPositionQuery(
|
||||||
|
recordPositionQueryArgs satisfies FindByPositionQueryArgs,
|
||||||
|
tableName,
|
||||||
|
dataSourceSchema,
|
||||||
|
);
|
||||||
|
case RecordPositionQueryType.FIND_MIN_POSITION:
|
||||||
|
return buildFindMinPositionQuery(tableName, dataSourceSchema);
|
||||||
|
case RecordPositionQueryType.FIND_MAX_POSITION:
|
||||||
|
return buildFindMaxPositionQuery(tableName, dataSourceSchema);
|
||||||
|
case RecordPositionQueryType.UPDATE_POSITION:
|
||||||
|
return buildUpdatePositionQuery(
|
||||||
|
recordPositionQueryArgs satisfies UpdatePositionQueryArgs,
|
||||||
|
tableName,
|
||||||
|
dataSourceSchema,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid RecordPositionQueryType');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFindByPositionQuery = (
|
||||||
|
{ positionValue }: FindByPositionQueryArgs,
|
||||||
|
name: string,
|
||||||
|
dataSourceSchema: string,
|
||||||
|
): [RecordPositionQuery, RecordPositionQueryParams] => {
|
||||||
|
const positionStringParam = positionValue ? '= $1' : 'IS NULL';
|
||||||
|
|
||||||
|
return [
|
||||||
|
`SELECT id, position FROM ${dataSourceSchema}."${name}"
|
||||||
|
WHERE "position" ${positionStringParam}`,
|
||||||
|
positionValue ? [positionValue] : [],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFindMaxPositionQuery = (
|
||||||
|
name: string,
|
||||||
|
dataSourceSchema: string,
|
||||||
|
): [RecordPositionQuery, RecordPositionQueryParams] => {
|
||||||
|
return [
|
||||||
|
`SELECT MAX(position) as position FROM ${dataSourceSchema}."${name}"`,
|
||||||
|
[],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFindMinPositionQuery = (
|
||||||
|
name: string,
|
||||||
|
dataSourceSchema: string,
|
||||||
|
): [RecordPositionQuery, RecordPositionQueryParams] => {
|
||||||
|
return [
|
||||||
|
`SELECT MIN(position) as position FROM ${dataSourceSchema}."${name}"`,
|
||||||
|
[],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildUpdatePositionQuery = (
|
||||||
|
{ recordId, positionValue }: UpdatePositionQueryArgs,
|
||||||
|
name: string,
|
||||||
|
dataSourceSchema: string,
|
||||||
|
): [RecordPositionQuery, RecordPositionQueryParams] => {
|
||||||
|
return [
|
||||||
|
`UPDATE ${dataSourceSchema}."${name}"
|
||||||
|
SET "position" = $1
|
||||||
|
WHERE "id" = $2`,
|
||||||
|
[positionValue, recordId],
|
||||||
|
];
|
||||||
|
};
|
||||||
@ -6,6 +6,7 @@ import { Repository } from 'typeorm';
|
|||||||
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
import { WorkflowExecutor } from 'src/modules/workflow/workflow-executor/interfaces/workflow-executor.interface';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
@ -33,6 +34,7 @@ export class CreateRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
|
private readonly recordPositionService: RecordPositionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute({
|
async execute({
|
||||||
@ -80,8 +82,15 @@ export class CreateRecordWorkflowAction implements WorkflowExecutor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const position = await this.recordPositionService.buildRecordPosition({
|
||||||
|
value: 'first',
|
||||||
|
objectMetadata,
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
const objectRecord = await repository.save({
|
const objectRecord = await repository.save({
|
||||||
...workflowActionInput.objectRecord,
|
...workflowActionInput.objectRecord,
|
||||||
|
position,
|
||||||
createdBy: {
|
createdBy: {
|
||||||
source: FieldActorSource.WORKFLOW,
|
source: FieldActorSource.WORKFLOW,
|
||||||
name: 'Workflow',
|
name: 'Workflow',
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
|||||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||||
|
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
@ -16,6 +17,7 @@ import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-execut
|
|||||||
WorkspaceCacheStorageModule,
|
WorkspaceCacheStorageModule,
|
||||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
|
RecordPositionModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ScopedWorkspaceContextFactory,
|
ScopedWorkspaceContextFactory,
|
||||||
|
|||||||
Reference in New Issue
Block a user