From 9d4ed323a791d1d1c54334a888f5b2f41af3516b Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sat, 9 Dec 2023 10:38:37 +0100 Subject: [PATCH] Fix optimistic rendering (#2882) * Release 0.2.1 * Optimistic rendering fixes * Fix optimistic rendering * Fix issues on Tasks * Fix Opportunity picker and relation picker --- front/package.json | 4 +- .../components/ActivityBodyEditor.tsx | 1 + .../activities/components/ActivityEditor.tsx | 1 + .../hooks/useOpenCreateActivityDrawer.ts | 1 + .../components/ActivityActionBar.tsx | 1 + .../activities/tasks/hooks/useCompleteTask.ts | 1 + .../hooks/useOptimisticEffect.ts | 1 + .../components/NewOpportunityButton.tsx | 6 +- .../components/RecordShowPage.tsx | 38 ++++---- .../object-record/hooks/useCreateOneRecord.ts | 39 +++++--- .../object-record/hooks/useDeleteOneRecord.ts | 22 +++-- .../hooks/useGenerateEmptyRecord.ts | 96 +++++++++---------- .../object-record/hooks/useUpdateOneRecord.ts | 25 +++-- .../components/RelationPicker.tsx | 6 +- .../SingleEntitySelect.stories.tsx | 7 +- .../internal/useRelationPickerScopedStates.ts | 13 ++- .../hooks/useEntitySelectScroll.ts | 13 +-- .../hooks/useEntitySelectSearch.ts | 15 ++- .../hooks/useRelationPicker.ts | 22 ++++- .../relationPickerPreselectedIdScopedState.ts | 9 +- .../relationPickerSearchFilterScopedState.ts | 12 +-- .../utils/getRelationPickerScopedStates.ts | 14 +++ .../components/NavigationDrawerSection.tsx | 2 +- .../SettingsObjectNewFieldStep2.tsx | 66 +++++++++++-- server/package.json | 4 +- .../object-metadata.service.ts | 5 +- 26 files changed, 267 insertions(+), 157 deletions(-) diff --git a/front/package.json b/front/package.json index 25d9f2712..2fb923515 100644 --- a/front/package.json +++ b/front/package.json @@ -1,6 +1,6 @@ { "name": "twenty", - "version": "0.2.0", + "version": "0.2.1", "private": true, "dependencies": { "@air/react-drag-to-select": "^5.0.8", @@ -179,4 +179,4 @@ "msw": { "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/front/src/modules/activities/components/ActivityBodyEditor.tsx b/front/src/modules/activities/components/ActivityBodyEditor.tsx index c5b470da5..03ad3482c 100644 --- a/front/src/modules/activities/components/ActivityBodyEditor.tsx +++ b/front/src/modules/activities/components/ActivityBodyEditor.tsx @@ -25,6 +25,7 @@ export const ActivityBodyEditor = ({ const [body, setBody] = useState(null); const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular: 'activity', + refetchFindManyQuery: true, }); useEffect(() => { diff --git a/front/src/modules/activities/components/ActivityEditor.tsx b/front/src/modules/activities/components/ActivityEditor.tsx index 023a5b4d9..612754c6c 100644 --- a/front/src/modules/activities/components/ActivityEditor.tsx +++ b/front/src/modules/activities/components/ActivityEditor.tsx @@ -82,6 +82,7 @@ export const ActivityEditor = ({ const containerRef = useRef(null); const { updateOneRecord: updateOneActivity } = useUpdateOneRecord({ objectNameSingular: 'activity', + refetchFindManyQuery: true, }); const { FieldContextProvider: DueAtFieldContextProvider } = useFieldContext({ diff --git a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts index 628f88421..0d90a48a3 100644 --- a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts +++ b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts @@ -24,6 +24,7 @@ export const useOpenCreateActivityDrawer = () => { }); const { createOneRecord: createOneActivity } = useCreateOneRecord({ objectNameSingular: 'activity', + refetchFindManyQuery: true, }); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const setHotkeyScope = useSetHotkeyScope(); diff --git a/front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx b/front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx index 8b28d1263..a104636e1 100644 --- a/front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx +++ b/front/src/modules/activities/right-drawer/components/ActivityActionBar.tsx @@ -13,6 +13,7 @@ export const ActivityActionBar = ({ activityId }: ActivityActionBarProps) => { const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState); const { deleteOneRecord: deleteOneActivity } = useDeleteOneRecord({ objectNameSingular: 'activity', + refetchFindManyQuery: true, }); const deleteActivity = () => { diff --git a/front/src/modules/activities/tasks/hooks/useCompleteTask.ts b/front/src/modules/activities/tasks/hooks/useCompleteTask.ts index e3a015a84..36bc95b8d 100644 --- a/front/src/modules/activities/tasks/hooks/useCompleteTask.ts +++ b/front/src/modules/activities/tasks/hooks/useCompleteTask.ts @@ -8,6 +8,7 @@ type Task = Pick; export const useCompleteTask = (task: Task) => { const { updateOneRecord: updateOneActivity } = useUpdateOneRecord({ objectNameSingular: 'activity', + refetchFindManyQuery: true, }); const completeTask = useCallback( diff --git a/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts b/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts index 3ba46aae3..7bbc90bd8 100644 --- a/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts +++ b/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts @@ -146,6 +146,7 @@ export const useOptimisticEffect = ({ } } }, + [apolloClient.cache], ); return { diff --git a/front/src/modules/companies/components/NewOpportunityButton.tsx b/front/src/modules/companies/components/NewOpportunityButton.tsx index a0ca37159..b4923005a 100644 --- a/front/src/modules/companies/components/NewOpportunityButton.tsx +++ b/front/src/modules/companies/components/NewOpportunityButton.tsx @@ -7,12 +7,10 @@ import { BoardColumnContext } from '@/object-record/record-board/contexts/BoardC import { useCreateOpportunity } from '@/object-record/record-board/hooks/internal/useCreateOpportunity'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; -import { relationPickerSearchFilterScopedState } from '@/object-record/relation-picker/states/relationPickerSearchFilterScopedState'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; export const NewOpportunityButton = () => { const [isCreatingCard, setIsCreatingCard] = useState(false); @@ -55,9 +53,7 @@ export const NewOpportunityButton = () => { setIsCreatingCard(false); }; - const [relationPickerSearchFilter] = useRecoilScopedState( - relationPickerSearchFilterScopedState, - ); + const { relationPickerSearchFilter } = useRelationPicker(); // TODO: refactor useFilteredSearchEntityQuery const { findManyRecordsQuery } = useObjectMetadataItem({ diff --git a/front/src/modules/object-record/components/RecordShowPage.tsx b/front/src/modules/object-record/components/RecordShowPage.tsx index 477017436..03e722849 100644 --- a/front/src/modules/object-record/components/RecordShowPage.tsx +++ b/front/src/modules/object-record/components/RecordShowPage.tsx @@ -68,6 +68,13 @@ export const RecordShowPage = () => { objectNameSingular, }); + const objectMetadataType = + objectMetadataItem?.nameSingular === 'company' + ? 'Company' + : objectMetadataItem?.nameSingular === 'person' + ? 'Person' + : 'Custom'; + const useUpdateOneObjectRecordMutation: () => [ (params: any) => any, any, @@ -171,22 +178,21 @@ export const RecordShowPage = () => { hasBackButton Icon={IconBuildingSkyscraper} > - - + {objectMetadataType !== 'Custom' && ( + <> + + + + )} diff --git a/front/src/modules/object-record/hooks/useCreateOneRecord.ts b/front/src/modules/object-record/hooks/useCreateOneRecord.ts index d8c3ffe24..92d88c461 100644 --- a/front/src/modules/object-record/hooks/useCreateOneRecord.ts +++ b/front/src/modules/object-record/hooks/useCreateOneRecord.ts @@ -1,24 +1,29 @@ import { useApolloClient } from '@apollo/client'; +import { getOperationName } from '@apollo/client/utilities'; import { v4 } from 'uuid'; import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { useGenerateEmptyRecord } from '@/object-record/hooks/useGenerateEmptyRecord'; import { capitalize } from '~/utils/string/capitalize'; +type useCreateOneRecordProps = { + objectNameSingular: string; + refetchFindManyQuery?: boolean; +}; + export const useCreateOneRecord = ({ objectNameSingular, -}: ObjectMetadataItemIdentifier) => { + refetchFindManyQuery = false, +}: useCreateOneRecordProps) => { const { triggerOptimisticEffects } = useOptimisticEffect({ objectNameSingular, }); - const { objectMetadataItem, createOneRecordMutation } = useObjectMetadataItem( - { + const { objectMetadataItem, createOneRecordMutation, findManyRecordsQuery } = + useObjectMetadataItem({ objectNameSingular, - }, - ); + }); // TODO: type this with a minimal type at least with Record const apolloClient = useApolloClient(); @@ -30,20 +35,30 @@ export const useCreateOneRecord = ({ const createOneRecord = async (input: Record) => { const recordId = v4(); - triggerOptimisticEffects( - `${capitalize(objectMetadataItem.nameSingular)}Edge`, - generateEmptyRecord(recordId), - ); + const generatedEmptyRecord = generateEmptyRecord({ + id: recordId, + ...input, + }); + + if (generatedEmptyRecord) { + triggerOptimisticEffects( + `${capitalize(objectMetadataItem.nameSingular)}Edge`, + generatedEmptyRecord, + ); + } const createdObject = await apolloClient.mutate({ mutation: createOneRecordMutation, variables: { - input: { ...input, id: recordId }, + input: { id: recordId, ...input }, }, optimisticResponse: { [`create${capitalize(objectMetadataItem.nameSingular)}`]: - generateEmptyRecord(recordId), + generateEmptyRecord({ id: recordId, ...input }), }, + refetchQueries: refetchFindManyQuery + ? [getOperationName(findManyRecordsQuery) ?? ''] + : [], }); if (!createdObject.data) { diff --git a/front/src/modules/object-record/hooks/useDeleteOneRecord.ts b/front/src/modules/object-record/hooks/useDeleteOneRecord.ts index 42b3fcc23..839e2edd1 100644 --- a/front/src/modules/object-record/hooks/useDeleteOneRecord.ts +++ b/front/src/modules/object-record/hooks/useDeleteOneRecord.ts @@ -1,25 +1,30 @@ import { useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; +import { getOperationName } from '@apollo/client/utilities'; import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; import { useOptimisticEvict } from '@/apollo/optimistic-effect/hooks/useOptimisticEvict'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { capitalize } from '~/utils/string/capitalize'; +type useDeleteOneRecordProps = { + objectNameSingular: string; + refetchFindManyQuery?: boolean; +}; + export const useDeleteOneRecord = ({ objectNameSingular, -}: ObjectMetadataItemIdentifier) => { + refetchFindManyQuery = false, +}: useDeleteOneRecordProps) => { const { performOptimisticEvict } = useOptimisticEvict(); const { triggerOptimisticEffects } = useOptimisticEffect({ objectNameSingular, }); - const { objectMetadataItem, deleteOneRecordMutation } = useObjectMetadataItem( - { + const { objectMetadataItem, deleteOneRecordMutation, findManyRecordsQuery } = + useObjectMetadataItem({ objectNameSingular, - }, - ); + }); const apolloClient = useApolloClient(); @@ -42,6 +47,9 @@ export const useDeleteOneRecord = ({ variables: { idToDelete, }, + refetchQueries: refetchFindManyQuery + ? [getOperationName(findManyRecordsQuery) ?? ''] + : [], }); return deletedRecord.data[ @@ -54,6 +62,8 @@ export const useDeleteOneRecord = ({ performOptimisticEvict, apolloClient, deleteOneRecordMutation, + refetchFindManyQuery, + findManyRecordsQuery, ], ); diff --git a/front/src/modules/object-record/hooks/useGenerateEmptyRecord.ts b/front/src/modules/object-record/hooks/useGenerateEmptyRecord.ts index e6fd1a709..23350b18a 100644 --- a/front/src/modules/object-record/hooks/useGenerateEmptyRecord.ts +++ b/front/src/modules/object-record/hooks/useGenerateEmptyRecord.ts @@ -5,13 +5,17 @@ export const useGenerateEmptyRecord = ({ }: { objectMetadataItem: ObjectMetadataItem; }) => { - const generateEmptyRecord = (id: string) => { + // Todo fix typing once we generate the return base on Metadata + const generateEmptyRecord = (input: Partial & { id: string }) => { + // Todo replace this by runtime typing + const validatedInput = input as { id: string } & { [key: string]: any }; + if (objectMetadataItem.nameSingular === 'company') { return { - id, + id: validatedInput.id, domainName: '', accountOwnerId: null, - createdAt: '2023-12-05T16:04:42.261Z', + createdAt: new Date().toISOString(), address: '', people: [ { @@ -38,7 +42,7 @@ export const useGenerateEmptyRecord = ({ currencyCode: null, __typename: 'Currency', }, - updatedAt: '2023-12-05T16:04:42.261Z', + updatedAt: new Date().toISOString(), employees: null, accountOwner: null, name: '', @@ -56,12 +60,12 @@ export const useGenerateEmptyRecord = ({ __typename: 'OpportunityConnection', }, __typename: 'Company', - }; + } as T; } if (objectMetadataItem.nameSingular === 'person') { return { - id, + id: validatedInput.id, activityTargets: { edges: [], __typename: 'ActivityTargetConnection', @@ -98,8 +102,8 @@ export const useGenerateEmptyRecord = ({ __typename: 'FullName', }, avatarUrl: '', - updatedAt: '2023-12-05T16:45:11.840Z', - createdAt: '2023-12-05T16:45:11.840Z', + updatedAt: new Date().toISOString(), + createdAt: new Date().toISOString(), city: '', linkedinLink: { label: '', @@ -107,25 +111,16 @@ export const useGenerateEmptyRecord = ({ __typename: 'Link', }, __typename: 'Person', - }; + } as T; } if (objectMetadataItem.nameSingular === 'opportunity') { return { - id, - pipelineStepId: '30b14887-d592-427d-bd97-6e670158db02', + id: validatedInput.id, + pipelineStepId: validatedInput.pipelineStepId, closeDate: null, - companyId: '04b2e9f5-0713-40a5-8216-82802401d33e', - updatedAt: '2023-12-05T16:46:27.621Z', - pipelineStep: { - id: '30b14887-d592-427d-bd97-6e670158db02', - position: 2, - name: 'Meeting', - updatedAt: '2023-12-05T11:29:21.485Z', - createdAt: '2023-12-05T11:29:21.485Z', - color: 'sky', - __typename: 'PipelineStep', - }, + updatedAt: new Date().toISOString(), + pipelineStep: null, probability: '0', pointOfContactId: null, personId: null, @@ -134,41 +129,38 @@ export const useGenerateEmptyRecord = ({ currencyCode: null, __typename: 'Currency', }, - createdAt: '2023-12-05T16:46:27.621Z', + createdAt: new Date().toISOString(), pointOfContact: null, person: null, - company: { - id: '04b2e9f5-0713-40a5-8216-82802401d33e', - domainName: 'qonto.com', - accountOwnerId: null, - createdAt: '2023-12-05T11:29:21.484Z', - address: '', - xLink: { - label: '', - url: '', - __typename: 'Link', - }, - idealCustomerProfile: null, - annualRecurringRevenue: { - amountMicros: null, - currencyCode: null, - __typename: 'Currency', - }, - updatedAt: '2023-12-05T11:29:21.484Z', - employees: null, - name: 'Qonto', - linkedinLink: { - label: '', - url: '', - __typename: 'Link', - }, - __typename: 'Company', - }, + company: null, + companyId: validatedInput.companyId, __typename: 'Opportunity', - }; + } as T; } - return {}; + if (objectMetadataItem.nameSingular === 'opportunity') { + return { + id: validatedInput.id, + pipelineStepId: validatedInput.pipelineStepId, + closeDate: null, + updatedAt: new Date().toISOString(), + pipelineStep: null, + probability: '0', + pointOfContactId: null, + personId: null, + amount: { + amountMicros: null, + currencyCode: null, + __typename: 'Currency', + }, + createdAt: new Date().toISOString(), + pointOfContact: null, + person: null, + company: null, + companyId: validatedInput.companyId, + __typename: 'Opportunity', + } as T; + } }; return { diff --git a/front/src/modules/object-record/hooks/useUpdateOneRecord.ts b/front/src/modules/object-record/hooks/useUpdateOneRecord.ts index dcf81f809..a9543d278 100644 --- a/front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -1,16 +1,26 @@ import { useApolloClient } from '@apollo/client'; +import { getOperationName } from '@apollo/client/utilities'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; -import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { capitalize } from '~/utils/string/capitalize'; +type useUpdateOneRecordProps = { + objectNameSingular: string; + refetchFindManyQuery?: boolean; +}; + export const useUpdateOneRecord = ({ objectNameSingular, -}: ObjectMetadataItemIdentifier) => { - const { objectMetadataItem, updateOneRecordMutation, getRecordFromCache } = - useObjectMetadataItem({ - objectNameSingular, - }); + refetchFindManyQuery = false, +}: useUpdateOneRecordProps) => { + const { + objectMetadataItem, + updateOneRecordMutation, + getRecordFromCache, + findManyRecordsQuery, + } = useObjectMetadataItem({ + objectNameSingular, + }); const apolloClient = useApolloClient(); @@ -38,6 +48,9 @@ export const useUpdateOneRecord = ({ ...input, }, }, + refetchQueries: refetchFindManyQuery + ? [getOperationName(findManyRecordsQuery) ?? ''] + : [], }); if (!updatedRecord?.data) { diff --git a/front/src/modules/object-record/relation-picker/components/RelationPicker.tsx b/front/src/modules/object-record/relation-picker/components/RelationPicker.tsx index e0366f9d9..8f1bc6b77 100644 --- a/front/src/modules/object-record/relation-picker/components/RelationPicker.tsx +++ b/front/src/modules/object-record/relation-picker/components/RelationPicker.tsx @@ -7,11 +7,9 @@ import { FieldDefinition } from '@/object-record/field/types/FieldDefinition'; import { FieldRelationMetadata } from '@/object-record/field/types/FieldMetadata'; import { SingleEntitySelect } from '@/object-record/relation-picker/components/SingleEntitySelect'; import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; -import { relationPickerSearchFilterScopedState } from '@/object-record/relation-picker/states/relationPickerSearchFilterScopedState'; import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { IconForbid } from '@/ui/display/icon'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; export type RelationPickerProps = { recordId?: string; @@ -32,8 +30,8 @@ export const RelationPicker = ({ initialSearchFilter, fieldDefinition, }: RelationPickerProps) => { - const [relationPickerSearchFilter, setRelationPickerSearchFilter] = - useRecoilScopedState(relationPickerSearchFilterScopedState); + const { relationPickerSearchFilter, setRelationPickerSearchFilter } = + useRelationPicker(); useEffect(() => { setRelationPickerSearchFilter(initialSearchFilter ?? ''); diff --git a/front/src/modules/object-record/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx b/front/src/modules/object-record/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx index 3b666a8fa..317cce650 100644 --- a/front/src/modules/object-record/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx +++ b/front/src/modules/object-record/relation-picker/components/__stories__/SingleEntitySelect.stories.tsx @@ -2,14 +2,13 @@ import { expect } from '@storybook/jest'; import { Meta, StoryObj } from '@storybook/react'; import { userEvent, within } from '@storybook/testing-library'; +import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; import { IconUserCircle } from '@/ui/display/icon'; -import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; import { ComponentWithRecoilScopeDecorator } from '~/testing/decorators/ComponentWithRecoilScopeDecorator'; import { mockedPeopleData } from '~/testing/mock-data/people'; import { sleep } from '~/testing/sleep'; -import { relationPickerSearchFilterScopedState } from '../../states/relationPickerSearchFilterScopedState'; import { EntityForSelect } from '../../types/EntityForSelect'; import { SingleEntitySelect } from '../SingleEntitySelect'; @@ -44,9 +43,7 @@ const meta: Meta = { width, }) => { // eslint-disable-next-line react-hooks/rules-of-hooks - const relationPickerSearchFilter = useRecoilScopedValue( - relationPickerSearchFilterScopedState, - ); + const { relationPickerSearchFilter } = useRelationPicker(); return ( ; }) => { - const [relationPickerPreselectedId, setRelationPickerPreselectedId] = - useRecoilScopedState( - relationPickerPreselectedIdScopedState, - RelationPickerRecoilScopeContext, - ); + const { relationPickerPreselectedId, setRelationPickerPreselectedId } = + useRelationPicker(); const preselectedIdIndex = getPreselectedIdIndex( selectableOptionIds, - relationPickerPreselectedId, + relationPickerPreselectedId ?? '', ); const resetScroll = () => { diff --git a/front/src/modules/object-record/relation-picker/hooks/useEntitySelectSearch.ts b/front/src/modules/object-record/relation-picker/hooks/useEntitySelectSearch.ts index 241da3f73..379ef7bdb 100644 --- a/front/src/modules/object-record/relation-picker/hooks/useEntitySelectSearch.ts +++ b/front/src/modules/object-record/relation-picker/hooks/useEntitySelectSearch.ts @@ -1,16 +1,13 @@ import debounce from 'lodash.debounce'; -import { relationPickerPreselectedIdScopedState } from '@/object-record/relation-picker/states/relationPickerPreselectedIdScopedState'; -import { relationPickerSearchFilterScopedState } from '@/object-record/relation-picker/states/relationPickerSearchFilterScopedState'; -import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; +import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker'; export const useEntitySelectSearch = () => { - const [, setRelationPickerPreselectedId] = useRecoilScopedState( - relationPickerPreselectedIdScopedState, - ); - - const [relationPickerSearchFilter, setRelationPickerSearchFilter] = - useRecoilScopedState(relationPickerSearchFilterScopedState); + const { + setRelationPickerPreselectedId, + relationPickerSearchFilter, + setRelationPickerSearchFilter, + } = useRelationPicker(); const debouncedSetSearchFilter = debounce( setRelationPickerSearchFilter, diff --git a/front/src/modules/object-record/relation-picker/hooks/useRelationPicker.ts b/front/src/modules/object-record/relation-picker/hooks/useRelationPicker.ts index fce29cb6c..b71671b2e 100644 --- a/front/src/modules/object-record/relation-picker/hooks/useRelationPicker.ts +++ b/front/src/modules/object-record/relation-picker/hooks/useRelationPicker.ts @@ -14,10 +14,14 @@ export const useRelationPicker = (props?: useRelationPickeProps) => { props?.relationPickerScopeId, ); - const { identifiersMapperState, searchQueryState } = - useRelationPickerScopedStates({ - relationPickerScopedId: scopeId, - }); + const { + identifiersMapperState, + searchQueryState, + relationPickerSearchFilterState, + relationPickerPreselectedIdState, + } = useRelationPickerScopedStates({ + relationPickerScopedId: scopeId, + }); const [identifiersMapper, setIdentifiersMapper] = useRecoilState( identifiersMapperState, @@ -25,11 +29,21 @@ export const useRelationPicker = (props?: useRelationPickeProps) => { const [searchQuery, setSearchQuery] = useRecoilState(searchQueryState); + const [relationPickerSearchFilter, setRelationPickerSearchFilter] = + useRecoilState(relationPickerSearchFilterState); + + const [relationPickerPreselectedId, setRelationPickerPreselectedId] = + useRecoilState(relationPickerPreselectedIdState); + return { scopeId, identifiersMapper, setIdentifiersMapper, searchQuery, setSearchQuery, + relationPickerSearchFilter, + setRelationPickerSearchFilter, + relationPickerPreselectedId, + setRelationPickerPreselectedId, }; }; diff --git a/front/src/modules/object-record/relation-picker/states/relationPickerPreselectedIdScopedState.ts b/front/src/modules/object-record/relation-picker/states/relationPickerPreselectedIdScopedState.ts index 101f15a50..33707973b 100644 --- a/front/src/modules/object-record/relation-picker/states/relationPickerPreselectedIdScopedState.ts +++ b/front/src/modules/object-record/relation-picker/states/relationPickerPreselectedIdScopedState.ts @@ -1,9 +1,8 @@ -import { atomFamily } from 'recoil'; +import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; -export const relationPickerPreselectedIdScopedState = atomFamily< - string, - string +export const relationPickerPreselectedIdScopedState = createScopedState< + string | undefined >({ key: 'relationPickerPreselectedIdScopedState', - default: (param) => param, + defaultValue: undefined, }); diff --git a/front/src/modules/object-record/relation-picker/states/relationPickerSearchFilterScopedState.ts b/front/src/modules/object-record/relation-picker/states/relationPickerSearchFilterScopedState.ts index 6be7ea8bf..113d2f4ce 100644 --- a/front/src/modules/object-record/relation-picker/states/relationPickerSearchFilterScopedState.ts +++ b/front/src/modules/object-record/relation-picker/states/relationPickerSearchFilterScopedState.ts @@ -1,8 +1,6 @@ -import { atomFamily } from 'recoil'; +import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState'; -export const relationPickerSearchFilterScopedState = atomFamily( - { - key: 'relationPickerSearchFilterScopedState', - default: '', - }, -); +export const relationPickerSearchFilterScopedState = createScopedState({ + key: 'relationPickerSearchFilterScopedState', + defaultValue: '', +}); diff --git a/front/src/modules/object-record/relation-picker/utils/getRelationPickerScopedStates.ts b/front/src/modules/object-record/relation-picker/utils/getRelationPickerScopedStates.ts index ba9381d79..d2962561e 100644 --- a/front/src/modules/object-record/relation-picker/utils/getRelationPickerScopedStates.ts +++ b/front/src/modules/object-record/relation-picker/utils/getRelationPickerScopedStates.ts @@ -1,4 +1,6 @@ import { identifiersMapperScopedState } from '@/object-record/relation-picker/states/identifiersMapperScopedState'; +import { relationPickerPreselectedIdScopedState } from '@/object-record/relation-picker/states/relationPickerPreselectedIdScopedState'; +import { relationPickerSearchFilterScopedState } from '@/object-record/relation-picker/states/relationPickerSearchFilterScopedState'; import { searchQueryScopedState } from '@/object-record/relation-picker/states/searchQueryScopedState'; import { getScopedState } from '@/ui/utilities/recoil-scope/utils/getScopedState'; @@ -17,8 +19,20 @@ export const getRelationPickerScopedStates = ({ relationPickerScopeId, ); + const relationPickerPreselectedIdState = getScopedState( + relationPickerPreselectedIdScopedState, + relationPickerScopeId, + ); + + const relationPickerSearchFilterState = getScopedState( + relationPickerSearchFilterScopedState, + relationPickerScopeId, + ); + return { identifiersMapperState, + relationPickerSearchFilterState, + relationPickerPreselectedIdState, searchQueryState, }; }; diff --git a/front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx b/front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx index 7847ccaf1..bea1d0f8e 100644 --- a/front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx +++ b/front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerSection.tsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled'; const StyledSection = styled.div` display: flex; flex-direction: column; - gap: ${({ theme }) => theme.spacing(1)}; + gap: ${({ theme }) => theme.betweenSiblingsGap}; `; export { StyledSection as NavigationDrawerSection }; diff --git a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx index 4c0d8ca50..d8f150f8c 100644 --- a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx +++ b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx @@ -3,8 +3,8 @@ import { useNavigate, useParams } from 'react-router-dom'; import { useCreateOneRelationMetadataItem } from '@/object-metadata/hooks/useCreateOneRelationMetadataItem'; import { useFieldMetadataItem } from '@/object-metadata/hooks/useFieldMetadataItem'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; -import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; @@ -74,8 +74,8 @@ export const SettingsObjectNewFieldStep2 = () => { const [objectViews, setObjectViews] = useState([]); const [relationObjectViews, setRelationObjectViews] = useState([]); - const { createOneRecord: createOneViewField } = useCreateOneRecord({ - objectNameSingular: 'viewField', + const { modifyRecordFromCache: modifyViewFromCache } = useObjectMetadataItem({ + objectNameSingular: 'view', }); useFindManyRecords({ @@ -141,7 +141,7 @@ export const SettingsObjectNewFieldStep2 = () => { ); objectViews.forEach(async (view) => { - await createOneViewField?.({ + const viewFieldToCreate = { viewId: view.id, fieldMetadataId: validatedFormValues.relation.type === 'MANY_TO_ONE' @@ -150,10 +150,25 @@ export const SettingsObjectNewFieldStep2 = () => { position: activeObjectMetadataItem.fields.length, isVisible: true, size: 100, + }; + + modifyViewFromCache(view.id, { + // Todo fix typing + viewFields: (viewFields: any) => { + return { + edges: viewFields.edges.concat({ node: viewFieldToCreate }), + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + }; + }, }); }); relationObjectViews.forEach(async (view) => { - await createOneViewField?.({ + const viewFieldToCreate = { viewId: view.id, fieldMetadataId: validatedFormValues.relation.type === 'MANY_TO_ONE' @@ -162,10 +177,24 @@ export const SettingsObjectNewFieldStep2 = () => { position: relationObjectMetadataItem?.fields.length, isVisible: true, size: 100, + }; + modifyViewFromCache(view.id, { + // Todo fix typing + viewFields: (viewFields: any) => { + return { + edges: viewFields.edges.concat({ node: viewFieldToCreate }), + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + }; + }, }); }); } else { - await createMetadataField({ + const createdMetadataField = await createMetadataField({ description: validatedFormValues.description, icon: validatedFormValues.icon, label: validatedFormValues.label ?? '', @@ -176,6 +205,31 @@ export const SettingsObjectNewFieldStep2 = () => { ? validatedFormValues.select : undefined, }); + + objectViews.forEach(async (view) => { + const viewFieldToCreate = { + viewId: view.id, + fieldMetadataId: createdMetadataField.data?.createOneField.id, + position: activeObjectMetadataItem.fields.length, + isVisible: true, + size: 100, + }; + + modifyViewFromCache(view.id, { + // Todo fix typing + viewFields: (viewFields: any) => { + return { + edges: viewFields.edges.concat({ node: viewFieldToCreate }), + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + startCursor: '', + endCursor: '', + }, + }; + }, + }); + }); } navigate(`/settings/objects/${objectSlug}`); diff --git a/server/package.json b/server/package.json index e72468d5b..3550d8673 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "server", - "version": "0.2.0", + "version": "0.2.1", "description": "", "author": "", "private": true, @@ -148,4 +148,4 @@ "resolutions": { "graphql": "16.8.0" } -} +} \ No newline at end of file diff --git a/server/src/metadata/object-metadata/object-metadata.service.ts b/server/src/metadata/object-metadata/object-metadata.service.ts index 8e0c9bdf6..b0d57b7e2 100644 --- a/server/src/metadata/object-metadata/object-metadata.service.ts +++ b/server/src/metadata/object-metadata/object-metadata.service.ts @@ -131,6 +131,7 @@ export class ObjectMetadataService extends TypeOrmQueryService