Feat/activities custom objects (#3213)

* WIP

* WIP - MultiObjectSearch

* WIP

* WIP

* Finished working version

* Fix

* Fixed and cleaned

* Fix

* Disabled files and emails for custom objects

* Cleaned console.log

* Fixed attachment

* Fixed

* fix lint

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2024-01-05 09:08:33 +01:00
committed by GitHub
parent c15e138d72
commit b112b74022
72 changed files with 1611 additions and 551 deletions

View File

@ -5,11 +5,10 @@ import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimis
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { useGenerateEmptyRecord } from '@/object-record/hooks/useGenerateEmptyRecord';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { capitalize } from '~/utils/string/capitalize';
export const useCreateManyRecords = <
T extends Record<string, unknown> & { id: string },
>({
export const useCreateManyRecords = <T extends ObjectRecord>({
objectNameSingular,
}: ObjectMetadataItemIdentifier) => {
const { triggerOptimisticEffects } = useOptimisticEffect({
@ -27,17 +26,16 @@ export const useCreateManyRecords = <
const apolloClient = useApolloClient();
const createManyRecords = async (data: Record<string, any>[]) => {
const createManyRecords = async (data: Partial<T>[]) => {
const withIds = data.map((record) => ({
...record,
id: (record.id as string) ?? v4(),
}));
withIds.forEach((record) => {
const emptyRecord: Record<string, unknown> | undefined =
generateEmptyRecord({
id: record.id,
});
const emptyRecord: T | undefined = generateEmptyRecord({
id: record.id,
} as T);
if (emptyRecord) {
triggerOptimisticEffects({

View File

@ -34,7 +34,7 @@ export const useCreateOneRecord = <T>({
const createOneRecord = async (input: Record<string, any>) => {
const recordId = v4();
const generatedEmptyRecord = generateEmptyRecord<Record<string, unknown>>({
const generatedEmptyRecord = generateEmptyRecord({
id: recordId,
createdAt: new Date().toISOString(),
...input,

View File

@ -1,4 +1,5 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { generateEmptyFieldValue } from '@/object-record/utils/generateEmptyFieldValue';
export const useGenerateEmptyRecord = ({
@ -7,11 +8,11 @@ export const useGenerateEmptyRecord = ({
objectMetadataItem: ObjectMetadataItem;
}) => {
// Todo fix typing once we generate the return base on Metadata
const generateEmptyRecord = <T>(input: Partial<T> & { id: string }) => {
const generateEmptyRecord = <T extends ObjectRecord>(input: T) => {
// Todo replace this by runtime typing
const validatedInput = input as { id: string } & { [key: string]: any };
const validatedInput = input as T;
const emptyRecord = {} as Record<string, any>;
const emptyRecord = {} as any;
for (const fieldMetadataItem of objectMetadataItem.fields) {
emptyRecord[fieldMetadataItem.name] =
@ -19,7 +20,7 @@ export const useGenerateEmptyRecord = ({
generateEmptyFieldValue(fieldMetadataItem);
}
return emptyRecord;
return emptyRecord as T;
};
return {

View File

@ -0,0 +1,84 @@
import { gql } from '@apollo/client';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
export const useGenerateFindManyRecordsForMultipleMetadataItemsQuery = ({
objectMetadataItems,
depth,
}: {
objectMetadataItems: ObjectMetadataItem[];
depth?: number;
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
const capitalizedObjectNameSingulars = objectMetadataItems.map(
({ nameSingular }) => capitalize(nameSingular),
);
const filterPerMetadataItemArray = capitalizedObjectNameSingulars
.map(
(capitalizedObjectNameSingular) =>
`$filter${capitalizedObjectNameSingular}: ${capitalizedObjectNameSingular}FilterInput`,
)
.join(', ');
const orderByPerMetadataItemArray = capitalizedObjectNameSingulars
.map(
(capitalizedObjectNameSingular) =>
`$orderBy${capitalizedObjectNameSingular}: ${capitalizedObjectNameSingular}OrderByInput`,
)
.join(', ');
const lastCursorPerMetadataItemArray = capitalizedObjectNameSingulars
.map(
(capitalizedObjectNameSingular) =>
`$lastCursor${capitalizedObjectNameSingular}: String`,
)
.join(', ');
const limitPerMetadataItemArray = capitalizedObjectNameSingulars
.map(
(capitalizedObjectNameSingular) =>
`$limit${capitalizedObjectNameSingular}: Float = 5`,
)
.join(', ');
return gql`
query FindManyRecordsMultipleMetadataItems(
${filterPerMetadataItemArray},
${orderByPerMetadataItemArray},
${lastCursorPerMetadataItemArray},
${limitPerMetadataItemArray}
) {
${objectMetadataItems
.map(
({ namePlural, nameSingular, fields }) =>
`${namePlural}(filter: $filter${capitalize(
nameSingular,
)}, orderBy: $orderBy${capitalize(
nameSingular,
)}, first: $limit${capitalize(
nameSingular,
)}, after: $lastCursor${capitalize(nameSingular)}){
edges {
node {
id
${fields
.map((field) => mapFieldMetadataToGraphQLQuery(field, depth))
.join('\n')}
}
cursor
}
pageInfo {
hasNextPage
startCursor
endCursor
}
}`,
)
.join('\n')}
}
`;
};

View File

@ -1,7 +1,6 @@
import { gql } from '@apollo/client';
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
import { EMPTY_QUERY } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { capitalize } from '~/utils/string/capitalize';
@ -14,10 +13,6 @@ export const useGenerateFindManyRecordsQuery = ({
}) => {
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
if (!objectMetadataItem) {
return EMPTY_QUERY;
}
return gql`
query FindMany${capitalize(
objectMetadataItem.namePlural,

View File

@ -67,13 +67,6 @@ export const useRecordTableContextMenuEntries = (
const { createFavorite, favorites, deleteFavorite } = useFavorites();
const objectMetadataType =
objectNameSingular === 'company'
? 'Company'
: objectNameSingular === 'person'
? 'Person'
: 'Custom';
const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => {
const selectedRowIds = injectSelectorSnapshotValueWithRecordTableScopeId(
snapshot,
@ -212,14 +205,14 @@ export const useRecordTableContextMenuEntries = (
label: 'Task',
Icon: IconCheckbox,
onClick: () => {
openCreateActivityDrawer('Task', objectMetadataType);
openCreateActivityDrawer('Task', objectNameSingular);
},
},
{
label: 'Note',
Icon: IconNotes,
onClick: () => {
openCreateActivityDrawer('Note', objectMetadataType);
openCreateActivityDrawer('Note', objectNameSingular);
},
},
...(dataExecuteQuickActionOnmentEnabled