Skip to content

Commit de85a04

Browse files
authored
fix: disordered grouping caused by collapsing multi-value field group headers (#1677)
* fix: cached requests in record operations * fix: deletion failure due to collapsedGroupIds params * fix: disordered grouping caused by collapsing multi-value field group headers * chore: update e2e test * chore: e2e testing for isNotExactly
1 parent 9e78049 commit de85a04

21 files changed

Lines changed: 262 additions & 116 deletions

File tree

apps/nestjs-backend/src/db-provider/filter-query/cell-value-filter.abstract.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
hasAllOf,
1515
hasAnyOf,
1616
hasNoneOf,
17+
isNotExactly,
1718
is,
1819
isAfter,
1920
isAnyOf,
@@ -67,6 +68,7 @@ export abstract class AbstractCellValueFilter implements ICellValueFilterInterfa
6768
[isNoneOf.value]: this.isNoneOfOperatorHandler,
6869
[hasNoneOf.value]: this.isNoneOfOperatorHandler,
6970
[hasAllOf.value]: this.hasAllOfOperatorHandler,
71+
[isNotExactly.value]: this.isNotExactlyOperatorHandler,
7072
[isWithIn.value]: this.isWithInOperatorHandler,
7173
[isEmpty.value]: this.isEmptyOperatorHandler,
7274
[isNotEmpty.value]: this.isNotEmptyOperatorHandler,
@@ -193,6 +195,14 @@ export abstract class AbstractCellValueFilter implements ICellValueFilterInterfa
193195
throw new NotImplementedException();
194196
}
195197

