Implements new record sort CRUD (#10448)
This PR implements new record sorts CRUD as already done on record filters, which is based on record sorts state instead of combined view sorts. It implements a new useSaveRecordSortsToViewSorts with its underlying utils, to compute diff between two view sorts array. The associated unit tests have also been written. This PR also fixes the bug where the view bar disappeared when deleting the already saved record sort of a view.
This commit is contained in:
@ -0,0 +1,40 @@
|
||||
import { RecordSortDirection } from '@/object-record/record-sort/types/RecordSortDirection';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { areViewSortsEqual } from '@/views/utils/areViewSortsEqual';
|
||||
|
||||
describe('areViewSortsEqual', () => {
|
||||
const baseSort: ViewSort = {
|
||||
__typename: 'ViewSort',
|
||||
id: 'sort-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
direction: 'asc',
|
||||
};
|
||||
|
||||
it('should return true when all comparable properties are equal', () => {
|
||||
const sortA = { ...baseSort };
|
||||
const sortB = { ...baseSort };
|
||||
|
||||
expect(areViewSortsEqual(sortA, sortB)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when displayValue is different', () => {
|
||||
const sortA = { ...baseSort };
|
||||
const sortB = { ...baseSort, direction: 'desc' as RecordSortDirection };
|
||||
|
||||
expect(areViewSortsEqual(sortA, sortB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when fieldMetadataId is different', () => {
|
||||
const sortA = { ...baseSort };
|
||||
const sortB = { ...baseSort, fieldMetadataId: 'field-2' };
|
||||
|
||||
expect(areViewSortsEqual(sortA, sortB)).toBe(false);
|
||||
});
|
||||
|
||||
it('should ignore non-comparable properties', () => {
|
||||
const sortA = { ...baseSort, id: 'id-1', createdAt: '2023-01-01' };
|
||||
const sortB = { ...baseSort, id: 'id-2', createdAt: '2023-01-02' };
|
||||
|
||||
expect(areViewSortsEqual(sortA, sortB)).toBe(true);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,84 @@
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { getViewSortsToCreate } from '../getViewSortsToCreate';
|
||||
|
||||
describe('getViewSortsToCreate', () => {
|
||||
const baseSort: ViewSort = {
|
||||
__typename: 'ViewSort',
|
||||
id: 'sort-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
direction: 'asc',
|
||||
};
|
||||
|
||||
it('should return all sorts when current sorts array is empty', () => {
|
||||
const currentViewSorts: ViewSort[] = [];
|
||||
const newViewSorts: ViewSort[] = [
|
||||
{ ...baseSort },
|
||||
{
|
||||
...baseSort,
|
||||
id: 'sort-2',
|
||||
fieldMetadataId: 'field-2',
|
||||
} satisfies ViewSort,
|
||||
];
|
||||
|
||||
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual(newViewSorts);
|
||||
});
|
||||
|
||||
it('should return empty array when new sorts array is empty', () => {
|
||||
const currentViewSorts: ViewSort[] = [baseSort];
|
||||
const newViewSorts: ViewSort[] = [];
|
||||
|
||||
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return only sorts that do not exist in current sorts', () => {
|
||||
const existingSort = { ...baseSort };
|
||||
const newSortWithDifferentFieldMetadataId = {
|
||||
...baseSort,
|
||||
id: 'sort-2',
|
||||
fieldMetadataId: 'field-2',
|
||||
} satisfies ViewSort;
|
||||
|
||||
const currentViewSorts: ViewSort[] = [existingSort];
|
||||
|
||||
const newViewSorts: ViewSort[] = [
|
||||
existingSort,
|
||||
newSortWithDifferentFieldMetadataId,
|
||||
];
|
||||
|
||||
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([newSortWithDifferentFieldMetadataId]);
|
||||
});
|
||||
|
||||
it('should handle sorts with different fieldMetadataIds', () => {
|
||||
const existingSort = { ...baseSort };
|
||||
const sortWithDifferentFieldMetadataId = {
|
||||
...baseSort,
|
||||
fieldMetadataId: 'group-2',
|
||||
} satisfies ViewSort;
|
||||
|
||||
const currentViewSorts: ViewSort[] = [existingSort];
|
||||
|
||||
const newViewSorts: ViewSort[] = [
|
||||
existingSort,
|
||||
sortWithDifferentFieldMetadataId,
|
||||
];
|
||||
|
||||
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([sortWithDifferentFieldMetadataId]);
|
||||
});
|
||||
|
||||
it('should handle empty arrays for both inputs', () => {
|
||||
const currentViewSorts: ViewSort[] = [];
|
||||
const newViewSorts: ViewSort[] = [];
|
||||
|
||||
const result = getViewSortsToCreate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,72 @@
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { getViewSortsToDelete } from '../getViewSortsToDelete';
|
||||
|
||||
describe('getViewSortsToDelete', () => {
|
||||
const baseSort: ViewSort = {
|
||||
__typename: 'ViewSort',
|
||||
id: 'sort-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
direction: 'asc',
|
||||
};
|
||||
|
||||
it('should return empty array when current sorts array is empty', () => {
|
||||
const currentViewSorts: ViewSort[] = [];
|
||||
const newViewSorts: ViewSort[] = [baseSort];
|
||||
|
||||
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return all current sorts when new sorts array is empty', () => {
|
||||
const existingSort = { ...baseSort };
|
||||
const currentViewSorts: ViewSort[] = [existingSort];
|
||||
const newViewSorts: ViewSort[] = [];
|
||||
|
||||
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([existingSort]);
|
||||
});
|
||||
|
||||
it('should return sorts that exist in current but not in new sorts', () => {
|
||||
const sortToDelete = { ...baseSort };
|
||||
const sortToKeep = {
|
||||
...baseSort,
|
||||
id: 'filter-2',
|
||||
fieldMetadataId: 'field-2',
|
||||
} satisfies ViewSort;
|
||||
|
||||
const currentViewSorts: ViewSort[] = [sortToDelete, sortToKeep];
|
||||
const newViewSorts: ViewSort[] = [sortToKeep];
|
||||
|
||||
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([sortToDelete]);
|
||||
});
|
||||
|
||||
it('should handle empty arrays for both inputs', () => {
|
||||
const currentViewSorts: ViewSort[] = [];
|
||||
const newViewSorts: ViewSort[] = [];
|
||||
|
||||
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not delete sorts that match in both fieldMetadataId and direction', () => {
|
||||
const existingSort = { ...baseSort };
|
||||
const matchingSort = {
|
||||
__typename: 'ViewSort',
|
||||
id: 'sort-2',
|
||||
fieldMetadataId: 'field-1',
|
||||
direction: 'asc',
|
||||
} satisfies ViewSort;
|
||||
|
||||
const currentViewSorts: ViewSort[] = [existingSort];
|
||||
const newViewSorts: ViewSort[] = [matchingSort];
|
||||
|
||||
const result = getViewSortsToDelete(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,66 @@
|
||||
import { RecordSortDirection } from '@/object-record/record-sort/types/RecordSortDirection';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { getViewSortsToUpdate } from '../getViewSortsToUpdate';
|
||||
|
||||
describe('getViewSortsToUpdate', () => {
|
||||
const baseSort: ViewSort = {
|
||||
__typename: 'ViewSort',
|
||||
id: 'sort-1',
|
||||
fieldMetadataId: 'field-1',
|
||||
direction: 'asc' as RecordSortDirection,
|
||||
};
|
||||
|
||||
it('should return empty array when current sorts array is empty', () => {
|
||||
const currentViewSorts: ViewSort[] = [];
|
||||
const newViewSorts: ViewSort[] = [baseSort];
|
||||
|
||||
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return empty array when new sorts array is empty', () => {
|
||||
const currentViewSorts: ViewSort[] = [baseSort];
|
||||
const newViewSorts: ViewSort[] = [];
|
||||
|
||||
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return sorts that exist in both arrays but have different direction', () => {
|
||||
const existingSort = { ...baseSort };
|
||||
const updatedSort = {
|
||||
...baseSort,
|
||||
direction: 'desc',
|
||||
} satisfies ViewSort;
|
||||
|
||||
const currentViewSorts: ViewSort[] = [existingSort];
|
||||
const newViewSorts: ViewSort[] = [updatedSort];
|
||||
|
||||
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([updatedSort]);
|
||||
});
|
||||
|
||||
it('should not return sorts that exist in both arrays with same values', () => {
|
||||
const existingSort = { ...baseSort };
|
||||
const sameSort = { ...baseSort };
|
||||
|
||||
const currentViewSorts: ViewSort[] = [existingSort];
|
||||
const newViewSorts: ViewSort[] = [sameSort];
|
||||
|
||||
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle empty arrays for both inputs', () => {
|
||||
const currentViewSorts: ViewSort[] = [];
|
||||
const newViewSorts: ViewSort[] = [];
|
||||
|
||||
const result = getViewSortsToUpdate(currentViewSorts, newViewSorts);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,12 @@
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
|
||||
export const areViewSortsEqual = (viewSortA: ViewSort, viewSortB: ViewSort) => {
|
||||
const propertiesToCompare: (keyof ViewSort)[] = [
|
||||
'fieldMetadataId',
|
||||
'direction',
|
||||
];
|
||||
|
||||
return propertiesToCompare.every(
|
||||
(property) => viewSortA[property] === viewSortB[property],
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,18 @@
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const getViewSortsToCreate = (
|
||||
currentViewSorts: ViewSort[],
|
||||
newViewSorts: ViewSort[],
|
||||
) => {
|
||||
return newViewSorts.filter((newViewSort) => {
|
||||
const correspondingViewSort = currentViewSorts.find(
|
||||
(currentViewSort) =>
|
||||
currentViewSort.fieldMetadataId === newViewSort.fieldMetadataId,
|
||||
);
|
||||
|
||||
const shouldCreateBecauseViewSortIsNew = !isDefined(correspondingViewSort);
|
||||
|
||||
return shouldCreateBecauseViewSortIsNew;
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
|
||||
export const getViewSortsToDelete = (
|
||||
currentViewSorts: ViewSort[],
|
||||
newViewSorts: ViewSort[],
|
||||
) => {
|
||||
return currentViewSorts.filter(
|
||||
(currentViewSort) =>
|
||||
!newViewSorts.some(
|
||||
(newViewSort) =>
|
||||
newViewSort.fieldMetadataId === currentViewSort.fieldMetadataId,
|
||||
),
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
import { areViewSortsEqual } from '@/views/utils/areViewSortsEqual';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const getViewSortsToUpdate = (
|
||||
currentViewSorts: ViewSort[],
|
||||
newViewSorts: ViewSort[],
|
||||
) => {
|
||||
return newViewSorts.filter((newViewSort) => {
|
||||
const correspondingViewSort = currentViewSorts.find(
|
||||
(currentViewSort) =>
|
||||
currentViewSort.fieldMetadataId === newViewSort.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!isDefined(correspondingViewSort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const shouldUpdateBecauseViewSortIsDifferent = !areViewSortsEqual(
|
||||
newViewSort,
|
||||
correspondingViewSort,
|
||||
);
|
||||
|
||||
return shouldUpdateBecauseViewSortIsDifferent;
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,9 @@
|
||||
import { RecordSort } from '@/object-record/record-sort/types/RecordSort';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
|
||||
export const mapRecordSortToViewSort = (recordSort: RecordSort): ViewSort => {
|
||||
return {
|
||||
__typename: 'ViewSort',
|
||||
...recordSort,
|
||||
} satisfies ViewSort;
|
||||
};
|
||||
Reference in New Issue
Block a user