Skip to content

Commit

Permalink
☑️ Refacto "Select All/Unselect all" on indexes (#5320)
Browse files Browse the repository at this point in the history
### Description

- Refacto "Select All/Unselect all" on indexes
- Add sequential mass deletion from front end (limited to 10k records)
- Fixed coverage with new unit tests on new useFetchAllRecordIds hook
and other utils

### Refs

Closes #4397 
Closes #5169

### Demo


https://github.com/twentyhq/twenty/assets/26528466/2658ad2c-827e-4670-b42b-3092e268ff32

---------

Co-authored-by: gitstart-twenty <[email protected]>
Co-authored-by: v1b3m <[email protected]>
Co-authored-by: Toledodev <[email protected]>
Co-authored-by: Félix Malfait <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
  • Loading branch information
6 people authored Jul 15, 2024
1 parent e5d76a3 commit d560d25
Show file tree
Hide file tree
Showing 40 changed files with 1,351 additions and 435 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getObjectMetadataItemByNameSingular } from '@/object-metadata/utils/getObjectMetadataItemBySingularName';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';

const mockObjectMetadataItems = getObjectMetadataItemsMock();

describe('getObjectMetadataItemBySingularName', () => {
it('should work as expected', () => {
const firstObjectMetadataItem = mockObjectMetadataItems[0];

const foundObjectMetadataItem = getObjectMetadataItemByNameSingular({
objectMetadataItems: mockObjectMetadataItems,
objectNameSingular: firstObjectMetadataItem.nameSingular,
});

expect(foundObjectMetadataItem.id).toEqual(firstObjectMetadataItem.id);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { peopleQueryResult } from '~/testing/mock-data/people';

import { isObjectRecordConnection } from '@/object-record/cache/utils/isObjectRecordConnection';

describe('isObjectRecordConnection', () => {
it('should work with query result', () => {
const validQueryResult = peopleQueryResult.people;

const isValidQueryResult = isObjectRecordConnection(
'person',
validQueryResult,
);

expect(isValidQueryResult).toEqual(true);
});

it('should fail with invalid result', () => {
const invalidResult = { test: 123 };

const isValidQueryResult = isObjectRecordConnection(
'person',
invalidResult,
);

expect(isValidQueryResult).toEqual(false);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DEFAULT_MUTATION_BATCH_SIZE = 30;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DEFAULT_QUERY_PAGE_SIZE = 30;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DELETE_MAX_COUNT = 10000;
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type RecordGqlConnection = {
__typename?: string;
edges: RecordGqlEdge[];
pageInfo: {
__typename?: string;
hasNextPage?: boolean;
hasPreviousPage?: boolean;
startCursor?: Nullable<string>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
import { gql } from '@apollo/client';

import { peopleQueryResult } from '~/testing/mock-data/people';


export const query = gql`
query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) {
people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
edges {
node {
__typename
id
}
cursor
}
pageInfo {
hasNextPage
startCursor
endCursor
}
totalCount
}
}
`;

export const mockPageSize = 2;

export const peopleMockWithIdsOnly: RecordGqlConnection = { ...peopleQueryResult.people,edges: peopleQueryResult.people.edges.map((edge) => ({ ...edge, node: { __typename: 'Person', id: edge.node.id } })) };

export const firstRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize].cursor;
export const secondRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
export const thirdRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;

export const variablesFirstRequest = {
filter: undefined,
limit: undefined,
orderBy: undefined
};

export const variablesSecondRequest = {
filter: undefined,
limit: undefined,
orderBy: undefined,
lastCursor: firstRequestLastCursor
};

export const variablesThirdRequest = {
filter: undefined,
limit: undefined,
orderBy: undefined,
lastCursor: secondRequestLastCursor
}

const paginateRequestResponse = (response: RecordGqlConnection, start: number, end: number, hasNextPage: boolean, totalCount: number) => {
return {
...response,
edges: [
...response.edges.slice(start, end)
],
pageInfo: {
...response.pageInfo,
startCursor: response.edges[start].cursor,
endCursor: response.edges[end].cursor,
hasNextPage,
} satisfies RecordGqlConnection['pageInfo'],
totalCount,
}
}

export const responseFirstRequest = {
people: paginateRequestResponse(peopleMockWithIdsOnly, 0, mockPageSize, true, 6),
};

export const responseSecondRequest = {
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize, mockPageSize * 2, true, 6),
};

export const responseThirdRequest = {
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize * 2, mockPageSize * 3, false, 6),
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import { ReactNode } from 'react';
import { RecoilRoot } from 'recoil';

import {
Expand All @@ -23,7 +23,7 @@ const mocks = [
},
result: jest.fn(() => ({
data: {
deletePeople: responseData,
deletePeople: [responseData],
},
})),
},
Expand All @@ -49,7 +49,7 @@ describe('useDeleteManyRecords', () => {
await act(async () => {
const res = await result.current.deleteManyRecords(people);
expect(res).toBeDefined();
expect(res).toHaveProperty('id');
expect(res[0]).toHaveProperty('id');
});

expect(mocks[0].result).toHaveBeenCalled();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { MockedProvider } from '@apollo/client/testing';
import { act, renderHook } from '@testing-library/react';
import { ReactNode, useEffect } from 'react';
import { RecoilRoot, useRecoilState } from 'recoil';

import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
import {
mockPageSize,
peopleMockWithIdsOnly,
query,
responseFirstRequest,
responseSecondRequest,
responseThirdRequest,
variablesFirstRequest,
variablesSecondRequest,
variablesThirdRequest,
} from '@/object-record/hooks/__mocks__/useFetchAllRecordIds';
import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds';
import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext';

const mocks = [
{
delay: 100,
request: {
query,
variables: variablesFirstRequest,
},
result: jest.fn(() => ({
data: responseFirstRequest,
})),
},
{
delay: 100,
request: {
query,
variables: variablesSecondRequest,
},
result: jest.fn(() => ({
data: responseSecondRequest,
})),
},
{
delay: 100,
request: {
query,
variables: variablesThirdRequest,
},
result: jest.fn(() => ({
data: responseThirdRequest,
})),
},
];

describe('useFetchAllRecordIds', () => {
it('fetches all record ids with fetch more synchronous loop', async () => {
const Wrapper = ({ children }: { children: ReactNode }) => (
<RecoilRoot>
<SnackBarManagerScopeInternalContext.Provider
value={{
scopeId: 'snack-bar-manager',
}}
>
<MockedProvider mocks={mocks} addTypename={false}>
{children}
</MockedProvider>
</SnackBarManagerScopeInternalContext.Provider>
</RecoilRoot>
);

const { result } = renderHook(
() => {
const [, setObjectMetadataItems] = useRecoilState(
objectMetadataItemsState,
);

useEffect(() => {
setObjectMetadataItems(getObjectMetadataItemsMock());
}, [setObjectMetadataItems]);

return useFetchAllRecordIds({
objectNameSingular: 'person',
pageSize: mockPageSize,
});
},
{
wrapper: Wrapper,
},
);

const { fetchAllRecordIds } = result.current;

let recordIds: string[] = [];

await act(async () => {
recordIds = await fetchAllRecordIds();
});

expect(mocks[0].result).toHaveBeenCalled();
expect(mocks[1].result).toHaveBeenCalled();
expect(mocks[2].result).toHaveBeenCalled();

expect(recordIds).toEqual(
peopleMockWithIdsOnly.edges.map((edge) => edge.node.id).slice(0, 6),
);
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import { MockedProvider } from '@apollo/client/testing';
import { renderHook } from '@testing-library/react';
import { ReactNode } from 'react';
import { RecoilRoot, useSetRecoilState } from 'recoil';

import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
Expand Down
Loading

0 comments on commit d560d25

Please sign in to comment.