Skip to content

Commit

Permalink
fix: update link field error (#230)
Browse files Browse the repository at this point in the history
* chore: unified localStorage key

* fix: update primary field cell value may cause error link cell title

* fix: update two same link fields cause error
  • Loading branch information
tea-artist authored Oct 29, 2023
1 parent 2a02d5f commit 401f6a7
Show file tree
Hide file tree
Showing 12 changed files with 255 additions and 50 deletions.
8 changes: 7 additions & 1 deletion apps/nestjs-backend/src/features/calculation/link.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,14 @@ export class LinkService {
const recordMapByTableId: IRecordMapByTableId = {};
// include main table update message, and foreign table update will reflect to main table
const updateForeignKeyParams: IUpdateForeignKeyParam[] = [];
const checkSet = new Set<string>();
const checkSetMap: { [fieldId: string]: Set<string> } = {};
function pushForeignKeyParam(param: IUpdateForeignKeyParam) {
let checkSet = checkSetMap[param.mainLinkFieldId];
if (!checkSet) {
checkSet = new Set<string>();
checkSetMap[param.mainLinkFieldId] = checkSet;
}

if (param.fRecordId) {
if (checkSet.has(param.recordId)) {
throw new BadRequestException(
Expand Down
33 changes: 16 additions & 17 deletions apps/nestjs-backend/src/features/calculation/reference.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, Injectable, Logger } from '@nestjs/common';
import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common';
import type {
IFieldVo,
ILinkCellValue,
Expand Down Expand Up @@ -584,7 +584,7 @@ export class ReferenceService {
return this.calculateFormula(field, fieldMap, recordItem);
}

throw new Error(`Unsupported field type ${field.type}`);
throw new BadRequestException(`Unsupported field type ${field.type}`);
}

private calculateFormula(field: FormulaFieldDto, fieldMap: IFieldMap, recordItem: IRecordItem) {
Expand Down Expand Up @@ -617,18 +617,17 @@ export class ReferenceService {

if (Array.isArray(dependencies)) {
// sort lookup values by link cell order
if (field.lookupOptions) {
const linkFieldId = field.lookupOptions.linkFieldId;
const linkCellValues = recordItem.record.fields[linkFieldId] as ILinkCellValue[];

const dependenciesIndexed = keyBy(dependencies, 'id');
// when delete a link cell, the link cell value will be null
// but dependencies will still be there in the first round calculation
if (linkCellValues) {
dependencies = linkCellValues.map((v) => {
return dependenciesIndexed[v.id];
});
}
const linkFieldId = field.lookupOptions ? field.lookupOptions.linkFieldId : field.id;

const linkCellValues = recordItem.record.fields[linkFieldId] as ILinkCellValue[];

const dependenciesIndexed = keyBy(dependencies, 'id');
// when delete a link cell, the link cell value will be null
// but dependencies will still be there in the first round calculation
if (linkCellValues) {
dependencies = linkCellValues.map((v) => {
return dependenciesIndexed[v.id];
});
}

return dependencies
Expand All @@ -654,7 +653,7 @@ export class ReferenceService {
lookupValues: unknown
): unknown {
if (field.type !== FieldType.Link && field.type !== FieldType.Rollup) {
throw new Error('rollup only support link and rollup field currently');
throw new BadRequestException('rollup only support link and rollup field currently');
}

const fieldVo = instanceToPlain(lookupField, { excludePrefixes: ['_'] }) as IFieldVo;
Expand Down Expand Up @@ -994,7 +993,7 @@ export class ReferenceService {
affectedRecordItems,
});
}
throw new Error('Unsupported relationship');
throw new BadRequestException('Unsupported relationship');
});
return records
.map((record, i) => ({ record, dependencies: dependenciesArr[i] }))
Expand Down Expand Up @@ -1110,7 +1109,7 @@ export class ReferenceService {
const records = allRecordByDbTableName[cover.dbTableName];
const record = records.find((r) => r.id === cover.id);
if (!record) {
throw new Error(`Can not find record: ${cover.id} in DB`);
throw new BadRequestException(`Can not find record: ${cover.id} in database`);
}
record.fields[cover.fieldId] = cover.newValue;
});
Expand Down
205 changes: 204 additions & 1 deletion apps/nestjs-backend/test/link-api.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import type { INestApplication } from '@nestjs/common';
import type {
IFieldRo,
ILinkFieldOptions,
IRecord,
ITableFullVo,
IUpdateRecordRo,
} from '@teable-group/core';
import { FieldType, Relationship, NumberFormattingType } from '@teable-group/core';
import { FieldType, Relationship, NumberFormattingType, FieldKeyType } from '@teable-group/core';
import type request from 'supertest';
import {
initApp,
Expand Down Expand Up @@ -805,6 +806,59 @@ describe('OpenAPI link (e2e)', () => {
},
]);
});

it('should update link cellValue when change primary field value', async () => {
await updateRecordByApi(request, table2.id, table2.records[0].id, table2.fields[0].id, 'B1');
await updateRecordByApi(request, table2.id, table2.records[1].id, table2.fields[0].id, 'B2');

await updateRecordByApi(request, table1.id, table1.records[0].id, table1.fields[2].id, [
{
id: table2.records[0].id,
},
{
id: table2.records[1].id,
},
]);

await updateRecordByApi(request, table2.id, table2.records[0].id, table2.fields[0].id, 'B1+');

const record1 = await getRecord(request, table1.id, table1.records[0].id);

expect(record1.fields[table1.fields[2].id]).toEqual([
{
title: 'B1+',
id: table2.records[0].id,
},
{
title: 'B2',
id: table2.records[1].id,
},
]);

await updateRecordByApi(request, table2.id, table2.records[1].id, table2.fields[0].id, 'B2+');
const record2 = await getRecord(request, table1.id, table1.records[0].id);
expect(record2.fields[table1.fields[2].id]).toEqual([
{
title: 'B1+',
id: table2.records[0].id,
},
{
title: 'B2+',
id: table2.records[1].id,
},
]);
});

it('should not insert illegal value in link cel', async () => {
await updateRecordByApi(
request,
table1.id,
table1.records[0].id,
table1.fields[2].id,
['NO'],
400
);
});
});

describe('multi link with depends same field', () => {
Expand Down Expand Up @@ -1087,4 +1141,153 @@ describe('OpenAPI link (e2e)', () => {
expect(table2Record.fields[symOneManyField.id]).toBeUndefined();
});
});

describe('Create two bi-link for two tables', () => {
let table1: ITableFullVo;
let table2: ITableFullVo;
beforeEach(async () => {
// create tables
const textFieldRo: IFieldRo = {
name: 'text field',
type: FieldType.SingleLineText,
};

const createTable1Result = await request
.post(`/api/base/${baseId}/table`)
.send({
name: 'table1',
fields: [textFieldRo],
records: [
{ fields: { 'text field': 'table1_1' } },
{ fields: { 'text field': 'table1_2' } },
{ fields: { 'text field': 'table1_3' } },
],
})
.expect(201);

table1 = createTable1Result.body;

const createTable2Result = await request
.post(`/api/base/${baseId}/table`)
.send({
name: 'table2',
fields: [textFieldRo],
records: [
{ fields: { 'text field': 'table2_1' } },
{ fields: { 'text field': 'table2_2' } },
{ fields: { 'text field': 'table2_3' } },
],
})
.expect(201);

table2 = createTable2Result.body;
});

afterEach(async () => {
await request.delete(`/api/base/${baseId}/table/arbitrary/${table1.id}`);
await request.delete(`/api/base/${baseId}/table/arbitrary/${table2.id}`);
});

it('should update record in two same manyOne link', async () => {
// create link field
const table1LinkFieldRo: IFieldRo = {
name: 'link field',
type: FieldType.Link,
options: {
relationship: Relationship.ManyOne,
foreignTableId: table2.id,
},
};

await createField(request, table1.id, table1LinkFieldRo);
await createField(request, table1.id, table1LinkFieldRo);

const getFields1Result = await request.get(`/api/table/${table1.id}/field`).expect(200);
const getFields2Result = await request.get(`/api/table/${table2.id}/field`).expect(200);

table1.fields = getFields1Result.body;
table2.fields = getFields2Result.body;

const result = await request
.put(`/api/table/${table1.id}/record/${table1.records[0].id}`)
.send({
fieldKeyType: FieldKeyType.Id,
record: {
fields: {
[table1.fields[1].id]: {
id: table2.records[0].id,
},
[table1.fields[2].id]: {
id: table2.records[0].id,
},
},
},
} as IUpdateRecordRo)
.expect(200);
const record = result.body as IRecord;
expect(record.fields[table1.fields[1].id]).toEqual({
id: table2.records[0].id,
title: 'table2_1',
});
expect(record.fields[table1.fields[2].id]).toEqual({
id: table2.records[0].id,
title: 'table2_1',
});
});

it('should update record in two same oneMany link', async () => {
// create link field
const table1LinkFieldRo: IFieldRo = {
name: 'link field',
type: FieldType.Link,
options: {
relationship: Relationship.OneMany,
foreignTableId: table2.id,
},
};

await createField(request, table1.id, table1LinkFieldRo);
await createField(request, table1.id, table1LinkFieldRo);

const getFields1Result = await request.get(`/api/table/${table1.id}/field`).expect(200);
const getFields2Result = await request.get(`/api/table/${table2.id}/field`).expect(200);

table1.fields = getFields1Result.body;
table2.fields = getFields2Result.body;

const result = await request
.put(`/api/table/${table1.id}/record/${table1.records[0].id}`)
.send({
fieldKeyType: FieldKeyType.Id,
record: {
fields: {
[table1.fields[1].id]: [
{
id: table2.records[0].id,
},
],
[table1.fields[2].id]: [
{
id: table2.records[0].id,
},
],
},
},
} as IUpdateRecordRo)
.expect(200);
const record = result.body as IRecord;
expect(record.fields[table1.fields[1].id]).toEqual([
{
id: table2.records[0].id,
title: 'table2_1',
},
]);
expect(record.fields[table1.fields[2].id]).toEqual([
{
id: table2.records[0].id,
title: 'table2_1',
},
]);
});
});
});
16 changes: 8 additions & 8 deletions apps/nestjs-backend/test/reference.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,11 @@ describe('Reference Service (e2e)', () => {
fields: { fieldC: 'C2', manyToOneB: { title: 'C1, C2', id: 'idB1' } },
recordOrder: {},
},
idC3: {
id: 'idC3',
fields: { fieldC: 'C3', manyToOneB: { title: 'C3', id: 'idB2' } },
recordOrder: {},
},
idB1: {
id: 'idB1',
fields: {
Expand All @@ -533,18 +538,13 @@ describe('Reference Service (e2e)', () => {
},
recordOrder: {},
},
idC3: {
id: 'idC3',
fields: { fieldC: 'C3', manyToOneB: { title: 'C3', id: 'idC3' } },
recordOrder: {},
},
idA1: {
id: 'idA1',
fields: {
fieldA: 'A1',
oneToManyB: [
{ title: 'C1, C2', id: 'idB1' },
{ title: 'C3', id: 'idC3' },
{ title: 'C3', id: 'idB2' },
],
},
recordOrder: {},
Expand Down Expand Up @@ -648,11 +648,11 @@ describe('Reference Service (e2e)', () => {
fieldId: 'oneToManyB',
oldValue: [
{ title: 'C1, C2', id: 'idB1' },
{ title: 'C3', id: 'idC3' },
{ title: 'C3', id: 'idB2' },
],
newValue: [
{ title: 'CX, C2', id: 'idB1' },
{ title: 'C3', id: 'idC3' },
{ title: 'C3', id: 'idB2' },
],
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Loader2 } from '@teable-group/icons';
import { LocalStorageKeys } from '@teable-group/sdk/config';
import { useFields, useTable, useView } from '@teable-group/sdk/hooks';
import type { FormView } from '@teable-group/sdk/model';
import { Button, cn, useToast } from '@teable-group/ui-lib/shadcn';
import { omit } from 'lodash';
import { useMemo, useRef, useState } from 'react';
import { useLocalStorage, useMap, useSet } from 'react-use';
import { FORM_VIEW_DATA_LOCAL_STORAGE_KEY } from '../constant';
import { generateUniqLocalKey } from '../util';
import { FormField } from './FormField';

Expand All @@ -16,7 +16,7 @@ export const FormPreviewer = () => {
const { toast } = useToast();
const localKey = generateUniqLocalKey(table?.id, view?.id);
const [formDataMap, setFormDataMap] = useLocalStorage<Record<string, Record<string, unknown>>>(
FORM_VIEW_DATA_LOCAL_STORAGE_KEY,
LocalStorageKeys.ViewFromData,
{}
);
const [formData, { set: setFormData, reset: resetFormData }] = useMap<Record<string, unknown>>(
Expand Down
2 changes: 0 additions & 2 deletions apps/nextjs-app/src/features/app/blocks/view/form/constant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
export const FORM_VIEW_DATA_LOCAL_STORAGE_KEY = '__t-form-data';

export const FORM_SIDEBAR_DROPPABLE_ID = 'form-sidebar';
export const FORM_EDITOR_DROPPABLE_ID = 'form-editor';
Loading

0 comments on commit 401f6a7

Please sign in to comment.