198+
isNotExactlyOperatorHandler(
199+
_builderClient: Knex.QueryBuilder,
200+
_operator: IFilterOperator,
201+
_value: IFilterValue
202+
): Knex.QueryBuilder {
203+
throw new NotImplementedException();
204+
}
205+
196206
isWithInOperatorHandler(
197207
_builderClient: Knex.QueryBuilder,
198208
_operator: IFilterOperator,

apps/nestjs-backend/src/db-provider/filter-query/cell-value-filter.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface ICellValueFilterInterface {
1111
isOperatorHandler: ICellValueFilterHandler;
1212
isExactlyOperatorHandler: ICellValueFilterHandler;
1313
isNotOperatorHandler: ICellValueFilterHandler;
14+
isNotExactlyOperatorHandler: ICellValueFilterHandler;
1415
containsOperatorHandler: ICellValueFilterHandler;
1516
doesNotContainOperatorHandler: ICellValueFilterHandler;
1617
isGreaterOperatorHandler: ICellValueFilterHandler;

apps/nestjs-backend/src/db-provider/filter-query/postgres/cell-value-filter/multiple-value/multiple-json-cell-value-filter.adapter.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,35 @@ export class MultipleJsonCellValueFilterAdapter extends CellValueFilterPostgres
142142
return builderClient;
143143
}
144144

145+
isNotExactlyOperatorHandler(
146+
builderClient: Knex.QueryBuilder,
147+
_operator: IFilterOperator,
148+
value: ILiteralValueList
149+
): Knex.QueryBuilder {
150+
const { type } = this.field;
151+
const sqlPlaceholders = this.createSqlPlaceholders(value);
152+
153+
builderClient.where((builder) => {
154+
if (isUserOrLink(type)) {
155+
builder
156+
.whereRaw(
157+
`NOT (jsonb_path_query_array(COALESCE(??, '[]')::jsonb, '$[*].id') @> to_jsonb(ARRAY[${sqlPlaceholders}]) AND to_jsonb(ARRAY[${sqlPlaceholders}]) @> jsonb_path_query_array(COALESCE(??, '[]')::jsonb, '$[*].id'))`,
158+
[this.tableColumnRef, ...value, ...value, this.tableColumnRef]
159+
)
160+
.orWhereNull(this.tableColumnRef);
161+
} else {
162+
builder
163+
.whereRaw(
164+
`NOT (COALESCE(??, '[]')::jsonb @> to_jsonb(ARRAY[${sqlPlaceholders}]) AND to_jsonb(ARRAY[${sqlPlaceholders}]) @> COALESCE(??, '[]')::jsonb)`,
165+
[this.tableColumnRef, ...value, ...value, this.tableColumnRef]
166+
)
167+
.orWhereNull(this.tableColumnRef);
168+
}
169+
});
170+
171+
return builderClient;
172+
}
173+
145174
containsOperatorHandler(
146175
builderClient: Knex.QueryBuilder,
147176
_operator: IFilterOperator,

apps/nestjs-backend/src/db-provider/filter-query/sqlite/cell-value-filter/multiple-value/multiple-json-cell-value-filter.adapter.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,25 @@ export class MultipleJsonCellValueFilterAdapter extends CellValueFilterSqlite {
9494
return builderClient;
9595
}
9696

97+
isNotExactlyOperatorHandler(
98+
builderClient: Knex.QueryBuilder,
99+
operator: IFilterOperator,
100+
value: ILiteralValueList
101+
): Knex.QueryBuilder {
102+
const jsonColumn = this.getJsonQueryColumn(this.field, operator);
103+
const isNotExactlySql = `NOT ((
104+
select count(${jsonColumn}) from
105+
json_each(${this.tableColumnRef})
106+
where ${jsonColumn} in (${this.createSqlPlaceholders(value)})
107+
) >= ? AND (
108+
select count(distinct ${jsonColumn}) from
109+
json_each(${this.tableColumnRef})
110+
) = ?)`;
111+
112+
builderClient.whereRaw(isNotExactlySql, [...value, value.length, value.length]);
113+
return builderClient;
114+
}
115+
97116
containsOperatorHandler(
98117
builderClient: Knex.QueryBuilder,
99118
operator: IFilterOperator,

apps/nestjs-backend/src/utils/filter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
isNot,
66
is,
77
isNotEmpty,
8-
hasNoneOf,
8+
isNotExactly,
99
CellValueType,
1010
exactFormatDate,
1111
} from '@teable/core';
@@ -70,7 +70,7 @@ export const generateFilterItem = (field: IFieldInstance, value: unknown) => {
7070
timeZone,
7171
};
7272
} else if (SPECIAL_OPERATOR_FIELD_TYPE_SET.has(type) && isMultipleCellValue) {
73-
operator = hasNoneOf.value;
73+
operator = isNotExactly.value;
7474
}
7575

7676
return {

apps/nestjs-backend/test/data-helpers/caces/record-filter-query/multiple-select-field.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import { hasAllOf, hasAnyOf, hasNoneOf, isEmpty, isExactly, isNotEmpty } from '@teable/core';
1+
import {
2+
hasAllOf,
3+
hasAnyOf,
4+
hasNoneOf,
5+
isNotExactly,
6+
isEmpty,
7+
isExactly,
8+
isNotEmpty,
9+
} from '@teable/core';
210

311
export const MULTIPLE_SELECT_FIELD_CASES = [
412
{
@@ -43,6 +51,13 @@ export const MULTIPLE_SELECT_FIELD_CASES = [
4351
expectResultLength: 1,
4452
expectMoreResults: false,
4553
},
54+
{
55+
fieldIndex: 6,
56+
operator: isNotExactly.value,
57+
queryValue: ['rap', 'rock'],
58+
expectResultLength: 22,
59+
expectMoreResults: true,
60+
},
4661
];
4762

4863
export const MULTIPLE_SELECT_LOOKUP_FIELD_CASES = [
@@ -88,4 +103,11 @@ export const MULTIPLE_SELECT_LOOKUP_FIELD_CASES = [
88103
expectResultLength: 1,
89104
expectMoreResults: false,
90105
},
106+
{
107+
fieldIndex: 9,
108+
operator: isNotExactly.value,
109+
queryValue: ['rap'],
110+
expectResultLength: 20,
111+
expectMoreResults: false,
112+
},
91113
];

apps/nestjs-backend/test/data-helpers/caces/record-filter-query/user-field.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
isNoneOf,
1010
isNot,
1111
isNotEmpty,
12+
isNotExactly,
1213
Me,
1314
} from '@teable/core';
1415

@@ -107,6 +108,13 @@ export const MULTIPLE_USER_FIELD_CASES = [
107108
expectResultLength: 1,
108109
expectMoreResults: true,
109110
},
111+
{
112+
fieldIndex: 7,
113+
operator: isNotExactly.value,
114+
queryValue: ['usrTestUserId', 'usrTestUserId_1'],
115+
expectResultLength: 22,
116+
expectMoreResults: true,
117+
},
110118
{
111119
fieldIndex: 7,
112120
operator: hasNoneOf.value,
@@ -211,6 +219,13 @@ export const MULTIPLE_USER_LOOKUP_FIELD_CASES = [
211219
expectResultLength: 5,
212220
expectMoreResults: true,
213221
},
222+
{
223+
fieldIndex: 10,
224+
operator: isNotExactly.value,
225+
queryValue: ['usrTestUserId', 'usrTestUserId_1'],
226+
expectResultLength: 16,
227+
expectMoreResults: true,
228+
},
214229
{
215230
fieldIndex: 10,
216231
operator: hasNoneOf.value,

apps/nextjs-app/src/features/app/blocks/view/calendar/components/AddEventButton.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,19 @@ export const AddEventButton = (props: IAddEventButtonProps) => {
2727

2828
if (!tableId || !startDateField || !endDateField) return;
2929

30-
const { data } = await createRecords(tableId, {
31-
fieldKeyType: FieldKeyType.Id,
32-
records: [
33-
{
34-
fields: {
35-
[startDateField.id]: date.toISOString(),
36-
[endDateField.id]: date.toISOString(),
30+
const { data } = await createRecords({
31+
tableId,
32+
recordsRo: {
33+
fieldKeyType: FieldKeyType.Id,
34+
records: [
35+
{
36+
fields: {
37+
[startDateField.id]: date.toISOString(),
38+
[endDateField.id]: date.toISOString(),
39+
},
3740
},
38-
},
39-
],
41+
],
42+
},
4043
});
4144

4245
setExpandRecordId?.(data.records[0].id);

apps/nextjs-app/src/features/app/blocks/view/calendar/components/Calendar.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,16 +137,19 @@ export const Calendar = (props: ICalendarProps) => {
137137
const newDate = set(date, { hours: 0, minutes: 0, seconds: 0, milliseconds: 0 });
138138
const newDateStr = fromZonedTime(newDate, timeZone).toISOString();
139139

140-
const { data } = await createRecords(tableId, {
141-
fieldKeyType: FieldKeyType.Id,
142-
records: [
143-
{
144-
fields: {
145-
[startDateField.id]: newDateStr,
146-
[endDateField.id]: newDateStr,
140+
const { data } = await createRecords({
141+
tableId,
142+
recordsRo: {
143+
fieldKeyType: FieldKeyType.Id,
144+
records: [
145+
{
146+
fields: {
147+
[startDateField.id]: newDateStr,
148+
[endDateField.id]: newDateStr,
149+
},
147150
},
148-
},
149-
],
151+
],
152+
},
150153
});
151154

152155
setExpandRecordId?.(data.records[0].id);

apps/nextjs-app/src/features/app/blocks/view/form/FormViewBase.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@ export const FormViewBase = () => {
2424

2525
const submitForm = async (fields: Record<string, unknown>) => {
2626
if (!tableId) return;
27-
await createRecords(tableId, {
28-
fieldKeyType: FieldKeyType.Id,
29-
records: [
30-
{
31-
fields,
32-
},
33-
],
27+
await createRecords({
28+
tableId,
29+
recordsRo: {
30+
fieldKeyType: FieldKeyType.Id,
31+
records: [{ fields }],
32+
},
3433
});
3534
};
3635

0 commit comments

Comments
 (0)