mirror of
https://github.com/twentyhq/twenty.git
synced 2024-08-18 02:10:25 +03:00
Recursively turn relation connection into records (#3334)
* Use new ObjectRecordConnection * Use new records without connection in GraphQLView * Added playwright for storybook tests * Fixed lint * Fixed test and tsc * Fixed storybook tests * wip tests * Added useMapConnectionToRecords unit test --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
parent
299bed511f
commit
e2bdf0ce45
@ -241,6 +241,7 @@
|
||||
"msw": "^2.0.11",
|
||||
"msw-storybook-addon": "2.0.0--canary.122.b3ed3b1.0",
|
||||
"nx": "^17.2.8",
|
||||
"playwright": "^1.40.1",
|
||||
"prettier": "^3.1.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^5.0.5",
|
||||
|
@ -9,14 +9,16 @@
|
||||
"start:clean": "yarn start --force",
|
||||
"build": "tsc && vite build && yarn build:inject-runtime-env",
|
||||
"build:inject-runtime-env": "sh ./scripts/inject-runtime-env.sh",
|
||||
"tsc:spec": "tsc --project tsconfig.spec.json --noEmit",
|
||||
"tsc": "tsc --project tsconfig.app.json --watch",
|
||||
"tsc:ci": "tsc --project tsconfig.app.json",
|
||||
"tsc:ci": "tsc --project tsconfig.app.json --noEmit && tsc --project tsconfig.spec.json --noEmit && tsc --project tsconfig.node.json --noEmit",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --report-unused-disable-directives --max-warnings 0 --config .eslintrc.cjs",
|
||||
"lint:ci": "yarn lint --config .eslintrc-ci.cjs",
|
||||
"fmt:fix": "prettier --cache --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||
"fmt": "prettier --check \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||
"test": "jest",
|
||||
"test-watch": "jest --coverage=false --watch",
|
||||
"tsup": "tsup",
|
||||
"coverage": "jest --coverage",
|
||||
"storybook:modules:dev": "STORYBOOK_SCOPE=modules yarn storybook:dev",
|
||||
@ -29,6 +31,7 @@
|
||||
"storybook:docs:build": "STORYBOOK_SCOPE=ui-docs yarn storybook:build",
|
||||
"storybook:test": "test-storybook",
|
||||
"storybook:test-slow": "test-storybook --maxWorkers=3",
|
||||
"storybook:test-single-worker": "test-storybook --maxWorkers=1",
|
||||
"storybook:coverage": "yarn storybook:test-slow --coverage && npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook --check-coverage",
|
||||
"storybook:modules:coverage": "STORYBOOK_SCOPE=modules yarn storybook:coverage",
|
||||
"storybook:pages:coverage": "STORYBOOK_SCOPE=pages yarn storybook:coverage",
|
||||
|
@ -4,7 +4,7 @@ import { produce } from 'immer';
|
||||
import { OptimisticEffectDefinition } from '@/apollo/optimistic-effect/types/OptimisticEffectDefinition';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
@ -21,77 +21,39 @@ export const getRecordOptimisticEffectDefinition = ({
|
||||
deletedRecordIds,
|
||||
variables,
|
||||
}) => {
|
||||
const newRecordPaginatedCacheField = produce<
|
||||
PaginatedRecordTypeResults<any>
|
||||
>(currentData as PaginatedRecordTypeResults<any>, (draft) => {
|
||||
const existingDataIsEmpty = !draft || !draft.edges || !draft.edges[0];
|
||||
const newRecordPaginatedCacheField = produce<ObjectRecordConnection<any>>(
|
||||
currentData as ObjectRecordConnection<any>,
|
||||
(draft) => {
|
||||
const existingDataIsEmpty = !draft || !draft.edges || !draft.edges[0];
|
||||
|
||||
if (isNonEmptyArray(createdRecords)) {
|
||||
if (existingDataIsEmpty) {
|
||||
return {
|
||||
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
edges: createdRecords.map((createdRecord) => ({
|
||||
node: createdRecord,
|
||||
cursor: '',
|
||||
})),
|
||||
pageInfo: {
|
||||
endCursor: '',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
for (const createdRecord of createdRecords) {
|
||||
const existingRecord = draft.edges.find(
|
||||
(edge) => edge.node.id === createdRecord.id,
|
||||
);
|
||||
|
||||
if (existingRecord) {
|
||||
existingRecord.node = createdRecord;
|
||||
continue;
|
||||
}
|
||||
|
||||
draft.edges.unshift({
|
||||
node: createdRecord,
|
||||
cursor: '',
|
||||
if (isNonEmptyArray(createdRecords)) {
|
||||
if (existingDataIsEmpty) {
|
||||
return {
|
||||
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(deletedRecordIds)) {
|
||||
draft.edges = draft.edges.filter(
|
||||
(edge) => !deletedRecordIds.includes(edge.node.id),
|
||||
);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(updatedRecords)) {
|
||||
for (const updatedRecord of updatedRecords) {
|
||||
const updatedRecordIsOutOfQueryFilter =
|
||||
isDefined(variables.filter) &&
|
||||
!isRecordMatchingFilter({
|
||||
record: updatedRecord,
|
||||
filter: variables.filter,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
if (updatedRecordIsOutOfQueryFilter) {
|
||||
draft.edges = draft.edges.filter(
|
||||
(edge) => edge.node.id !== updatedRecord.id,
|
||||
);
|
||||
edges: createdRecords.map((createdRecord) => ({
|
||||
node: createdRecord,
|
||||
cursor: '',
|
||||
})),
|
||||
pageInfo: {
|
||||
endCursor: '',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const foundUpdatedRecordInCacheQuery = draft.edges.find(
|
||||
(edge) => edge.node.id === updatedRecord.id,
|
||||
);
|
||||
for (const createdRecord of createdRecords) {
|
||||
const existingRecord = draft.edges.find(
|
||||
(edge) => edge.node.id === createdRecord.id,
|
||||
);
|
||||
|
||||
if (foundUpdatedRecordInCacheQuery) {
|
||||
foundUpdatedRecordInCacheQuery.node = updatedRecord;
|
||||
} else {
|
||||
// TODO: add order by
|
||||
draft.edges.push({
|
||||
node: updatedRecord,
|
||||
if (existingRecord) {
|
||||
existingRecord.node = createdRecord;
|
||||
continue;
|
||||
}
|
||||
|
||||
draft.edges.unshift({
|
||||
node: createdRecord,
|
||||
cursor: '',
|
||||
__typename: `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
@ -100,8 +62,49 @@ export const getRecordOptimisticEffectDefinition = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isNonEmptyArray(deletedRecordIds)) {
|
||||
draft.edges = draft.edges.filter(
|
||||
(edge) => !deletedRecordIds.includes(edge.node.id),
|
||||
);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(updatedRecords)) {
|
||||
for (const updatedRecord of updatedRecords) {
|
||||
const updatedRecordIsOutOfQueryFilter =
|
||||
isDefined(variables.filter) &&
|
||||
!isRecordMatchingFilter({
|
||||
record: updatedRecord,
|
||||
filter: variables.filter,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
if (updatedRecordIsOutOfQueryFilter) {
|
||||
draft.edges = draft.edges.filter(
|
||||
(edge) => edge.node.id !== updatedRecord.id,
|
||||
);
|
||||
} else {
|
||||
const foundUpdatedRecordInCacheQuery = draft.edges.find(
|
||||
(edge) => edge.node.id === updatedRecord.id,
|
||||
);
|
||||
|
||||
if (foundUpdatedRecordInCacheQuery) {
|
||||
foundUpdatedRecordInCacheQuery.node = updatedRecord;
|
||||
} else {
|
||||
// TODO: add order by
|
||||
draft.edges.push({
|
||||
node: updatedRecord,
|
||||
cursor: '',
|
||||
__typename: `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Edge`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return newRecordPaginatedCacheField;
|
||||
},
|
||||
|
@ -0,0 +1,752 @@
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { Person } from '@/people/types/Person';
|
||||
|
||||
export const emptyConnectionMock: ObjectRecordConnection = {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '',
|
||||
endCursor: '',
|
||||
},
|
||||
__typename: 'ObjectRecordConnection',
|
||||
};
|
||||
|
||||
export const companiesConnectionWithPeopleConnectionWithFavoritesConnectionMock: ObjectRecordConnection<
|
||||
Partial<Company> &
|
||||
Pick<Company, 'id'> & {
|
||||
people: ObjectRecordConnection<
|
||||
Pick<Person, 'id' | 'name'> & {
|
||||
favorites: ObjectRecordConnection<
|
||||
Pick<Favorite, 'id' | 'personId' | 'companyId' | 'position'>
|
||||
>;
|
||||
}
|
||||
>;
|
||||
}
|
||||
> = {
|
||||
pageInfo: {
|
||||
endCursor: 'WyJmZTI1NmIzOS0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: false,
|
||||
startCursor: 'WyIwNGIyZTlmNS0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UiXQ==',
|
||||
},
|
||||
edges: [
|
||||
{
|
||||
cursor: 'WyIwNGIyZTlmNS0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UiXQ==',
|
||||
node: {
|
||||
id: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
||||
name: 'Qonto',
|
||||
people: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIwZDk0MDk5Ny1jMjFlLTRlYzItODczYi1kZTQyNjRkODkwMjUiXQ==',
|
||||
node: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkwZGYiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049190df',
|
||||
name: {
|
||||
firstName: 'Bertrand',
|
||||
lastName: 'Voulzy',
|
||||
},
|
||||
favorites: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyJjODVhODY3Yy01YThmLTQ4NjEtOGVkMi05NmMzOTAyNDg0MjMiXQ==',
|
||||
node: {
|
||||
id: 'c85a867c-5a8f-4861-8ed2-96c390248423',
|
||||
personId: '240da2ec-2d40-4e49-8df4-9c6a049190df',
|
||||
companyId: null,
|
||||
position: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyJjODVhODY3Yy01YThmLTQ4NjEtOGVkMi05NmMzOTAyNDg0MjMiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyJjODVhODY3Yy01YThmLTQ4NjEtOGVkMi05NmMzOTAyNDg0MjMiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkwZWYiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049190ef',
|
||||
name: {
|
||||
firstName: 'Madison',
|
||||
lastName: 'Perez',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyI1Njk1NTQyMi01ZDU0LTQxYjctYmEzNi1mMGQyMGUxNDE3YWUiXQ==',
|
||||
node: {
|
||||
id: '56955422-5d54-41b7-ba36-f0d20e1417ae',
|
||||
name: {
|
||||
firstName: 'Avery',
|
||||
lastName: 'Carter',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyI3NTUwMzVkYi02MjNkLTQxZmUtOTJlNy1kZDQ1YjdjNTY4ZTEiXQ==',
|
||||
node: {
|
||||
id: '755035db-623d-41fe-92e7-dd45b7c568e1',
|
||||
name: {
|
||||
firstName: 'Ethan',
|
||||
lastName: 'Mitchell',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyJhMmU3OGE1Zi0zMzhiLTQ2ZGYtODgxMS1mYTA4YzdkMTlkMzUiXQ==',
|
||||
node: {
|
||||
id: 'a2e78a5f-338b-46df-8811-fa08c7d19d35',
|
||||
name: {
|
||||
firstName: 'Elizabeth',
|
||||
lastName: 'Baker',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyJjYTFmNWJmMy02NGFkLTRiMGUtYmJmZC1lOWZkNzk1YjcwMTYiXQ==',
|
||||
node: {
|
||||
id: 'ca1f5bf3-64ad-4b0e-bbfd-e9fd795b7016',
|
||||
name: {
|
||||
firstName: 'Christopher',
|
||||
lastName: 'Nelson',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyJjYTFmNWJmMy02NGFkLTRiMGUtYmJmZC1lOWZkNzk1YjcwMTYiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkwZGYiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIxMTg5OTVmMy01ZDgxLTQ2ZDYtYmY4My1mN2ZkMzNlYTYxMDIiXQ==',
|
||||
node: {
|
||||
id: '118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
||||
name: 'Facebook',
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyI5M2M3MmQyZS1mNTE3LTQyZmQtODBhZS0xNDE3M2IzYjcwYWUiXQ==',
|
||||
node: {
|
||||
id: '93c72d2e-f517-42fd-80ae-14173b3b70ae',
|
||||
name: {
|
||||
firstName: 'Christopher',
|
||||
lastName: 'Gonzalez',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyJlZWVhY2FjZi1lZWUxLTQ2OTAtYWQyYy04NjE5ZTViNTZhMmUiXQ==',
|
||||
node: {
|
||||
id: 'eeeacacf-eee1-4690-ad2c-8619e5b56a2e',
|
||||
name: {
|
||||
firstName: 'Ashley',
|
||||
lastName: 'Parker',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyJlZWVhY2FjZi1lZWUxLTQ2OTAtYWQyYy04NjE5ZTViNTZhMmUiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyI5M2M3MmQyZS1mNTE3LTQyZmQtODBhZS0xNDE3M2IzYjcwYWUiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIxZDNhMWM2ZS03MDdlLTQ0ZGMtYTFkMi0zMDAzMGJmMWE5NDQiXQ==',
|
||||
node: {
|
||||
id: '1d3a1c6e-707e-44dc-a1d2-30030bf1a944',
|
||||
name: 'Netflix',
|
||||
people: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI0NjBiNmZiMS1lZDg5LTQxM2EtYjMxYS05NjI5ODZlNjdiYjQiXQ==',
|
||||
node: {
|
||||
id: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
name: 'Microsoft',
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyIxZDE1MTg1Mi00OTBmLTQ0NjYtODM5MS03MzNjZmQ2NmEwYzgiXQ==',
|
||||
node: {
|
||||
id: '1d151852-490f-4466-8391-733cfd66a0c8',
|
||||
name: {
|
||||
firstName: 'Isabella',
|
||||
lastName: 'Scott',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyI5ODQwNmUyNi04MGYxLTRkZmYtYjU3MC1hNzQ5NDI1MjhkZTMiXQ==',
|
||||
node: {
|
||||
id: '98406e26-80f1-4dff-b570-a74942528de3',
|
||||
name: {
|
||||
firstName: 'Matthew',
|
||||
lastName: 'Green',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyI5YjMyNGE4OC02Nzg0LTQ0NDktYWZkZi1kYzYyY2I4NzAyZjIiXQ==',
|
||||
node: {
|
||||
id: '9b324a88-6784-4449-afdf-dc62cb8702f2',
|
||||
name: {
|
||||
firstName: 'Nicholas',
|
||||
lastName: 'Wright',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyI5YjMyNGE4OC02Nzg0LTQ0NDktYWZkZi1kYzYyY2I4NzAyZjIiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyIxZDE1MTg1Mi00OTBmLTQ0NjYtODM5MS03MzNjZmQ2NmEwYzgiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI3YTkzZDFlNS0zZjc0LTQ5MmQtYTEwMS0yYTcwZjUwYTE2NDUiXQ==',
|
||||
node: {
|
||||
id: '7a93d1e5-3f74-492d-a101-2a70f50a1645',
|
||||
name: 'Libeo',
|
||||
people: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI4OWJiODI1Yy0xNzFlLTRiY2MtOWNmNy00MzQ0OGQ2ZmIyNzgiXQ==',
|
||||
node: {
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
name: 'Airbnb',
|
||||
people: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI5ZDE2MmRlNi1jZmJmLTQxNTYtYTc5MC1lMzk4NTRkY2Q0ZWIiXQ==',
|
||||
node: {
|
||||
id: '9d162de6-cfbf-4156-a790-e39854dcd4eb',
|
||||
name: 'Claap',
|
||||
people: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJhNjc0ZmE2Yy0xNDU1LTRjNTctYWZhZi1kZDVkYzA4NjM2MWQiXQ==',
|
||||
node: {
|
||||
id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d',
|
||||
name: 'Algolia',
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGYiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049191df',
|
||||
name: {
|
||||
firstName: 'Lorie',
|
||||
lastName: 'Vladim',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGYiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGYiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJhN2JjNjhkNS1mNzllLTQwZGQtYmQwNi1jMzZlNmFiYjQ2NzgiXQ==',
|
||||
node: {
|
||||
id: 'a7bc68d5-f79e-40dd-bd06-c36e6abb4678',
|
||||
name: 'Samsung',
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGUiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049191de',
|
||||
name: {
|
||||
firstName: 'Louis',
|
||||
lastName: 'Duss',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGUiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGUiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJhYWZmY2ZiZC1mODZiLTQxOWYtYjc5NC0wMjMxOWFiZTg2MzciXQ==',
|
||||
node: {
|
||||
id: 'aaffcfbd-f86b-419f-b794-02319abe8637',
|
||||
name: 'Hasura',
|
||||
people: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJmMzNkYzI0Mi01NTE4LTQ1NTMtOTQzMy00MmQ4ZWI4MjgzNGIiXQ==',
|
||||
node: {
|
||||
id: 'f33dc242-5518-4553-9433-42d8eb82834b',
|
||||
name: 'Wework',
|
||||
people: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJmZTI1NmIzOS0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
|
||||
node: {
|
||||
id: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
name: 'Linkedin',
|
||||
people: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyIwYWEwMGJlYi1hYzczLTQ3OTctODI0ZS04N2ExZjVhZWE5ZTAiXQ==',
|
||||
node: {
|
||||
id: '0aa00beb-ac73-4797-824e-87a1f5aea9e0',
|
||||
name: {
|
||||
firstName: 'Sylvie',
|
||||
lastName: 'Palmer',
|
||||
},
|
||||
favorites: {
|
||||
edges: [
|
||||
{
|
||||
cursor:
|
||||
'WyIzN2I5NzE0MC0yNmI5LTQ5OGMtODM3Yi00ZjNkZTQ5OWFkODMiXQ==',
|
||||
node: {
|
||||
id: '37b97140-26b9-498c-837b-4f3de499ad83',
|
||||
personId: '0aa00beb-ac73-4797-824e-87a1f5aea9e0',
|
||||
companyId: null,
|
||||
position: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyIzN2I5NzE0MC0yNmI5LTQ5OGMtODM3Yi00ZjNkZTQ5OWFkODMiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyIzN2I5NzE0MC0yNmI5LTQ5OGMtODM3Yi00ZjNkZTQ5OWFkODMiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor:
|
||||
'WyI4NjA4MzE0MS0xYzBlLTQ5NGMtYTFiNi04NWIxYzZmZWZhYTUiXQ==',
|
||||
node: {
|
||||
id: '86083141-1c0e-494c-a1b6-85b1c6fefaa5',
|
||||
name: {
|
||||
firstName: 'Christoph',
|
||||
lastName: 'Callisto',
|
||||
},
|
||||
favorites: {
|
||||
edges: [],
|
||||
pageInfo: {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor:
|
||||
'WyI4NjA4MzE0MS0xYzBlLTQ5NGMtYTFiNi04NWIxYzZmZWZhYTUiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor:
|
||||
'WyIwYWEwMGJlYi1hYzczLTQ3OTctODI0ZS04N2ExZjVhZWE5ZTAiXQ==',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const peopleWithTheirUniqueCompanies: ObjectRecordConnection<
|
||||
Pick<Person, 'id'> & { company: Pick<Company, 'id' | 'name'> }
|
||||
> = {
|
||||
pageInfo: {
|
||||
endCursor: 'WyJlZWVhY2FjZi1lZWUxLTQ2OTAtYWQyYy04NjE5ZTViNTZhMmUiXQ==',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: 'WyIwYWEwMGJlYi1hYzczLTQ3OTctODI0ZS04N2ExZjVhZWE5ZTAiXQ==',
|
||||
},
|
||||
edges: [
|
||||
{
|
||||
cursor: 'WyIwYWEwMGJlYi1hYzczLTQ3OTctODI0ZS04N2ExZjVhZWE5ZTAiXQ==',
|
||||
node: {
|
||||
id: '0aa00beb-ac73-4797-824e-87a1f5aea9e0',
|
||||
company: {
|
||||
id: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
name: 'Linkedin',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIxZDE1MTg1Mi00OTBmLTQ0NjYtODM5MS03MzNjZmQ2NmEwYzgiXQ==',
|
||||
node: {
|
||||
id: '1d151852-490f-4466-8391-733cfd66a0c8',
|
||||
company: {
|
||||
id: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
name: 'Microsoft',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkwZGYiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049190df',
|
||||
company: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkwZWYiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049190ef',
|
||||
company: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGUiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049191de',
|
||||
company: {
|
||||
id: 'a7bc68d5-f79e-40dd-bd06-c36e6abb4678',
|
||||
name: 'Samsung',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyIyNDBkYTJlYy0yZDQwLTRlNDktOGRmNC05YzZhMDQ5MTkxZGYiXQ==',
|
||||
node: {
|
||||
id: '240da2ec-2d40-4e49-8df4-9c6a049191df',
|
||||
company: {
|
||||
id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d',
|
||||
name: 'Algolia',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI1Njk1NTQyMi01ZDU0LTQxYjctYmEzNi1mMGQyMGUxNDE3YWUiXQ==',
|
||||
node: {
|
||||
id: '56955422-5d54-41b7-ba36-f0d20e1417ae',
|
||||
company: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI3NTUwMzVkYi02MjNkLTQxZmUtOTJlNy1kZDQ1YjdjNTY4ZTEiXQ==',
|
||||
node: {
|
||||
id: '755035db-623d-41fe-92e7-dd45b7c568e1',
|
||||
company: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI4NjA4MzE0MS0xYzBlLTQ5NGMtYTFiNi04NWIxYzZmZWZhYTUiXQ==',
|
||||
node: {
|
||||
id: '86083141-1c0e-494c-a1b6-85b1c6fefaa5',
|
||||
company: {
|
||||
id: 'fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
name: 'Linkedin',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI5M2M3MmQyZS1mNTE3LTQyZmQtODBhZS0xNDE3M2IzYjcwYWUiXQ==',
|
||||
node: {
|
||||
id: '93c72d2e-f517-42fd-80ae-14173b3b70ae',
|
||||
company: {
|
||||
id: '118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
||||
name: 'Facebook',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI5ODQwNmUyNi04MGYxLTRkZmYtYjU3MC1hNzQ5NDI1MjhkZTMiXQ==',
|
||||
node: {
|
||||
id: '98406e26-80f1-4dff-b570-a74942528de3',
|
||||
company: {
|
||||
id: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
name: 'Microsoft',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyI5YjMyNGE4OC02Nzg0LTQ0NDktYWZkZi1kYzYyY2I4NzAyZjIiXQ==',
|
||||
node: {
|
||||
id: '9b324a88-6784-4449-afdf-dc62cb8702f2',
|
||||
company: {
|
||||
id: '460b6fb1-ed89-413a-b31a-962986e67bb4',
|
||||
name: 'Microsoft',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJhMmU3OGE1Zi0zMzhiLTQ2ZGYtODgxMS1mYTA4YzdkMTlkMzUiXQ==',
|
||||
node: {
|
||||
id: 'a2e78a5f-338b-46df-8811-fa08c7d19d35',
|
||||
company: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJjYTFmNWJmMy02NGFkLTRiMGUtYmJmZC1lOWZkNzk1YjcwMTYiXQ==',
|
||||
node: {
|
||||
id: 'ca1f5bf3-64ad-4b0e-bbfd-e9fd795b7016',
|
||||
company: {
|
||||
id: '0d940997-c21e-4ec2-873b-de4264d89025',
|
||||
name: 'Google',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
cursor: 'WyJlZWVhY2FjZi1lZWUxLTQ2OTAtYWQyYy04NjE5ZTViNTZhMmUiXQ==',
|
||||
node: {
|
||||
id: 'eeeacacf-eee1-4690-ad2c-8619e5b56a2e',
|
||||
company: {
|
||||
id: '118995f3-5d81-46d6-bf83-f7fd33ea6102',
|
||||
name: 'Facebook',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
@ -0,0 +1,190 @@
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import {
|
||||
companiesConnectionWithPeopleConnectionWithFavoritesConnectionMock,
|
||||
emptyConnectionMock,
|
||||
peopleWithTheirUniqueCompanies,
|
||||
} from '@/object-record/hooks/__mocks__/useMapConnectionToRecords';
|
||||
import { useMapConnectionToRecords } from '@/object-record/hooks/useMapConnectionToRecords';
|
||||
import { Person } from '@/people/types/Person';
|
||||
import { getJestHookWrapper } from '~/testing/jest/getJestHookWrapper';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const Wrapper = getJestHookWrapper({
|
||||
apolloMocks: [],
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(objectMetadataItemsState, getObjectMetadataItemsMock());
|
||||
},
|
||||
});
|
||||
|
||||
describe('useMapConnectionToRecords', () => {
|
||||
it('Empty edges - should return an empty array if no edge', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const records = mapConnectionToRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
objectRecordConnection: emptyConnectionMock,
|
||||
depth: 5,
|
||||
});
|
||||
|
||||
return records;
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(Array.isArray(result.current)).toBe(true);
|
||||
});
|
||||
|
||||
it('No relation fields - should return an array of company records', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const records = mapConnectionToRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
objectRecordConnection:
|
||||
companiesConnectionWithPeopleConnectionWithFavoritesConnectionMock,
|
||||
depth: 5,
|
||||
});
|
||||
|
||||
return records;
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(Array.isArray(result.current)).toBe(true);
|
||||
});
|
||||
|
||||
it('n+1 relation fields - should return an array of company records with their people records', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const records = mapConnectionToRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
objectRecordConnection:
|
||||
companiesConnectionWithPeopleConnectionWithFavoritesConnectionMock,
|
||||
depth: 5,
|
||||
});
|
||||
|
||||
return records;
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const secondCompanyMock =
|
||||
companiesConnectionWithPeopleConnectionWithFavoritesConnectionMock
|
||||
.edges[1];
|
||||
|
||||
const secondCompanyPeopleMock = secondCompanyMock.node.people.edges.map(
|
||||
(edge) => edge.node,
|
||||
);
|
||||
|
||||
const companiesResult = result.current;
|
||||
const secondCompanyResult = result.current[1];
|
||||
const secondCompanyPeopleResult = secondCompanyResult.people;
|
||||
|
||||
expect(isNonEmptyArray(companiesResult)).toBe(true);
|
||||
expect(secondCompanyResult.id).toBe(secondCompanyMock.node.id);
|
||||
expect(isNonEmptyArray(secondCompanyPeopleResult)).toBe(true);
|
||||
expect(secondCompanyPeopleResult[0].id).toEqual(
|
||||
secondCompanyPeopleMock[0].id,
|
||||
);
|
||||
});
|
||||
|
||||
it('n+2 relation fields - should return an array of company records with their people records with their favorites records', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const records = mapConnectionToRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
objectRecordConnection:
|
||||
companiesConnectionWithPeopleConnectionWithFavoritesConnectionMock,
|
||||
depth: 5,
|
||||
});
|
||||
|
||||
return records;
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const secondCompanyMock =
|
||||
companiesConnectionWithPeopleConnectionWithFavoritesConnectionMock
|
||||
.edges[1];
|
||||
|
||||
const secondCompanyPeopleMock = secondCompanyMock.node.people;
|
||||
|
||||
const secondCompanyFirstPersonMock = secondCompanyPeopleMock.edges[0].node;
|
||||
|
||||
const secondCompanyFirstPersonFavoritesMock =
|
||||
secondCompanyFirstPersonMock.favorites;
|
||||
|
||||
const companiesResult = result.current;
|
||||
const secondCompanyResult = companiesResult[1];
|
||||
const secondCompanyPeopleResult = secondCompanyResult.people;
|
||||
const secondCompanyFirstPersonResult = secondCompanyPeopleResult[0];
|
||||
const secondCompanyFirstPersonFavoritesResult =
|
||||
secondCompanyFirstPersonResult.favorites;
|
||||
|
||||
expect(isNonEmptyArray(companiesResult)).toBe(true);
|
||||
expect(secondCompanyResult.id).toBe(secondCompanyMock.node.id);
|
||||
expect(isNonEmptyArray(secondCompanyPeopleResult)).toBe(true);
|
||||
expect(secondCompanyFirstPersonResult.id).toEqual(
|
||||
secondCompanyFirstPersonMock.id,
|
||||
);
|
||||
expect(isNonEmptyArray(secondCompanyFirstPersonFavoritesResult)).toBe(true);
|
||||
expect(secondCompanyFirstPersonFavoritesResult[0].id).toEqual(
|
||||
secondCompanyFirstPersonFavoritesMock.edges[0].node.id,
|
||||
);
|
||||
});
|
||||
|
||||
it("n+1 relation field TO_ONE_OBJECT - should return an array of people records with their company, mapConnectionToRecords shouldn't try to parse TO_ONE_OBJECT", async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const records = mapConnectionToRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Person,
|
||||
objectRecordConnection: peopleWithTheirUniqueCompanies,
|
||||
depth: 5,
|
||||
});
|
||||
|
||||
return records as (Person & { company: Company })[];
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const firstPersonMock = peopleWithTheirUniqueCompanies.edges[0].node;
|
||||
|
||||
const firstPersonsCompanyMock = firstPersonMock.company;
|
||||
|
||||
const peopleResult = result.current;
|
||||
|
||||
const firstPersonResult = result.current[0];
|
||||
const firstPersonsCompanyresult = firstPersonResult.company;
|
||||
|
||||
expect(isNonEmptyArray(peopleResult)).toBe(true);
|
||||
expect(firstPersonResult.id).toBe(firstPersonMock.id);
|
||||
|
||||
expect(isDefined(firstPersonsCompanyresult)).toBe(true);
|
||||
expect(firstPersonsCompanyresult.id).toEqual(firstPersonsCompanyMock.id);
|
||||
});
|
||||
});
|
@ -9,7 +9,11 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useRecordOptimisticEffect } from '@/object-metadata/hooks/useRecordOptimisticEffect';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { OrderByField } from '@/object-metadata/types/OrderByField';
|
||||
import { useMapConnectionToRecords } from '@/object-record/hooks/useMapConnectionToRecords';
|
||||
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor';
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/search/hooks/useFilteredSearchEntityQuery';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
@ -19,28 +23,24 @@ import { capitalize } from '~/utils/string/capitalize';
|
||||
import { cursorFamilyState } from '../states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '../states/hasNextPageFamilyState';
|
||||
import { isFetchingMoreRecordsFamilyState } from '../states/isFetchingMoreRecordsFamilyState';
|
||||
import { PaginatedRecordType } from '../types/PaginatedRecordType';
|
||||
import {
|
||||
PaginatedRecordTypeEdge,
|
||||
PaginatedRecordTypeResults,
|
||||
} from '../types/PaginatedRecordTypeResults';
|
||||
import { ObjectRecordQueryResult } from '../types/ObjectRecordQueryResult';
|
||||
import { mapPaginatedRecordsToRecords } from '../utils/mapPaginatedRecordsToRecords';
|
||||
|
||||
export const useFindManyRecords = <
|
||||
RecordType extends { id: string } & Record<string, any>,
|
||||
>({
|
||||
export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit = DEFAULT_SEARCH_REQUEST_LIMIT,
|
||||
onCompleted,
|
||||
skip,
|
||||
useRecordsWithoutConnection = false,
|
||||
}: ObjectMetadataItemIdentifier & {
|
||||
filter?: ObjectRecordQueryFilter;
|
||||
orderBy?: OrderByField;
|
||||
limit?: number;
|
||||
onCompleted?: (data: PaginatedRecordTypeResults<RecordType>) => void;
|
||||
onCompleted?: (data: ObjectRecordConnection<T>) => void;
|
||||
skip?: boolean;
|
||||
useRecordsWithoutConnection?: boolean;
|
||||
}) => {
|
||||
const findManyQueryStateIdentifier =
|
||||
objectNameSingular +
|
||||
@ -75,7 +75,7 @@ export const useFindManyRecords = <
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
|
||||
const { data, loading, error, fetchMore } = useQuery<
|
||||
PaginatedRecordType<RecordType>
|
||||
ObjectRecordQueryResult<T>
|
||||
>(findManyRecordsQuery, {
|
||||
skip: skip || !objectMetadataItem || !currentWorkspace,
|
||||
variables: {
|
||||
@ -88,10 +88,10 @@ export const useFindManyRecords = <
|
||||
|
||||
if (data?.[objectMetadataItem.namePlural]) {
|
||||
setLastCursor(
|
||||
data?.[objectMetadataItem.namePlural]?.pageInfo.endCursor,
|
||||
data?.[objectMetadataItem.namePlural]?.pageInfo.endCursor ?? '',
|
||||
);
|
||||
setHasNextPage(
|
||||
data?.[objectMetadataItem.namePlural]?.pageInfo.hasNextPage,
|
||||
data?.[objectMetadataItem.namePlural]?.pageInfo.hasNextPage ?? false,
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -125,7 +125,7 @@ export const useFindManyRecords = <
|
||||
const nextEdges =
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
|
||||
|
||||
let newEdges: PaginatedRecordTypeEdge<RecordType>[] = [];
|
||||
let newEdges: ObjectRecordEdge<T>[] = [];
|
||||
|
||||
if (isNonEmptyArray(previousEdges) && isNonEmptyArray(nextEdges)) {
|
||||
newEdges = filterUniqueRecordEdgesByCursor([
|
||||
@ -138,11 +138,11 @@ export const useFindManyRecords = <
|
||||
if (data?.[objectMetadataItem.namePlural]) {
|
||||
setLastCursor(
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo
|
||||
.endCursor,
|
||||
.endCursor ?? '',
|
||||
);
|
||||
setHasNextPage(
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo
|
||||
.hasNextPage,
|
||||
.hasNextPage ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@ -164,7 +164,7 @@ export const useFindManyRecords = <
|
||||
pageInfo:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
|
||||
},
|
||||
} as PaginatedRecordType<RecordType>);
|
||||
} as ObjectRecordQueryResult<T>);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
@ -198,18 +198,39 @@ export const useFindManyRecords = <
|
||||
enqueueSnackBar,
|
||||
]);
|
||||
|
||||
// TODO: remove this and use only mapConnectionToRecords when we've finished the refactor
|
||||
const records = useMemo(
|
||||
() =>
|
||||
mapPaginatedRecordsToRecords({
|
||||
pagedRecords: data,
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
}),
|
||||
}) as T[],
|
||||
[data, objectMetadataItem],
|
||||
);
|
||||
|
||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||
|
||||
const recordsWithoutConnection = useMemo(
|
||||
() =>
|
||||
useRecordsWithoutConnection
|
||||
? (mapConnectionToRecords({
|
||||
objectRecordConnection: data?.[objectMetadataItem.namePlural],
|
||||
objectNameSingular,
|
||||
depth: 5,
|
||||
}) as T[])
|
||||
: [],
|
||||
[
|
||||
data,
|
||||
objectNameSingular,
|
||||
objectMetadataItem.namePlural,
|
||||
mapConnectionToRecords,
|
||||
useRecordsWithoutConnection,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
records: records as RecordType[],
|
||||
records: useRecordsWithoutConnection ? recordsWithoutConnection : records,
|
||||
loading,
|
||||
error,
|
||||
fetchMoreRecords,
|
||||
|
@ -0,0 +1,113 @@
|
||||
import { useCallback } from 'react';
|
||||
import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { produce } from 'immer';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { parseFieldRelationType } from '@/object-metadata/utils/parseFieldRelationType';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useMapConnectionToRecords = () => {
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const mapConnectionToRecords = useCallback(
|
||||
<T extends ObjectRecord>({
|
||||
objectRecordConnection,
|
||||
objectNameSingular,
|
||||
objectNamePlural,
|
||||
depth,
|
||||
}: {
|
||||
objectRecordConnection: ObjectRecordConnection<T> | undefined | null;
|
||||
objectNameSingular?: string;
|
||||
objectNamePlural?: string;
|
||||
depth: number;
|
||||
}): ObjectRecord[] => {
|
||||
if (
|
||||
!isDefined(objectRecordConnection) ||
|
||||
!isNonEmptyArray(objectMetadataItems)
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentLevelObjectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular ||
|
||||
objectMetadataItem.namePlural === objectNamePlural,
|
||||
);
|
||||
|
||||
if (!currentLevelObjectMetadataItem) {
|
||||
throw new Error(
|
||||
`Could not find object metadata item for object name singular "${objectNameSingular}" in mapConnectionToRecords`,
|
||||
);
|
||||
}
|
||||
|
||||
const relationFields = currentLevelObjectMetadataItem.fields.filter(
|
||||
(field) => field.type === FieldMetadataType.Relation,
|
||||
);
|
||||
|
||||
const objectRecords = [
|
||||
...(objectRecordConnection.edges?.map((edge) => edge.node) ?? []),
|
||||
];
|
||||
|
||||
return produce(objectRecords, (objectRecordsDraft) => {
|
||||
for (const objectRecordDraft of objectRecordsDraft) {
|
||||
for (const relationField of relationFields) {
|
||||
const relationType = parseFieldRelationType(relationField);
|
||||
|
||||
if (
|
||||
relationType === 'TO_ONE_OBJECT' ||
|
||||
relationType === 'FROM_ONE_OBJECT'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relatedObjectMetadataSingularName =
|
||||
relationField.toRelationMetadata?.fromObjectMetadata
|
||||
.nameSingular ??
|
||||
relationField.fromRelationMetadata?.toObjectMetadata
|
||||
.nameSingular ??
|
||||
null;
|
||||
|
||||
const relationFieldMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular ===
|
||||
relatedObjectMetadataSingularName,
|
||||
);
|
||||
|
||||
if (
|
||||
!relationFieldMetadataItem ||
|
||||
!isDefined(relatedObjectMetadataSingularName)
|
||||
) {
|
||||
throw new Error(
|
||||
`Could not find relation object metadata item for object name plural ${relationField.name} in mapConnectionToRecords`,
|
||||
);
|
||||
}
|
||||
|
||||
const relationConnection = objectRecordDraft?.[
|
||||
relationField.name
|
||||
] as ObjectRecordConnection | undefined | null;
|
||||
|
||||
if (!isDefined(relationConnection)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const relationConnectionMappedToRecords = mapConnectionToRecords({
|
||||
objectRecordConnection: relationConnection,
|
||||
objectNameSingular: relatedObjectMetadataSingularName,
|
||||
depth: depth - 1,
|
||||
});
|
||||
|
||||
(objectRecordDraft as any)[relationField.name] =
|
||||
relationConnectionMappedToRecords;
|
||||
}
|
||||
}
|
||||
}) as ObjectRecord[];
|
||||
},
|
||||
[objectMetadataItems],
|
||||
);
|
||||
|
||||
return mapConnectionToRecords;
|
||||
};
|
@ -7,7 +7,7 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
|
||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||
import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates';
|
||||
import { turnObjectDropdownFilterIntoQueryFilter } from '@/object-record/record-filter/utils/turnObjectDropdownFilterIntoQueryFilter';
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { Opportunity } from '@/pipeline/types/Opportunity';
|
||||
import { PipelineStep } from '@/pipeline/types/PipelineStep';
|
||||
|
||||
@ -57,7 +57,7 @@ export const useObjectRecordBoard = () => {
|
||||
objectNameSingular: CoreObjectNameSingular.PipelineStep,
|
||||
filter: {},
|
||||
onCompleted: useCallback(
|
||||
(data: PaginatedRecordTypeResults<PipelineStep>) => {
|
||||
(data: ObjectRecordConnection<PipelineStep>) => {
|
||||
setSavedPipelineSteps(data.edges.map((edge) => edge.node));
|
||||
},
|
||||
[setSavedPipelineSteps],
|
||||
@ -89,7 +89,7 @@ export const useObjectRecordBoard = () => {
|
||||
},
|
||||
},
|
||||
onCompleted: useCallback(
|
||||
(data: PaginatedRecordTypeResults<Company>) => {
|
||||
(data: ObjectRecordConnection<Company>) => {
|
||||
setSavedCompanies(data.edges.map((edge) => edge.node));
|
||||
},
|
||||
[setSavedCompanies],
|
||||
|
@ -1,11 +1,14 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
import { Nullable } from '~/types/Nullable';
|
||||
|
||||
export type ObjectRecordConnection = {
|
||||
edges: ObjectRecordEdge[];
|
||||
export type ObjectRecordConnection<T extends ObjectRecord = ObjectRecord> = {
|
||||
__typename?: string;
|
||||
edges: ObjectRecordEdge<T>[];
|
||||
pageInfo: {
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
startCursor?: string;
|
||||
endCursor?: string;
|
||||
startCursor?: Nullable<string>;
|
||||
endCursor?: Nullable<string>;
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
export type ObjectRecordEdge = {
|
||||
node: ObjectRecord;
|
||||
export type ObjectRecordEdge<T extends ObjectRecord> = {
|
||||
__typename?: string;
|
||||
node: T;
|
||||
cursor: string;
|
||||
};
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
|
||||
export type ObjectRecordQueryResult<T extends ObjectRecord> = {
|
||||
[objectNamePlural: string]: ObjectRecordConnection<T>;
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
import { PaginatedRecordTypeResults } from './PaginatedRecordTypeResults';
|
||||
|
||||
export type PaginatedRecordType<RecordType extends { id: string }> = {
|
||||
[objectNamePlural: string]: PaginatedRecordTypeResults<RecordType>;
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
export type PaginatedRecordTypeEdge<
|
||||
RecordType extends { id: string } & Record<string, any>,
|
||||
> = {
|
||||
node: RecordType;
|
||||
cursor: string;
|
||||
__typename?: string;
|
||||
};
|
||||
|
||||
export type PaginatedRecordTypeResults<
|
||||
RecordType extends { id: string } & Record<string, any>,
|
||||
> = {
|
||||
__typename?: string;
|
||||
edges: PaginatedRecordTypeEdge<RecordType>[];
|
||||
pageInfo: {
|
||||
hasNextPage: boolean;
|
||||
startCursor: string;
|
||||
endCursor: string;
|
||||
};
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
import { PaginatedRecordTypeEdge } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { ObjectRecordEdge } from '@/object-record/types/ObjectRecordEdge';
|
||||
|
||||
export const filterUniqueRecordEdgesByCursor = <
|
||||
RecordType extends { id: string },
|
||||
>(
|
||||
arrayToFilter: PaginatedRecordTypeEdge<RecordType>[],
|
||||
arrayToFilter: ObjectRecordEdge<RecordType>[],
|
||||
) => {
|
||||
const seenCursors = new Set();
|
||||
|
||||
|
@ -41,6 +41,7 @@ export const ViewBarEffect = () => {
|
||||
type: { eq: viewType },
|
||||
objectMetadataId: { eq: viewObjectMetadataId },
|
||||
},
|
||||
useRecordsWithoutConnection: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -284,33 +284,11 @@ describe('useViewBar', () => {
|
||||
|
||||
viewBar.setAvailableSortDefinitions([sortDefinition]);
|
||||
|
||||
viewBar.loadViewSorts(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewSort,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
mockedUuid,
|
||||
);
|
||||
viewBar.loadViewSorts([viewSort], mockedUuid);
|
||||
|
||||
viewBar.setAvailableFilterDefinitions([filterDefinition]);
|
||||
|
||||
viewBar.loadViewFilters(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewFilter,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
mockedUuid,
|
||||
);
|
||||
viewBar.loadViewFilters([viewFilter], mockedUuid);
|
||||
|
||||
return { viewBar };
|
||||
}, renderHookConfig);
|
||||
|
@ -159,18 +159,7 @@ describe('useViewBar > viewFields', () => {
|
||||
await act(async () => {
|
||||
result.current.viewBar.setAvailableFieldDefinitions([fieldDefinition]);
|
||||
|
||||
await result.current.viewBar.loadViewFields(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewField,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
currentViewId,
|
||||
);
|
||||
await result.current.viewBar.loadViewFields([viewField], currentViewId);
|
||||
result.current.viewBar.setCurrentViewId(currentViewId);
|
||||
});
|
||||
|
||||
|
@ -69,18 +69,7 @@ describe('useViewBar > viewFilters', () => {
|
||||
await act(async () => {
|
||||
result.current.viewBar.setAvailableFilterDefinitions([filterDefinition]);
|
||||
|
||||
await result.current.viewBar.loadViewFilters(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewFilter,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
currentViewId,
|
||||
);
|
||||
await result.current.viewBar.loadViewFilters([viewFilter], currentViewId);
|
||||
result.current.viewBar.setCurrentViewId(currentViewId);
|
||||
});
|
||||
|
||||
@ -93,18 +82,7 @@ describe('useViewBar > viewFilters', () => {
|
||||
|
||||
viewBar.setAvailableFilterDefinitions([filterDefinition]);
|
||||
|
||||
viewBar.loadViewFilters(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewFilter,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
currentViewId,
|
||||
);
|
||||
viewBar.loadViewFilters([viewFilter], currentViewId);
|
||||
viewBar.setCurrentViewId(currentViewId);
|
||||
|
||||
const { currentViewFiltersState } = useViewScopedStates({
|
||||
@ -174,18 +152,7 @@ describe('useViewBar > viewFilters', () => {
|
||||
|
||||
viewBar.setAvailableFilterDefinitions([filterDefinition]);
|
||||
|
||||
viewBar.loadViewFilters(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewFilter,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
currentViewId,
|
||||
);
|
||||
viewBar.loadViewFilters([viewFilter], currentViewId);
|
||||
viewBar.setCurrentViewId(currentViewId);
|
||||
|
||||
const { currentViewFiltersState } = useViewScopedStates({
|
||||
|
@ -65,18 +65,7 @@ describe('View Sorts', () => {
|
||||
await act(async () => {
|
||||
result.current.viewBar.setAvailableSortDefinitions([sortDefinition]);
|
||||
|
||||
await result.current.viewBar.loadViewSorts(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewSort,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
currentViewId,
|
||||
);
|
||||
await result.current.viewBar.loadViewSorts([viewSort], currentViewId);
|
||||
result.current.viewBar.setCurrentViewId(currentViewId);
|
||||
});
|
||||
|
||||
@ -89,18 +78,7 @@ describe('View Sorts', () => {
|
||||
|
||||
viewBar.setAvailableSortDefinitions([sortDefinition]);
|
||||
|
||||
viewBar.loadViewSorts(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewSort,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
currentViewId,
|
||||
);
|
||||
viewBar.loadViewSorts([viewSort], currentViewId);
|
||||
viewBar.setCurrentViewId(currentViewId);
|
||||
|
||||
const { currentViewSortsState } = useViewScopedStates({
|
||||
@ -161,18 +139,7 @@ describe('View Sorts', () => {
|
||||
|
||||
viewBar.setAvailableSortDefinitions([sortDefinition]);
|
||||
|
||||
viewBar.loadViewSorts(
|
||||
{
|
||||
edges: [
|
||||
{
|
||||
node: viewSort,
|
||||
cursor: '',
|
||||
},
|
||||
],
|
||||
pageInfo: { hasNextPage: false, startCursor: '', endCursor: '' },
|
||||
},
|
||||
currentViewId,
|
||||
);
|
||||
viewBar.loadViewSorts([viewSort], currentViewId);
|
||||
viewBar.setCurrentViewId(currentViewId);
|
||||
|
||||
const { currentViewSortsState } = useViewScopedStates({
|
||||
|
@ -3,7 +3,6 @@ import { useSearchParams } from 'react-router-dom';
|
||||
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
@ -93,10 +92,7 @@ export const useViewBar = (props?: UseViewProps) => {
|
||||
|
||||
const loadViewFields = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (
|
||||
data: PaginatedRecordTypeResults<ViewField>,
|
||||
currentViewId: string,
|
||||
) => {
|
||||
async (viewFields: ViewField[], currentViewId: string) => {
|
||||
const {
|
||||
availableFieldDefinitions,
|
||||
onViewFieldsChange,
|
||||
@ -119,8 +115,8 @@ export const useViewBar = (props?: UseViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const queriedViewFields = data.edges
|
||||
.map((viewField) => viewField.node)
|
||||
const queriedViewFields = viewFields
|
||||
.map((viewField) => viewField)
|
||||
.filter(assertNotNull);
|
||||
|
||||
if (isPersistingView) {
|
||||
@ -138,10 +134,7 @@ export const useViewBar = (props?: UseViewProps) => {
|
||||
|
||||
const loadViewFilters = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (
|
||||
data: PaginatedRecordTypeResults<Required<ViewFilter>>,
|
||||
currentViewId: string,
|
||||
) => {
|
||||
async (viewFilters: ViewFilter[], currentViewId: string) => {
|
||||
const {
|
||||
availableFilterDefinitions,
|
||||
savedViewFilters,
|
||||
@ -163,18 +156,18 @@ export const useViewBar = (props?: UseViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const queriedViewFilters = data.edges
|
||||
.map(({ node }) => {
|
||||
const queriedViewFilters = viewFilters
|
||||
.map((viewFilter) => {
|
||||
const availableFilterDefinition = availableFilterDefinitions.find(
|
||||
(filterDefinition) =>
|
||||
filterDefinition.fieldMetadataId === node.fieldMetadataId,
|
||||
filterDefinition.fieldMetadataId === viewFilter.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!availableFilterDefinition) return null;
|
||||
|
||||
return {
|
||||
...node,
|
||||
displayValue: node.displayValue ?? node.value,
|
||||
...viewFilter,
|
||||
displayValue: viewFilter.displayValue ?? viewFilter.value,
|
||||
definition: availableFilterDefinition,
|
||||
};
|
||||
})
|
||||
@ -191,10 +184,7 @@ export const useViewBar = (props?: UseViewProps) => {
|
||||
|
||||
const loadViewSorts = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async (
|
||||
data: PaginatedRecordTypeResults<Required<ViewSort>>,
|
||||
currentViewId: string,
|
||||
) => {
|
||||
async (viewSorts: Required<ViewSort>[], currentViewId: string) => {
|
||||
const { availableSortDefinitions, savedViewSorts, onViewSortsChange } =
|
||||
getViewScopedStateValuesFromSnapshot({
|
||||
snapshot,
|
||||
@ -213,18 +203,18 @@ export const useViewBar = (props?: UseViewProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const queriedViewSorts = data.edges
|
||||
.map(({ node }) => {
|
||||
const queriedViewSorts = viewSorts
|
||||
.map((viewSort) => {
|
||||
const availableSortDefinition = availableSortDefinitions.find(
|
||||
(sort) => sort.fieldMetadataId === node.fieldMetadataId,
|
||||
(sort) => sort.fieldMetadataId === viewSort.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!availableSortDefinition) return null;
|
||||
|
||||
return {
|
||||
id: node.id,
|
||||
fieldMetadataId: node.fieldMetadataId,
|
||||
direction: node.direction,
|
||||
id: viewSort.id,
|
||||
fieldMetadataId: viewSort.fieldMetadataId,
|
||||
direction: viewSort.direction,
|
||||
definition: availableSortDefinition,
|
||||
};
|
||||
})
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { ViewField } from '@/views/types/ViewField';
|
||||
import { ViewFilter } from '@/views/types/ViewFilter';
|
||||
import { ViewSort } from '@/views/types/ViewSort';
|
||||
@ -7,7 +6,7 @@ export type GraphQLView = {
|
||||
id: string;
|
||||
name: string;
|
||||
objectMetadataId: string;
|
||||
viewFields: PaginatedRecordTypeResults<ViewField>;
|
||||
viewFilters: PaginatedRecordTypeResults<ViewFilter>;
|
||||
viewSorts: PaginatedRecordTypeResults<ViewSort>;
|
||||
viewFields: ViewField[];
|
||||
viewFilters: ViewFilter[];
|
||||
viewSorts: ViewSort[];
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsHeaderContainer } from '@/settings/components/SettingsHeaderContainer';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
@ -93,7 +93,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
type: { eq: ViewType.Table },
|
||||
objectMetadataId: { eq: activeObjectMetadataItem?.id },
|
||||
},
|
||||
onCompleted: async (data: PaginatedRecordTypeResults<View>) => {
|
||||
onCompleted: async (data: ObjectRecordConnection<View>) => {
|
||||
const views = data.edges;
|
||||
|
||||
if (!views) return;
|
||||
@ -109,7 +109,7 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
type: { eq: ViewType.Table },
|
||||
objectMetadataId: { eq: formValues.relation?.objectMetadataId },
|
||||
},
|
||||
onCompleted: async (data: PaginatedRecordTypeResults<View>) => {
|
||||
onCompleted: async (data: ObjectRecordConnection<View>) => {
|
||||
const views = data.edges;
|
||||
|
||||
if (!views) return;
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider, MockedResponse } from '@apollo/client/testing';
|
||||
import { MutableSnapshot, RecoilRoot } from 'recoil';
|
||||
|
||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||
|
||||
export const getJestHookWrapper = ({
|
||||
apolloMocks,
|
||||
onInitializeRecoilSnapshot,
|
||||
}: {
|
||||
apolloMocks:
|
||||
| readonly MockedResponse<Record<string, any>, Record<string, any>>[]
|
||||
| undefined;
|
||||
onInitializeRecoilSnapshot?: (snapshot: MutableSnapshot) => void;
|
||||
}) => {
|
||||
return ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot initializeState={onInitializeRecoilSnapshot}>
|
||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||
<MockedProvider mocks={apolloMocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</SnackBarProviderScope>
|
||||
</RecoilRoot>
|
||||
);
|
||||
};
|
@ -35183,7 +35183,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"playwright@npm:^1.14.0":
|
||||
"playwright@npm:^1.14.0, playwright@npm:^1.40.1":
|
||||
version: 1.40.1
|
||||
resolution: "playwright@npm:1.40.1"
|
||||
dependencies:
|
||||
@ -41675,6 +41675,7 @@ __metadata:
|
||||
patch-package: "npm:^8.0.0"
|
||||
pg: "npm:^8.11.3"
|
||||
pg-boss: "npm:^9.0.3"
|
||||
playwright: "npm:^1.40.1"
|
||||
prettier: "npm:^3.1.1"
|
||||
prism-react-renderer: "npm:^2.1.0"
|
||||
raw-loader: "npm:^4.0.2"
|
||||
|
Loading…
Reference in New Issue
Block a user