Set record position on workflow creation (#11308)

- Migrate record position factory to core-modules 
- set position on record creation
This commit is contained in:
Thomas Trompette
2025-04-01 11:50:43 +02:00
committed by GitHub
parent b2012229f4
commit 023d071103
20 changed files with 303 additions and 317 deletions

View File

@ -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 {}

View File

@ -0,0 +1,71 @@
import { Test, TestingModule } from '@nestjs/testing';
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
describe('RecordPositionService', () => {
let workspaceDataSourceService;
let service: RecordPositionService;
beforeEach(async () => {
workspaceDataSourceService = {
getSchemaName: jest.fn().mockReturnValue('schemaName'),
executeRawQuery: jest.fn().mockResolvedValue([{ position: 1 }]),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
RecordPositionService,
{
provide: WorkspaceDataSourceService,
useValue: workspaceDataSourceService,
},
],
}).compile();
service = module.get<RecordPositionService>(RecordPositionService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('create', () => {
const objectMetadata = { isCustom: false, nameSingular: 'company' };
const workspaceId = 'workspaceId';
it('should return the value when value is a number', async () => {
const value = 1;
const result = await service.buildRecordPosition({
value,
objectMetadata,
workspaceId,
});
expect(result).toEqual(value);
});
it('should return the existing position -1 when value is first', async () => {
const value = 'first';
const result = await service.buildRecordPosition({
value,
objectMetadata,
workspaceId,
});
expect(result).toEqual(0);
});
it('should return the existing position + 1 when value is last', async () => {
const value = 'last';
const result = await service.buildRecordPosition({
value,
objectMetadata,
workspaceId,
});
expect(result).toEqual(2);
});
});
});

View File

@ -0,0 +1,89 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'class-validator';
import {
RecordPositionQueryArgs,
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';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
export type RecordPositionServiceCreateArgs = {
value: number | 'first' | 'last';
objectMetadata: { isCustom: boolean; nameSingular: string };
workspaceId: string;
index?: number;
};
@Injectable()
export class RecordPositionService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
async buildRecordPosition({
objectMetadata,
value,
workspaceId,
index = 0,
}: RecordPositionServiceCreateArgs): Promise<number> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
if (typeof value === 'number') {
return value;
}
if (value === 'first') {
const recordWithMinPosition =
await this.createAndExecuteRecordPositionQuery(
{
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION,
},
objectMetadata,
dataSourceSchema,
workspaceId,
);
return isDefined(recordWithMinPosition?.position)
? recordWithMinPosition.position - index - 1
: 1;
}
const recordWithMaxPosition =
await this.createAndExecuteRecordPositionQuery(
{
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION,
},
objectMetadata,
dataSourceSchema,
workspaceId,
);
return isDefined(recordWithMaxPosition?.position)
? recordWithMaxPosition.position + index + 1
: 1;
}
private async createAndExecuteRecordPositionQuery(
recordPositionQueryArgs: RecordPositionQueryArgs,
objectMetadata: { isCustom: boolean; nameSingular: string },
dataSourceSchema: string,
workspaceId: string,
) {
const [query, params] = buildRecordPositionQuery(
recordPositionQueryArgs,
objectMetadata,
dataSourceSchema,
);
const records = await this.workspaceDataSourceService.executeRawQuery(
query,
params,
workspaceId,
);
return records?.[0];
}
}

View File

@ -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;

View File

@ -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]);
});
});

View File

@ -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],
];
};