Fix optimistic rendering (#2882)

* Release 0.2.1

* Optimistic rendering fixes

* Fix optimistic rendering

* Fix issues on Tasks

* Fix Opportunity picker and relation picker
This commit is contained in:
Charles Bochet
2023-12-09 10:38:37 +01:00
committed by GitHub
parent e7bdb17128
commit 9d4ed323a7
26 changed files with 267 additions and 157 deletions

View File

@ -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"
}
}
}

View File

@ -25,6 +25,7 @@ export const ActivityBodyEditor = ({
const [body, setBody] = useState<string | null>(null);
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
useEffect(() => {

View File

@ -82,6 +82,7 @@ export const ActivityEditor = ({
const containerRef = useRef<HTMLDivElement>(null);
const { updateOneRecord: updateOneActivity } = useUpdateOneRecord<Activity>({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
const { FieldContextProvider: DueAtFieldContextProvider } = useFieldContext({

View File

@ -24,6 +24,7 @@ export const useOpenCreateActivityDrawer = () => {
});
const { createOneRecord: createOneActivity } = useCreateOneRecord<Activity>({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const setHotkeyScope = useSetHotkeyScope();

View File

@ -13,6 +13,7 @@ export const ActivityActionBar = ({ activityId }: ActivityActionBarProps) => {
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
const { deleteOneRecord: deleteOneActivity } = useDeleteOneRecord({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
const deleteActivity = () => {

View File

@ -8,6 +8,7 @@ type Task = Pick<Activity, 'id' | 'completedAt'>;
export const useCompleteTask = (task: Task) => {
const { updateOneRecord: updateOneActivity } = useUpdateOneRecord({
objectNameSingular: 'activity',
refetchFindManyQuery: true,
});
const completeTask = useCallback(

View File

@ -146,6 +146,7 @@ export const useOptimisticEffect = ({
}
}
},
[apolloClient.cache],
);
return {

View File

@ -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({

View File

@ -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}
>
<PageFavoriteButton
isFavorite={isFavorite}
onClick={handleFavoriteButtonClick}
/>
<ShowPageAddButton
key="add"
entity={{
id: record.id,
type:
objectMetadataItem?.nameSingular === 'company'
? 'Company'
: objectMetadataItem?.nameSingular === 'person'
? 'Person'
: 'Custom',
}}
/>
{objectMetadataType !== 'Custom' && (
<>
<PageFavoriteButton
isFavorite={isFavorite}
onClick={handleFavoriteButtonClick}
/>
<ShowPageAddButton
key="add"
entity={{
id: record.id,
type: objectMetadataType,
}}
/>
</>
)}
</PageHeader>
<PageBody>
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>

View File

@ -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 = <T>({
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<string, any>
const apolloClient = useApolloClient();
@ -30,20 +35,30 @@ export const useCreateOneRecord = <T>({
const createOneRecord = async (input: Record<string, any>) => {
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) {

View File

@ -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 = <T>({
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 = <T>({
variables: {
idToDelete,
},
refetchQueries: refetchFindManyQuery
? [getOperationName(findManyRecordsQuery) ?? '']
: [],
});
return deletedRecord.data[
@ -54,6 +62,8 @@ export const useDeleteOneRecord = <T>({
performOptimisticEvict,
apolloClient,
deleteOneRecordMutation,
refetchFindManyQuery,
findManyRecordsQuery,
],
);

View File

@ -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 = <T>(input: Partial<T> & { 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 {

View File

@ -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 = <T>({
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 = <T>({
...input,
},
},
refetchQueries: refetchFindManyQuery
? [getOperationName(findManyRecordsQuery) ?? '']
: [],
});
if (!updatedRecord?.data) {

View File

@ -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 ?? '');

View File

@ -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<typeof SingleEntitySelect> = {
width,
}) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const relationPickerSearchFilter = useRecoilScopedValue(
relationPickerSearchFilterScopedState,
);
const { relationPickerSearchFilter } = useRelationPicker();
return (
<SingleEntitySelect

View File

@ -12,17 +12,20 @@ export const useRelationPickerScopedStates = (args?: {
relationPickerScopedId,
);
const { identifiersMapperState } = getRelationPickerScopedStates({
relationPickerScopeId: scopeId,
});
const { searchQueryState } = getRelationPickerScopedStates({
const {
identifiersMapperState,
relationPickerSearchFilterState,
relationPickerPreselectedIdState,
searchQueryState,
} = getRelationPickerScopedStates({
relationPickerScopeId: scopeId,
});
return {
scopeId,
identifiersMapperState,
relationPickerSearchFilterState,
relationPickerPreselectedIdState,
searchQueryState,
};
};

View File

@ -1,12 +1,10 @@
import scrollIntoView from 'scroll-into-view';
import { Key } from 'ts-key-enum';
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { CreateButtonId } from '../constants';
import { RelationPickerRecoilScopeContext } from '../states/recoil-scope-contexts/RelationPickerRecoilScopeContext';
import { relationPickerPreselectedIdScopedState } from '../states/relationPickerPreselectedIdScopedState';
import { RelationPickerHotkeyScope } from '../types/RelationPickerHotkeyScope';
import { getPreselectedIdIndex } from '../utils/getPreselectedIdIndex';
@ -17,15 +15,12 @@ export const useEntitySelectScroll = ({
selectableOptionIds: string[];
containerRef: React.RefObject<HTMLDivElement>;
}) => {
const [relationPickerPreselectedId, setRelationPickerPreselectedId] =
useRecoilScopedState(
relationPickerPreselectedIdScopedState,
RelationPickerRecoilScopeContext,
);
const { relationPickerPreselectedId, setRelationPickerPreselectedId } =
useRelationPicker();
const preselectedIdIndex = getPreselectedIdIndex(
selectableOptionIds,
relationPickerPreselectedId,
relationPickerPreselectedId ?? '',
);
const resetScroll = () => {

View File

@ -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,

View File

@ -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,
};
};

View File

@ -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,
});

View File

@ -1,8 +1,6 @@
import { atomFamily } from 'recoil';
import { createScopedState } from '@/ui/utilities/recoil-scope/utils/createScopedState';
export const relationPickerSearchFilterScopedState = atomFamily<string, string>(
{
key: 'relationPickerSearchFilterScopedState',
default: '',
},
);
export const relationPickerSearchFilterScopedState = createScopedState<string>({
key: 'relationPickerSearchFilterScopedState',
defaultValue: '',
});

View File

@ -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,
};
};

View File

@ -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 };

View File

@ -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<View[]>([]);
const [relationObjectViews, setRelationObjectViews] = useState<View[]>([]);
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}`);

View File

@ -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"
}
}
}

View File

@ -131,6 +131,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
isNullable: true,
isActive: true,
isCustom: false,
isSystem: true,
workspaceId: record.workspaceId,
defaultValue: { type: 'now' },
},
@ -168,7 +169,9 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
type: FieldMetadataType.RELATION,
name: record.nameSingular,
label: record.labelSingular,
targetColumnMap: {},
targetColumnMap: {
value: `${createdObjectMetadata.targetTableName}Id`,
},
description: `ActivityTarget ${record.labelSingular}`,
icon: 'IconBuildingSkyscraper',
isNullable: true,