Skip to content

Commit bfc4202

Browse files
authored
fix: correct paste misalignment when using shuffled projection (T1329) (#2325)
* fix: correct paste misalignment when using shuffled projection * fix: form share collaborators user fields
1 parent 1b990f9 commit bfc4202

3 files changed

Lines changed: 140 additions & 3 deletions

File tree

apps/nestjs-backend/src/features/field/field.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ export class FieldService implements IReadonlyAdapterService {
841841
if (query?.projection) {
842842
const fieldIds = query.projection;
843843
const fieldMap = keyBy(result, 'id');
844-
result = fieldIds.map((fieldId) => fieldMap[fieldId]).filter(Boolean);
844+
return fieldIds.map((fieldId) => fieldMap[fieldId]).filter(Boolean);
845845
}
846846

847847
/**

apps/nestjs-backend/src/features/share/share.service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,11 +557,13 @@ export class ShareService {
557557
});
558558
}
559559

560-
const fields = await this.fieldService.getFieldsByQuery(tableId, {
560+
let fields = await this.fieldService.getFieldsByQuery(tableId, {
561561
viewId: view?.id,
562562
filterHidden: !view?.shareMeta?.includeHiddenField,
563-
projection: query?.fieldId ? [query.fieldId] : undefined,
564563
});
564+
if (query?.fieldId) {
565+
fields = fields.filter((field) => field.id === query.fieldId);
566+
}
565567
// If there is no user field, return an empty array
566568
if (
567569
!fields.some((field) =>

apps/nestjs-backend/test/selection.e2e-spec.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,4 +1034,139 @@ describe('OpenAPI SelectionController (e2e)', () => {
10341034
undefined,
10351035
]);
10361036
});
1037+
1038+
describe('paste with projection', () => {
1039+
let projectionTable: ITableFullVo;
1040+
1041+
beforeEach(async () => {
1042+
// Create a table with 4 fields: A, B, C, D
1043+
projectionTable = await createTable(baseId, {
1044+
name: 'projection-table',
1045+
fields: [
1046+
{ name: 'Field A', type: FieldType.SingleLineText },
1047+
{ name: 'Field B', type: FieldType.SingleLineText },
1048+
{ name: 'Field C', type: FieldType.SingleLineText },
1049+
{ name: 'Field D', type: FieldType.SingleLineText },
1050+
],
1051+
records: [
1052+
{ fields: { 'Field A': 'A1', 'Field B': 'B1', 'Field C': 'C1', 'Field D': 'D1' } },
1053+
{ fields: { 'Field A': 'A2', 'Field B': 'B2', 'Field C': 'C2', 'Field D': 'D2' } },
1054+
],
1055+
});
1056+
});
1057+
1058+
afterEach(async () => {
1059+
await permanentDeleteTable(baseId, projectionTable.id);
1060+
});
1061+
1062+
it('should paste correctly when projection order is shuffled', async () => {
1063+
const fieldA = projectionTable.fields.find((f) => f.name === 'Field A')!;
1064+
const fieldB = projectionTable.fields.find((f) => f.name === 'Field B')!;
1065+
const fieldC = projectionTable.fields.find((f) => f.name === 'Field C')!;
1066+
const fieldD = projectionTable.fields.find((f) => f.name === 'Field D')!;
1067+
1068+
// Projection order is shuffled: D, B, A (skip C)
1069+
// Original order in table: A, B, C, D
1070+
const projection = [fieldD.id, fieldB.id, fieldA.id];
1071+
1072+
// Paste 3 columns of data: should map to D, B, A respectively
1073+
await apiPaste(projectionTable.id, {
1074+
viewId: projectionTable.views[0].id,
1075+
content: 'NewD1\tNewB1\tNewA1',
1076+
ranges: [
1077+
[0, 0],
1078+
[0, 0],
1079+
],
1080+
projection,
1081+
});
1082+
1083+
const recordsData = await getRecords(projectionTable.id, {
1084+
viewId: projectionTable.views[0].id,
1085+
fieldKeyType: FieldKeyType.Id,
1086+
});
1087+
1088+
const firstRecord = recordsData.data.records[0];
1089+
1090+
// Verify: should update according to projection order
1091+
expect(firstRecord.fields[fieldA.id]).toBe('NewA1'); // projection column 3
1092+
expect(firstRecord.fields[fieldB.id]).toBe('NewB1'); // projection column 2
1093+
expect(firstRecord.fields[fieldC.id]).toBe('C1'); // not in projection, should remain unchanged
1094+
expect(firstRecord.fields[fieldD.id]).toBe('NewD1'); // projection column 1
1095+
});
1096+
1097+
it('should paste correctly when projection order is reversed', async () => {
1098+
const fieldA = projectionTable.fields.find((f) => f.name === 'Field A')!;
1099+
const fieldB = projectionTable.fields.find((f) => f.name === 'Field B')!;
1100+
const fieldC = projectionTable.fields.find((f) => f.name === 'Field C')!;
1101+
const fieldD = projectionTable.fields.find((f) => f.name === 'Field D')!;
1102+
1103+
// Projection completely reversed: D, C, B, A
1104+
const projection = [fieldD.id, fieldC.id, fieldB.id, fieldA.id];
1105+
1106+
// Paste 2x2 data
1107+
await apiPaste(projectionTable.id, {
1108+
viewId: projectionTable.views[0].id,
1109+
content: 'NewD1\tNewC1\nNewD2\tNewC2',
1110+
ranges: [
1111+
[0, 0],
1112+
[1, 1],
1113+
],
1114+
projection,
1115+
});
1116+
1117+
const recordsData = await getRecords(projectionTable.id, {
1118+
viewId: projectionTable.views[0].id,
1119+
fieldKeyType: FieldKeyType.Id,
1120+
});
1121+
1122+
// Verify first row: column 0 (index 0) maps to D, column 1 (index 1) maps to C
1123+
const firstRecord = recordsData.data.records[0];
1124+
expect(firstRecord.fields[fieldA.id]).toBe('A1'); // not in paste range, should remain unchanged
1125+
expect(firstRecord.fields[fieldB.id]).toBe('B1'); // not in paste range, should remain unchanged
1126+
expect(firstRecord.fields[fieldC.id]).toBe('NewC1');
1127+
expect(firstRecord.fields[fieldD.id]).toBe('NewD1');
1128+
1129+
// Verify second row
1130+
const secondRecord = recordsData.data.records[1];
1131+
expect(secondRecord.fields[fieldA.id]).toBe('A2');
1132+
expect(secondRecord.fields[fieldB.id]).toBe('B2');
1133+
expect(secondRecord.fields[fieldC.id]).toBe('NewC2');
1134+
expect(secondRecord.fields[fieldD.id]).toBe('NewD2');
1135+
});
1136+
1137+
it('should paste to correct field when using shuffled projection with column offset', async () => {
1138+
const fieldA = projectionTable.fields.find((f) => f.name === 'Field A')!;
1139+
const fieldB = projectionTable.fields.find((f) => f.name === 'Field B')!;
1140+
const fieldC = projectionTable.fields.find((f) => f.name === 'Field C')!;
1141+
const fieldD = projectionTable.fields.find((f) => f.name === 'Field D')!;
1142+
1143+
// Projection shuffled order: C, A, D
1144+
const projection = [fieldC.id, fieldA.id, fieldD.id];
1145+
1146+
// Paste to column index 1 (maps to Field A in projection)
1147+
await apiPaste(projectionTable.id, {
1148+
viewId: projectionTable.views[0].id,
1149+
content: 'UpdatedA1',
1150+
ranges: [
1151+
[1, 0],
1152+
[1, 0],
1153+
],
1154+
projection,
1155+
});
1156+
1157+
const recordsData = await getRecords(projectionTable.id, {
1158+
viewId: projectionTable.views[0].id,
1159+
fieldKeyType: FieldKeyType.Id,
1160+
});
1161+
1162+
const firstRecord = recordsData.data.records[0];
1163+
1164+
// Field A should be updated (projection index 1)
1165+
expect(firstRecord.fields[fieldA.id]).toBe('UpdatedA1');
1166+
// Other fields should remain unchanged
1167+
expect(firstRecord.fields[fieldB.id]).toBe('B1');
1168+
expect(firstRecord.fields[fieldC.id]).toBe('C1');
1169+
expect(firstRecord.fields[fieldD.id]).toBe('D1');
1170+
});
1171+
});
10371172
});

0 commit comments

Comments
 (0)