Activity cache injection (#3791)

* WIP

* Minor fixes

* Added TODO

* Fix post merge

* Fix

* Fixed warnings

* Fixed comments

* Fixed comments

* Fixed naming

* Removed comment

* WIP

* WIP 2

* Finished working version

* Fixes

* Fixed typing

* Fixes

* Fixes

* Fixes

* Naming fixes

* WIP

* Fix import

* WIP

* Working version on title

* Fixed create record id overwrite

* Removed unecessary callback

* Masterpiece

* Fixed delete on click outside drawer or delete

* Cleaned

* Cleaned

* Cleaned

* Minor fixes

* Fixes

* Fixed naming

* WIP

* Fix

* Fixed create from target inline cell

* Removed console.log

* Fixed delete activity optimistic effect

* Fixed no title

* Fixed debounce and title body creation

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-02-09 14:51:30 +01:00
committed by GitHub
parent 9ceff84bbf
commit cca72da708
87 changed files with 2195 additions and 1058 deletions

View File

@ -1,10 +1,18 @@
import styled from '@emotion/styled';
import { isNonEmptyArray } from '@sniptt/guards';
import { useRecoilState } from 'recoil';
import { v4 } from 'uuid';
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
import { useInjectIntoActivityTargetInlineCellCache } from '@/activities/inline-cell/hooks/useInjectIntoActivityTargetInlineCellCache';
import { isCreatingActivityState } from '@/activities/states/isCreatingActivityState';
import { Activity } from '@/activities/types/Activity';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { ActivityTargetObjectRecord } from '@/activities/types/ActivityTargetObject';
import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getTargetObjectFilterFieldName';
import { useObjectMetadataItemOnly } from '@/object-metadata/hooks/useObjectMetadataItemOnly';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useGenerateObjectRecordOptimisticResponse } from '@/object-record/cache/hooks/useGenerateObjectRecordOptimisticResponse';
import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell';
@ -18,14 +26,16 @@ const StyledSelectContainer = styled.div`
`;
type ActivityTargetInlineCellEditModeProps = {
activityId: string;
activity: Activity;
activityTargetObjectRecords: ActivityTargetObjectRecord[];
};
export const ActivityTargetInlineCellEditMode = ({
activityId,
activity,
activityTargetObjectRecords,
}: ActivityTargetInlineCellEditModeProps) => {
const [isCreatingActivity] = useRecoilState(isCreatingActivityState);
const selectedObjectRecordIds = activityTargetObjectRecords.map(
(activityTarget) => ({
objectNameSingular: activityTarget.targetObjectNameSingular,
@ -46,6 +56,21 @@ export const ActivityTargetInlineCellEditMode = ({
const { closeInlineCell: closeEditableField } = useInlineCell();
const { upsertActivity } = useUpsertActivity();
const { objectMetadataItem: objectMetadataItemActivityTarget } =
useObjectMetadataItemOnly({
objectNameSingular: CoreObjectNameSingular.ActivityTarget,
});
const { injectIntoActivityTargetInlineCellCache } =
useInjectIntoActivityTargetInlineCellCache();
const { generateObjectRecordOptimisticResponse } =
useGenerateObjectRecordOptimisticResponse({
objectMetadataItem: objectMetadataItemActivityTarget,
});
const handleSubmit = async (selectedRecords: ObjectRecordForSelect[]) => {
closeEditableField();
@ -67,25 +92,74 @@ export const ActivityTargetInlineCellEditMode = ({
),
);
if (activityTargetRecordsToCreate.length > 0) {
await createManyActivityTargets(
activityTargetRecordsToCreate.map((selectedRecord) => ({
id: v4(),
activityId,
[getActivityTargetObjectFieldIdName({
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
})]: selectedRecord.recordIdentifier.id,
})),
);
}
if (isCreatingActivity) {
let activityTargetsForCreation = activity.activityTargets;
if (activityTargetRecordsToDelete.length > 0) {
await deleteManyActivityTargets(
activityTargetRecordsToDelete.map(
(activityTargetObjectRecord) =>
activityTargetObjectRecord.activityTargetRecord.id,
),
);
if (isNonEmptyArray(activityTargetsForCreation)) {
const generatedActivityTargets = activityTargetRecordsToCreate.map(
(selectedRecord) => {
const emptyActivityTarget =
generateObjectRecordOptimisticResponse<ActivityTarget>({
id: v4(),
activityId: activity.id,
activity,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
[getActivityTargetObjectFieldIdName({
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
})]: selectedRecord.recordIdentifier.id,
});
return emptyActivityTarget;
},
);
activityTargetsForCreation.push(...generatedActivityTargets);
}
if (isNonEmptyArray(activityTargetRecordsToDelete)) {
activityTargetsForCreation = activityTargetsForCreation.filter(
(activityTarget) =>
!activityTargetRecordsToDelete.some(
(activityTargetObjectRecord) =>
activityTargetObjectRecord.targetObjectRecord.id ===
activityTarget.id,
),
);
}
injectIntoActivityTargetInlineCellCache({
activityId: activity.id,
activityTargetsToInject: activityTargetsForCreation,
});
upsertActivity({
activity,
input: {
activityTargets: activityTargetsForCreation,
},
});
} else {
if (activityTargetRecordsToCreate.length > 0) {
await createManyActivityTargets(
activityTargetRecordsToCreate.map((selectedRecord) => ({
id: v4(),
activityId: activity.id,
[getActivityTargetObjectFieldIdName({
nameSingular: selectedRecord.objectMetadataItem.nameSingular,
})]: selectedRecord.recordIdentifier.id,
})),
);
}
if (activityTargetRecordsToDelete.length > 0) {
await deleteManyActivityTargets(
activityTargetRecordsToDelete.map(
(activityTargetObjectRecord) =>
activityTargetObjectRecord.activityTargetRecord.id,
),
);
}
}
};

View File

@ -1,8 +1,7 @@
import { ActivityTargetChips } from '@/activities/components/ActivityTargetChips';
import { useActivityTargetObjectRecords } from '@/activities/hooks/useActivityTargetObjectRecords';
import { ActivityTargetInlineCellEditMode } from '@/activities/inline-cell/components/ActivityTargetInlineCellEditMode';
import { ActivityTarget } from '@/activities/types/ActivityTarget';
import { GraphQLActivity } from '@/activities/types/GraphQLActivity';
import { Activity } from '@/activities/types/Activity';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
@ -11,13 +10,7 @@ import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types
import { IconArrowUpRight, IconPencil } from '@/ui/display/icon';
type ActivityTargetsInlineCellProps = {
activity?: Pick<GraphQLActivity, 'id'> & {
activityTargets?: {
edges: Array<{
node: Pick<ActivityTarget, 'id'>;
}> | null;
};
};
activity: Activity;
};
export const ActivityTargetsInlineCell = ({
@ -47,8 +40,8 @@ export const ActivityTargetsInlineCell = ({
IconLabel={IconArrowUpRight}
editModeContent={
<ActivityTargetInlineCellEditMode
activityId={activity?.id ?? ''}
activityTargetObjectRecords={activityTargetObjectRecords as any}
activity={activity}
activityTargetObjectRecords={activityTargetObjectRecords}
/>
}
label="Relations"