Backfill position when not input (#5696)
- refactor record position factory and record position query factory - override position if not present during createMany To avoid overriding the same positions for all data in createMany, the logic is: - if inserted last, use last position + arg index + 1 - if inserted first, use first position - arg index - 1
This commit is contained in:
@ -12,12 +12,14 @@ describe('QueryRunnerArgsFactory', () => {
|
||||
const recordPositionFactory = {
|
||||
create: jest.fn().mockResolvedValue(2),
|
||||
};
|
||||
const workspaceId = 'workspaceId';
|
||||
const options = {
|
||||
fieldMetadataCollection: [
|
||||
{ name: 'position', type: FieldMetadataType.POSITION },
|
||||
{ name: 'testNumber', type: FieldMetadataType.NUMBER },
|
||||
] as FieldMetadataInterface[],
|
||||
objectMetadataItem: { isCustom: true, nameSingular: 'test' },
|
||||
workspaceId,
|
||||
} as WorkspaceQueryRunnerOptions;
|
||||
|
||||
let factory: QueryRunnerArgsFactory;
|
||||
@ -68,6 +70,36 @@ describe('QueryRunnerArgsFactory', () => {
|
||||
ResolverArgsType.CreateMany,
|
||||
);
|
||||
|
||||
expect(recordPositionFactory.create).toHaveBeenCalledWith(
|
||||
'last',
|
||||
{ isCustom: true, nameSingular: 'test' },
|
||||
workspaceId,
|
||||
0,
|
||||
);
|
||||
expect(result).toEqual({
|
||||
id: 'uuid',
|
||||
data: [{ position: 2, testNumber: 1 }],
|
||||
});
|
||||
});
|
||||
|
||||
it('createMany type should override position if not present', async () => {
|
||||
const args = {
|
||||
id: 'uuid',
|
||||
data: [{ testNumber: '1' }],
|
||||
};
|
||||
|
||||
const result = await factory.create(
|
||||
args,
|
||||
options,
|
||||
ResolverArgsType.CreateMany,
|
||||
);
|
||||
|
||||
expect(recordPositionFactory.create).toHaveBeenCalledWith(
|
||||
'first',
|
||||
{ isCustom: true, nameSingular: 'test' },
|
||||
workspaceId,
|
||||
0,
|
||||
);
|
||||
expect(result).toEqual({
|
||||
id: 'uuid',
|
||||
data: [{ position: 2, testNumber: 1 }],
|
||||
|
||||
@ -9,14 +9,15 @@ describe('RecordPositionFactory', () => {
|
||||
create: jest.fn().mockResolvedValue('query'),
|
||||
};
|
||||
|
||||
const workspaceDataSourceService = {
|
||||
getSchemaName: jest.fn().mockReturnValue('schemaName'),
|
||||
executeRawQuery: jest.fn().mockResolvedValue([{ position: 1 }]),
|
||||
};
|
||||
let workspaceDataSourceService;
|
||||
|
||||
let factory: RecordPositionFactory;
|
||||
|
||||
beforeEach(async () => {
|
||||
workspaceDataSourceService = {
|
||||
getSchemaName: jest.fn().mockReturnValue('schemaName'),
|
||||
executeRawQuery: jest.fn().mockResolvedValue([{ position: 1 }]),
|
||||
};
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
RecordPositionFactory,
|
||||
@ -44,10 +45,20 @@ describe('RecordPositionFactory', () => {
|
||||
|
||||
it('should return the value when value is a number', async () => {
|
||||
const value = 1;
|
||||
|
||||
workspaceDataSourceService.executeRawQuery.mockResolvedValue([]);
|
||||
|
||||
const result = await factory.create(value, objectMetadata, workspaceId);
|
||||
|
||||
expect(result).toEqual(value);
|
||||
});
|
||||
it('should throw an error when position is not unique', async () => {
|
||||
const value = 1;
|
||||
|
||||
await expect(
|
||||
factory.create(value, objectMetadata, workspaceId),
|
||||
).rejects.toThrow('Position is not unique');
|
||||
});
|
||||
it('should return the existing position -1 when value is first', async () => {
|
||||
const value = 'first';
|
||||
const result = await factory.create(value, objectMetadata, workspaceId);
|
||||
|
||||
@ -16,6 +16,11 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/fi
|
||||
|
||||
import { RecordPositionFactory } from './record-position.factory';
|
||||
|
||||
type ArgPositionBackfillInput = {
|
||||
argIndex?: number;
|
||||
shouldBackfillPosition: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class QueryRunnerArgsFactory {
|
||||
constructor(private readonly recordPositionFactory: RecordPositionFactory) {}
|
||||
@ -39,8 +44,11 @@ export class QueryRunnerArgsFactory {
|
||||
return {
|
||||
...args,
|
||||
data: await Promise.all(
|
||||
(args as CreateManyResolverArgs).data.map((arg) =>
|
||||
this.overrideDataByFieldMetadata(arg, options, fieldMetadataMap),
|
||||
(args as CreateManyResolverArgs).data.map((arg, index) =>
|
||||
this.overrideDataByFieldMetadata(arg, options, fieldMetadataMap, {
|
||||
argIndex: index,
|
||||
shouldBackfillPosition: true,
|
||||
}),
|
||||
),
|
||||
),
|
||||
} satisfies CreateManyResolverArgs;
|
||||
@ -73,6 +81,7 @@ export class QueryRunnerArgsFactory {
|
||||
(args as FindDuplicatesResolverArgs).data,
|
||||
options,
|
||||
fieldMetadataMap,
|
||||
{ shouldBackfillPosition: false },
|
||||
),
|
||||
};
|
||||
default:
|
||||
@ -84,11 +93,14 @@ export class QueryRunnerArgsFactory {
|
||||
data: Record<string, any> | undefined,
|
||||
options: WorkspaceQueryRunnerOptions,
|
||||
fieldMetadataMap: Map<string, FieldMetadataInterface>,
|
||||
argPositionBackfillInput: ArgPositionBackfillInput,
|
||||
) {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isFieldPositionPresent = false;
|
||||
|
||||
const createArgPromiseByArgKey = Object.entries(data).map(
|
||||
async ([key, value]) => {
|
||||
const fieldMetadata = fieldMetadataMap.get(key);
|
||||
@ -99,6 +111,8 @@ export class QueryRunnerArgsFactory {
|
||||
|
||||
switch (fieldMetadata.type) {
|
||||
case FieldMetadataType.POSITION:
|
||||
isFieldPositionPresent = true;
|
||||
|
||||
return [
|
||||
key,
|
||||
await this.recordPositionFactory.create(
|
||||
@ -108,6 +122,7 @@ export class QueryRunnerArgsFactory {
|
||||
nameSingular: options.objectMetadataItem.nameSingular,
|
||||
},
|
||||
options.workspaceId,
|
||||
argPositionBackfillInput.argIndex,
|
||||
),
|
||||
];
|
||||
case FieldMetadataType.NUMBER:
|
||||
@ -120,6 +135,27 @@ export class QueryRunnerArgsFactory {
|
||||
|
||||
const newArgEntries = await Promise.all(createArgPromiseByArgKey);
|
||||
|
||||
if (
|
||||
!isFieldPositionPresent &&
|
||||
argPositionBackfillInput.shouldBackfillPosition
|
||||
) {
|
||||
return Object.fromEntries([
|
||||
...newArgEntries,
|
||||
[
|
||||
'position',
|
||||
await this.recordPositionFactory.create(
|
||||
'first',
|
||||
{
|
||||
isCustom: options.objectMetadataItem.isCustom,
|
||||
nameSingular: options.objectMetadataItem.nameSingular,
|
||||
},
|
||||
options.workspaceId,
|
||||
argPositionBackfillInput.argIndex,
|
||||
),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
return Object.fromEntries(newArgEntries);
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { isDefined } from 'class-validator';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import {
|
||||
RecordPositionQueryArgs,
|
||||
RecordPositionQueryFactory,
|
||||
RecordPositionQueryType,
|
||||
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
||||
@ -19,40 +20,76 @@ export class RecordPositionFactory {
|
||||
value: number | 'first' | 'last',
|
||||
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||
workspaceId: string,
|
||||
index = 0,
|
||||
): Promise<number> {
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const query = await this.recordPositionQueryFactory.create(
|
||||
RecordPositionQueryType.GET,
|
||||
value,
|
||||
if (typeof value === 'number') {
|
||||
const recordWithSamePosition = await this.findRecordPosition(
|
||||
{
|
||||
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION,
|
||||
positionValue: value,
|
||||
},
|
||||
objectMetadata,
|
||||
dataSourceSchema,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (recordWithSamePosition) {
|
||||
throw new Error('Position is not unique');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value === 'first') {
|
||||
const recordWithMinPosition = await this.findRecordPosition(
|
||||
{
|
||||
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION,
|
||||
},
|
||||
objectMetadata,
|
||||
dataSourceSchema,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return isDefined(recordWithMinPosition?.position)
|
||||
? recordWithMinPosition.position - index - 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
const recordWithMaxPosition = await this.findRecordPosition(
|
||||
{
|
||||
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION,
|
||||
},
|
||||
objectMetadata,
|
||||
dataSourceSchema,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return isDefined(recordWithMaxPosition?.position)
|
||||
? recordWithMaxPosition.position + index + 1
|
||||
: 1;
|
||||
}
|
||||
|
||||
private async findRecordPosition(
|
||||
recordPositionQueryArgs: RecordPositionQueryArgs,
|
||||
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||
dataSourceSchema: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const [query, params] = await this.recordPositionQueryFactory.create(
|
||||
recordPositionQueryArgs,
|
||||
objectMetadata,
|
||||
dataSourceSchema,
|
||||
);
|
||||
|
||||
// If the value was 'first', the first record will be the one with the lowest position
|
||||
// If the value was 'last', the first record will be the one with the highest position
|
||||
const records = await this.workspaceDataSourceService.executeRawQuery(
|
||||
query,
|
||||
[],
|
||||
params,
|
||||
workspaceId,
|
||||
undefined,
|
||||
);
|
||||
|
||||
if (
|
||||
!isDefined(records) ||
|
||||
records.length === 0 ||
|
||||
!isDefined(records[0]?.position)
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return value === 'first'
|
||||
? records[0].position - 1
|
||||
: records[0].position + 1;
|
||||
return records?.[0];
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,16 +31,19 @@ export class RecordPositionBackfillService {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const query = await this.recordPositionQueryFactory.create(
|
||||
RecordPositionQueryType.UPDATE,
|
||||
position,
|
||||
const [query, params] = await this.recordPositionQueryFactory.create(
|
||||
{
|
||||
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION,
|
||||
recordId,
|
||||
positionValue: position,
|
||||
},
|
||||
objectMetadata as ObjectMetadataInterface,
|
||||
dataSourceSchema,
|
||||
);
|
||||
|
||||
this.workspaceDataSourceService.executeRawQuery(
|
||||
query,
|
||||
[position, recordId],
|
||||
params,
|
||||
workspaceId,
|
||||
undefined,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user