From baf12604431c62ec74234226b4f4ac2fc8035f4c Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 17 Nov 2023 16:24:58 +0100 Subject: [PATCH] Fix Activities and Tasks modules (#2561) * Fix activities * Fix Timeline * Refactor useCreateOne and useUpdateOne records * Fix seeds --- .../components/ActivityComments.tsx | 2 +- .../activities/components/ActivityEditor.tsx | 4 +- .../useHandleCheckableActivityTargetChange.ts | 4 +- .../hooks/useOpenCreateActivityDrawer.ts | 71 ++++++++++--------- .../tasks/hooks/useCurrentUserDueTaskCount.ts | 31 +------- .../timeline/components/Timeline.tsx | 29 ++++---- .../activities/utils/getRelationData.ts | 32 --------- .../utils/getTargetableEntitiesWithParents.ts | 16 +++++ .../hooks/useOptimisticEffect.ts | 13 +++- .../components/RecordTableContainer.tsx | 1 - .../components/RecordTablePage.tsx | 9 +-- .../hooks/useCreateOneObjectRecord.ts | 64 +++++++---------- .../hooks/useUpdateOneObjectRecord.ts | 46 ++++++------ .../modules/ui/theme/hooks/useColorScheme.ts | 2 +- front/src/pages/auth/CreateProfile.tsx | 9 +-- .../settings/data-model/SettingsNewObject.tsx | 2 +- .../SettingsObjectNewFieldStep2.tsx | 2 +- .../SettingsDevelopersApiKeyDetail.tsx | 30 +++++--- .../api-keys/SettingsDevelopersApiKeysNew.tsx | 28 ++++---- .../field-metadata/activity-target.ts | 2 +- .../metadata/field-metadata/activity.ts | 2 +- .../standard-objects/activity-target.ts | 45 ++++++++++-- .../standard-objects/activity.ts | 37 ++++++++-- 23 files changed, 259 insertions(+), 222 deletions(-) delete mode 100644 front/src/modules/activities/utils/getRelationData.ts create mode 100644 front/src/modules/activities/utils/getTargetableEntitiesWithParents.ts diff --git a/front/src/modules/activities/components/ActivityComments.tsx b/front/src/modules/activities/components/ActivityComments.tsx index 7927115e1..4728575ca 100644 --- a/front/src/modules/activities/components/ActivityComments.tsx +++ b/front/src/modules/activities/components/ActivityComments.tsx @@ -63,7 +63,7 @@ export const ActivityComments = ({ }: ActivityCommentsProps) => { const currentUser = useRecoilValue(currentUserState); const { createOneObject } = useCreateOneObjectRecord({ - objectNamePlural: 'commentsV2', + objectNameSingular: 'commentV2', }); if (!currentUser) { diff --git a/front/src/modules/activities/components/ActivityEditor.tsx b/front/src/modules/activities/components/ActivityEditor.tsx index f337dd57f..b22cc3045 100644 --- a/front/src/modules/activities/components/ActivityEditor.tsx +++ b/front/src/modules/activities/components/ActivityEditor.tsx @@ -86,8 +86,8 @@ export const ActivityEditor = ({ activity.completedAt ?? '', ); const containerRef = useRef(null); - const { updateOneObject } = useUpdateOneObjectRecord({ - objectNamePlural: 'activitiesV2', + const { updateOneObject } = useUpdateOneObjectRecord({ + objectNameSingular: 'activityV2', }); const updateTitle = useCallback( diff --git a/front/src/modules/activities/hooks/useHandleCheckableActivityTargetChange.ts b/front/src/modules/activities/hooks/useHandleCheckableActivityTargetChange.ts index 183a6dc99..0929c74f3 100644 --- a/front/src/modules/activities/hooks/useHandleCheckableActivityTargetChange.ts +++ b/front/src/modules/activities/hooks/useHandleCheckableActivityTargetChange.ts @@ -16,8 +16,8 @@ export const useHandleCheckableActivityTargetChange = ({ > | null; }; }) => { - const { createOneObject } = useCreateOneObjectRecord({ - objectNamePlural: 'activityTargetV2', + const { createOneObject } = useCreateOneObjectRecord({ + objectNameSingular: 'activityTargetV2', }); const { deleteOneObject } = useDeleteOneObjectRecord({ objectNamePlural: 'activityTargetV2', diff --git a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts index b6349a82f..8807681ad 100644 --- a/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts +++ b/front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts @@ -1,8 +1,7 @@ import { useRecoilState, useRecoilValue } from 'recoil'; -import { v4 } from 'uuid'; import { Activity, ActivityType } from '@/activities/types/Activity'; -import { currentUserState } from '@/auth/states/currentUserState'; +import { ActivityTarget } from '@/activities/types/ActivityTarget'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; @@ -13,14 +12,18 @@ import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope import { activityTargetableEntityArrayState } from '../states/activityTargetableEntityArrayState'; import { viewableActivityIdState } from '../states/viewableActivityIdState'; import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity'; -import { getRelationData } from '../utils/getRelationData'; +import { getTargetableEntitiesWithParents } from '../utils/getTargetableEntitiesWithParents'; export const useOpenCreateActivityDrawer = () => { const { openRightDrawer } = useRightDrawer(); - const { createOneObject } = useCreateOneObjectRecord({ - objectNamePlural: 'activitiesV2', - }); - const currentUser = useRecoilValue(currentUserState); + const { createOneObject: createOneActivityTarget } = + useCreateOneObjectRecord({ + objectNameSingular: 'activityTargetV2', + }); + const { createOneObject: createOneActivity } = + useCreateOneObjectRecord({ + objectNameSingular: 'activityV2', + }); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const setHotkeyScope = useSetHotkeyScope(); @@ -29,7 +32,7 @@ export const useOpenCreateActivityDrawer = () => { ); const [, setViewableActivityId] = useRecoilState(viewableActivityIdState); - return ({ + return async ({ type, targetableEntities, assigneeId, @@ -38,33 +41,35 @@ export const useOpenCreateActivityDrawer = () => { targetableEntities?: ActivityTargetableEntity[]; assigneeId?: string; }) => { - const now = new Date().toISOString(); + const targetableEntitiesWithRelations = targetableEntities + ? getTargetableEntitiesWithParents(targetableEntities) + : []; - createOneObject?.({ - id: v4(), - createdAt: now, - updatedAt: now, - author: { connect: { id: currentUser?.id ?? '' } }, - workspaceMemberAuthor: { - connect: { id: currentWorkspaceMember?.id ?? '' }, - }, - assignee: { connect: { id: assigneeId ?? currentUser?.id ?? '' } }, - workspaceMemberAssignee: { - connect: { id: currentWorkspaceMember?.id ?? '' }, - }, + const createdActivity = await createOneActivity?.({ + authorId: currentWorkspaceMember?.id, + assigneeId: assigneeId ?? currentWorkspaceMember?.id, type: type, - activityTargets: { - createMany: { - data: targetableEntities ? getRelationData(targetableEntities) : [], - skipDuplicates: true, - }, - }, - onCompleted: (data: Activity) => { - setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false }); - setViewableActivityId(data.id); - setActivityTargetableEntityArray(targetableEntities ?? []); - openRightDrawer(RightDrawerPages.CreateActivity); - }, }); + + if (!createdActivity) { + return; + } + + await Promise.all( + targetableEntitiesWithRelations.map(async (targetableEntity) => { + await createOneActivityTarget?.({ + companyId: + targetableEntity.type === 'Company' ? targetableEntity.id : null, + personId: + targetableEntity.type === 'Person' ? targetableEntity.id : null, + activityId: createdActivity.id, + }); + }), + ); + + setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false }); + setViewableActivityId(createdActivity.id); + setActivityTargetableEntityArray(targetableEntities ?? []); + openRightDrawer(RightDrawerPages.CreateActivity); }; }; diff --git a/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts b/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts index e7ac13493..b8664be70 100644 --- a/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts +++ b/front/src/modules/activities/tasks/hooks/useCurrentUserDueTaskCount.ts @@ -1,45 +1,20 @@ import { DateTime } from 'luxon'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilValue } from 'recoil'; -import { currentUserState } from '@/auth/states/currentUserState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; -import { turnFilterIntoWhereClause } from '@/ui/object/object-filter-dropdown/utils/turnFilterIntoWhereClause'; -import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { SortOrder } from '~/generated/graphql'; -import { ActivityType } from '~/generated-metadata/graphql'; import { parseDate } from '~/utils/date-utils'; export const useCurrentUserTaskCount = () => { - const [currentUser] = useRecoilState(currentUserState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const { objects } = useFindManyObjectRecords({ objectNamePlural: 'activitiesV2', filter: { - type: { equals: ActivityType.Task }, + type: { eq: 'Task' }, completedAt: { eq: null }, - ...(currentUser - ? turnFilterIntoWhereClause({ - fieldMetadataId: 'assigneeId', - value: currentUser.id, - operand: ViewFilterOperand.Is, - displayValue: - currentWorkspaceMember?.firstName + - ' ' + - currentWorkspaceMember?.lastName, - displayAvatarUrl: currentWorkspaceMember?.avatarUrl ?? undefined, - definition: { - type: 'ENTITY', - }, - }) - : {}), + assigneeId: { eq: currentWorkspaceMember?.id }, }, - orderBy: [ - { - createdAt: SortOrder.Desc, - }, - ], }); const currentUserDueTaskCount = objects.filter((task) => { diff --git a/front/src/modules/activities/timeline/components/Timeline.tsx b/front/src/modules/activities/timeline/components/Timeline.tsx index 65c823c81..2158fc53a 100644 --- a/front/src/modules/activities/timeline/components/Timeline.tsx +++ b/front/src/modules/activities/timeline/components/Timeline.tsx @@ -4,11 +4,9 @@ import styled from '@emotion/styled'; import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { Activity } from '@/activities/types/Activity'; -import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer'; import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import { SortOrder } from '~/generated/graphql'; import { TimelineItemsContainer } from './TimelineItemsContainer'; @@ -50,22 +48,29 @@ const StyledEmptyTimelineSubTitle = styled.div` `; export const Timeline = ({ entity }: { entity: ActivityTargetableEntity }) => { - const { objects, loading } = useFindManyObjectRecords({ + const { objects: activityTargets, loading } = useFindManyObjectRecords({ + objectNamePlural: 'activityTargetsV2', + filter: { + or: { + companyId: { eq: entity.id }, + personId: { eq: entity.id }, + }, + }, + }); + + const { objects: activities } = useFindManyObjectRecords({ + skip: !activityTargets?.length, objectNamePlural: 'activitiesV2', filter: { - companyId: { eq: entity.id }, + activityTargets: { in: activityTargets?.map((at) => at.id) }, + }, + orderBy: { + createdAt: 'AscNullsFirst', }, - orderBy: [ - { - createdAt: SortOrder.Desc, - }, - ], }); const openCreateActivity = useOpenCreateActivityDrawer(); - const activities: ActivityForDrawer[] = (objects ?? []) as Activity[]; - if (loading) { return <>; } @@ -95,7 +100,7 @@ export const Timeline = ({ entity }: { entity: ActivityTargetableEntity }) => { return ( - + ); }; diff --git a/front/src/modules/activities/utils/getRelationData.ts b/front/src/modules/activities/utils/getRelationData.ts deleted file mode 100644 index 97618e593..000000000 --- a/front/src/modules/activities/utils/getRelationData.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { v4 } from 'uuid'; - -import { ActivityTargetCreateManyActivityInput } from '~/generated/graphql'; - -import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity'; - -export const getRelationData = ( - entities: ActivityTargetableEntity[], -): ActivityTargetCreateManyActivityInput[] => { - const now = new Date().toISOString(); - - const relationData: ActivityTargetCreateManyActivityInput[] = []; - for (const entity of entities ?? []) { - relationData.push({ - companyId: entity.type === 'Company' ? entity.id : null, - personId: entity.type === 'Person' ? entity.id : null, - id: v4(), - createdAt: now, - }); - if (entity.relatedEntities) { - for (const relatedEntity of entity.relatedEntities ?? []) { - relationData.push({ - companyId: relatedEntity.type === 'Company' ? relatedEntity.id : null, - personId: relatedEntity.type === 'Person' ? relatedEntity.id : null, - id: v4(), - createdAt: now, - }); - } - } - } - return relationData; -}; diff --git a/front/src/modules/activities/utils/getTargetableEntitiesWithParents.ts b/front/src/modules/activities/utils/getTargetableEntitiesWithParents.ts new file mode 100644 index 000000000..f97d51d4a --- /dev/null +++ b/front/src/modules/activities/utils/getTargetableEntitiesWithParents.ts @@ -0,0 +1,16 @@ +import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity'; + +export const getTargetableEntitiesWithParents = ( + entities: ActivityTargetableEntity[], +): ActivityTargetableEntity[] => { + const entitiesWithRelations: ActivityTargetableEntity[] = []; + for (const entity of entities ?? []) { + entitiesWithRelations.push(entity); + if (entity.relatedEntities) { + for (const relatedEntity of entity.relatedEntities ?? []) { + entitiesWithRelations.push(relatedEntity); + } + } + } + return entitiesWithRelations; +}; diff --git a/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts b/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts index 19aff8cc5..8245609af 100644 --- a/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts +++ b/front/src/modules/apollo/optimistic-effect/hooks/useOptimisticEffect.ts @@ -8,7 +8,10 @@ import { isNonEmptyArray } from '@sniptt/guards'; import { useRecoilCallback } from 'recoil'; import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies'; -import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; +import { + EMPTY_QUERY, + useFindOneObjectMetadataItem, +} from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { GET_PEOPLE } from '@/people/graphql/queries/getPeople'; import { GET_API_KEYS } from '@/settings/developers/graphql/queries/getApiKeys'; @@ -22,7 +25,7 @@ import { optimisticEffectState } from '../states/optimisticEffectState'; import { OptimisticEffect } from '../types/internal/OptimisticEffect'; import { OptimisticEffectDefinition } from '../types/OptimisticEffectDefinition'; -export const useOptimisticEffect = (objectNameSingular: string) => { +export const useOptimisticEffect = (objectNameSingular?: string) => { const apolloClient = useApolloClient(); const { findManyQuery } = useFindOneObjectMetadataItem({ objectNameSingular, @@ -37,6 +40,12 @@ export const useOptimisticEffect = (objectNameSingular: string) => { variables: OperationVariables; definition: OptimisticEffectDefinition; }) => { + if (findManyQuery === EMPTY_QUERY) { + throw new Error( + `Trying to register an optimistic effect for unknown object ${objectNameSingular}`, + ); + } + const optimisticEffects = snapshot .getLoadable(optimisticEffectState) .getValue(); diff --git a/front/src/modules/object-record/components/RecordTableContainer.tsx b/front/src/modules/object-record/components/RecordTableContainer.tsx index 374723050..3fcc36d42 100644 --- a/front/src/modules/object-record/components/RecordTableContainer.tsx +++ b/front/src/modules/object-record/components/RecordTableContainer.tsx @@ -38,7 +38,6 @@ export const RecordTableContainer = ({ }); const { updateOneObject } = useUpdateOneObjectRecord({ - objectNamePlural, objectNameSingular: foundObjectMetadataItem?.nameSingular, }); diff --git a/front/src/modules/object-record/components/RecordTablePage.tsx b/front/src/modules/object-record/components/RecordTablePage.tsx index cb2122fc3..56f570b01 100644 --- a/front/src/modules/object-record/components/RecordTablePage.tsx +++ b/front/src/modules/object-record/components/RecordTablePage.tsx @@ -30,9 +30,10 @@ export type RecordTablePageProps = Pick< export const RecordTablePage = () => { const objectNamePlural = useParams().objectNamePlural ?? ''; - const { objectNotFoundInMetadata, loading } = useFindOneObjectMetadataItem({ - objectNamePlural, - }); + const { objectNotFoundInMetadata, loading, foundObjectMetadataItem } = + useFindOneObjectMetadataItem({ + objectNamePlural, + }); const navigate = useNavigate(); @@ -43,7 +44,7 @@ export const RecordTablePage = () => { }, [objectNotFoundInMetadata, loading, navigate]); const { createOneObject } = useCreateOneObjectRecord({ - objectNamePlural, + objectNameSingular: foundObjectMetadataItem?.nameSingular, }); const handleAddButtonClick = async () => { diff --git a/front/src/modules/object-record/hooks/useCreateOneObjectRecord.ts b/front/src/modules/object-record/hooks/useCreateOneObjectRecord.ts index d52fac0ca..865b3d4d8 100644 --- a/front/src/modules/object-record/hooks/useCreateOneObjectRecord.ts +++ b/front/src/modules/object-record/hooks/useCreateOneObjectRecord.ts @@ -4,60 +4,44 @@ import { v4 } from 'uuid'; import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; -import { CurrencyCode, FieldMetadataType } from '~/generated-metadata/graphql'; import { capitalize } from '~/utils/string/capitalize'; -const defaultFieldValues: Record = { - [FieldMetadataType.Currency]: { - amountMicros: null, - currencyCode: CurrencyCode.Usd, - }, - [FieldMetadataType.Boolean]: false, - [FieldMetadataType.Date]: null, - [FieldMetadataType.Email]: '', - [FieldMetadataType.Enum]: null, - [FieldMetadataType.Number]: null, - [FieldMetadataType.Probability]: null, - [FieldMetadataType.Relation]: null, - [FieldMetadataType.Phone]: '', - [FieldMetadataType.Text]: '', - [FieldMetadataType.Link]: { url: '', label: '' }, - [FieldMetadataType.Uuid]: '', -}; - -export const useCreateOneObjectRecord = ({ - objectNamePlural, -}: Pick) => { - const { triggerOptimisticEffects } = useOptimisticEffect('CompanyV2'); +export const useCreateOneObjectRecord = ({ + objectNameSingular, +}: Pick) => { + const { triggerOptimisticEffects } = useOptimisticEffect(objectNameSingular); const { foundObjectMetadataItem, objectNotFoundInMetadata, createOneMutation, } = useFindOneObjectMetadataItem({ - objectNamePlural, + objectNameSingular, }); // TODO: type this with a minimal type at least with Record const [mutate] = useMutation(createOneMutation); - const createOneObject = foundObjectMetadataItem - ? async (input: Record) => { - const createdObject = await mutate({ - variables: { - input: { ...input, id: v4() }, - }, - }); + const createOneObject = + objectNameSingular && foundObjectMetadataItem + ? async (input: Record) => { + const createdObject = await mutate({ + variables: { + input: { ...input, id: v4() }, + }, + }); - triggerOptimisticEffects( - `${capitalize(foundObjectMetadataItem.nameSingular)}Edge`, - createdObject.data[ - `create${capitalize(foundObjectMetadataItem.nameSingular)}` - ], - ); - return createdObject.data; - } - : undefined; + triggerOptimisticEffects( + `${capitalize(foundObjectMetadataItem.nameSingular)}Edge`, + createdObject.data[ + `create${capitalize(foundObjectMetadataItem.nameSingular)}` + ], + ); + return createdObject.data[ + `create${capitalize(objectNameSingular)}` + ] as T; + } + : undefined; return { createOneObject, diff --git a/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts b/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts index 6ff8b8fbb..9161bda39 100644 --- a/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts +++ b/front/src/modules/object-record/hooks/useUpdateOneObjectRecord.ts @@ -2,41 +2,45 @@ import { useMutation } from '@apollo/client'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; +import { capitalize } from '~/utils/string/capitalize'; -export const useUpdateOneObjectRecord = ({ - objectNamePlural, +export const useUpdateOneObjectRecord = ({ objectNameSingular, -}: ObjectMetadataItemIdentifier) => { +}: Pick) => { const { foundObjectMetadataItem, objectNotFoundInMetadata, updateOneMutation, } = useFindOneObjectMetadataItem({ - objectNamePlural, objectNameSingular, }); // TODO: type this with a minimal type at least with Record const [mutate] = useMutation(updateOneMutation); - const updateOneObject = foundObjectMetadataItem - ? ({ - idToUpdate, - input, - }: { - idToUpdate: string; - input: Record; - }) => { - return mutate({ - variables: { - idToUpdate: idToUpdate, - input: { - ...input, + const updateOneObject = + objectNameSingular && foundObjectMetadataItem + ? async ({ + idToUpdate, + input, + }: { + idToUpdate: string; + input: Record; + }) => { + const updatedObject = await mutate({ + variables: { + idToUpdate: idToUpdate, + input: { + ...input, + }, }, - }, - }); - } - : undefined; + }); + + return updatedObject.data[ + `update${capitalize(objectNameSingular)}` + ] as T; + } + : undefined; return { updateOneObject, diff --git a/front/src/modules/ui/theme/hooks/useColorScheme.ts b/front/src/modules/ui/theme/hooks/useColorScheme.ts index 6c76f4938..3760ff987 100644 --- a/front/src/modules/ui/theme/hooks/useColorScheme.ts +++ b/front/src/modules/ui/theme/hooks/useColorScheme.ts @@ -10,7 +10,7 @@ export const useColorScheme = () => { const { updateOneObject: updateOneWorkspaceMember } = useUpdateOneObjectRecord({ - objectNamePlural: 'workspaceMembersV2', + objectNameSingular: 'workspaceMemberV2', }); const colorScheme = currentWorkspaceMember?.colorScheme ?? 'System'; diff --git a/front/src/pages/auth/CreateProfile.tsx b/front/src/pages/auth/CreateProfile.tsx index 360593837..84f0b0bdc 100644 --- a/front/src/pages/auth/CreateProfile.tsx +++ b/front/src/pages/auth/CreateProfile.tsx @@ -18,6 +18,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar'; import { MainButton } from '@/ui/input/button/components/MainButton'; import { TextInput } from '@/ui/input/components/TextInput'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; const StyledContentContainer = styled.div` width: 100%; @@ -59,7 +60,7 @@ export const CreateProfile = () => { ); const { updateOneObject, objectNotFoundInMetadata } = - useUpdateOneObjectRecord({ + useUpdateOneObjectRecord({ objectNameSingular: 'workspaceMemberV2', }); @@ -91,7 +92,7 @@ export const CreateProfile = () => { throw new Error('Object not found in metadata'); } - const result = await updateOneObject({ + await updateOneObject({ idToUpdate: currentWorkspaceMember?.id, input: { firstName: data.firstName, @@ -99,10 +100,6 @@ export const CreateProfile = () => { }, }); - if (result.errors || !result.data?.updateWorkspaceMemberV2) { - throw result.errors ?? new Error('Unknown error'); - } - setCurrentWorkspaceMember( (current) => ({ diff --git a/front/src/pages/settings/data-model/SettingsNewObject.tsx b/front/src/pages/settings/data-model/SettingsNewObject.tsx index 7d7b805c0..505a7e202 100644 --- a/front/src/pages/settings/data-model/SettingsNewObject.tsx +++ b/front/src/pages/settings/data-model/SettingsNewObject.tsx @@ -33,7 +33,7 @@ export const SettingsNewObject = () => { } = useObjectMetadataItemForSettings(); const { createOneObject: createOneView } = useCreateOneObjectRecord({ - objectNamePlural: 'viewsV2', + objectNameSingular: 'viewV2', }); const [ diff --git a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx index 5b19ed931..8281a72e3 100644 --- a/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx +++ b/front/src/pages/settings/data-model/SettingsObjectNewField/SettingsObjectNewFieldStep2.tsx @@ -45,7 +45,7 @@ export const SettingsObjectNewFieldStep2 = () => { const [objectViews, setObjectViews] = useState([]); const { createOneObject: createOneViewField } = useCreateOneObjectRecord({ - objectNamePlural: 'viewFieldsV2', + objectNameSingular: 'viewFieldV2', }); useFindManyObjectRecords({ diff --git a/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx b/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx index dbbc704ab..537267ecc 100644 --- a/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx +++ b/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeyDetail.tsx @@ -21,7 +21,7 @@ import { TextInput } from '@/ui/input/components/TextInput'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; -import { useGenerateOneApiKeyTokenMutation } from '~/generated/graphql'; +import { ApiKey, useGenerateOneApiKeyTokenMutation } from '~/generated/graphql'; const StyledInfo = styled.span` color: ${({ theme }) => theme.font.color.light}; @@ -47,11 +47,13 @@ export const SettingsDevelopersApiKeyDetail = () => { ); const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation(); - const { createOneObject: createOneApiKey } = useCreateOneObjectRecord({ - objectNamePlural: 'apiKeysV2', - }); - const { updateOneObject: updateApiKey } = useUpdateOneObjectRecord({ - objectNamePlural: 'apiKeysV2', + const { createOneObject: createOneApiKey } = useCreateOneObjectRecord( + { + objectNameSingular: 'apiKeyV2', + }, + ); + const { updateOneObject: updateApiKey } = useUpdateOneObjectRecord({ + objectNameSingular: 'apiKeyV2', }); const { object: apiKeyData } = useFindOneObjectRecord({ @@ -77,17 +79,22 @@ export const SettingsDevelopersApiKeyDetail = () => { name: name, expiresAt: newExpiresAt, }); + + if (!newApiKey) { + return; + } + const tokenData = await generateOneApiKeyToken({ variables: { data: { - id: newApiKey.createApiKeyV2.id, - expiresAt: newApiKey.createApiKeyV2.expiresAt, - name: newApiKey.createApiKeyV2.name, // TODO update typing to remove useless name param here + id: newApiKey.id, + expiresAt: newApiKey.expiresAt, + name: newApiKey.name, // TODO update typing to remove useless name param here }, }, }); return { - id: newApiKey.createApiKeyV2.id, + id: newApiKey.id, token: tokenData.data?.generateApiKeyV2Token.token, }; }; @@ -100,7 +107,8 @@ export const SettingsDevelopersApiKeyDetail = () => { ); const apiKey = await createIntegration(apiKeyData.name, newExpiresAt); await deleteIntegration(false); - if (apiKey.token) { + + if (apiKey && apiKey.token) { setGeneratedApi(apiKey.id, apiKey.token); navigate(`/settings/developers/api-keys/${apiKey.id}`); } diff --git a/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx index 5ab2b30b2..b6e2031e2 100644 --- a/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx +++ b/front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx @@ -15,7 +15,7 @@ import { TextInput } from '@/ui/input/components/TextInput'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { Section } from '@/ui/layout/section/components/Section'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; -import { useGenerateOneApiKeyTokenMutation } from '~/generated/graphql'; +import { ApiKey, useGenerateOneApiKeyTokenMutation } from '~/generated/graphql'; export const SettingsDevelopersApiKeysNew = () => { const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation(); @@ -29,9 +29,11 @@ export const SettingsDevelopersApiKeysNew = () => { name: '', }); - const { createOneObject: createOneApiKey } = useCreateOneObjectRecord({ - objectNamePlural: 'apiKeysV2', - }); + const { createOneObject: createOneApiKey } = useCreateOneObjectRecord( + { + objectNameSingular: 'apiKeyV2', + }, + ); const onSave = async () => { const expiresAt = formValues.expirationDate ? DateTime.now().plus({ days: formValues.expirationDate }).toString() @@ -40,21 +42,23 @@ export const SettingsDevelopersApiKeysNew = () => { name: formValues.name, expiresAt, }); + + if (!newApiKey) { + return; + } + const tokenData = await generateOneApiKeyToken({ variables: { data: { - id: newApiKey.createApiKeyV2.id, - expiresAt: newApiKey.createApiKeyV2.expiresAt, - name: newApiKey.createApiKeyV2.name, // TODO update typing to remove useless name param here + id: newApiKey.id, + expiresAt: newApiKey.expiresAt, + name: newApiKey.name, // TODO update typing to remove useless name param here }, }, }); if (tokenData.data?.generateApiKeyV2Token) { - setGeneratedApi( - newApiKey.createApiKeyV2.id, - tokenData.data.generateApiKeyV2Token.token, - ); - navigate(`/settings/developers/api-keys/${newApiKey.createApiKeyV2.id}`); + setGeneratedApi(newApiKey.id, tokenData.data.generateApiKeyV2Token.token); + navigate(`/settings/developers/api-keys/${newApiKey.id}`); } }; const canSave = !!formValues.name && createOneApiKey; diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/activity-target.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/activity-target.ts index 00d956d76..03432e098 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/activity-target.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/activity-target.ts @@ -112,7 +112,7 @@ export const seedActivityTargetFieldMetadata = async ( targetColumnMap: {}, description: 'ActivityTarget activity', icon: 'IconNotes', - isNullable: false, + isNullable: true, isSystem: false, defaultValue: undefined, }, diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/activity.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/activity.ts index 5084f9153..8c2335f15 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/activity.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/activity.ts @@ -283,7 +283,7 @@ export const seedActivityFieldMetadata = async ( description: 'Activity author. This is the person who created the activity', icon: 'IconUserCircle', - isNullable: false, + isNullable: true, isSystem: false, defaultValue: undefined, }, diff --git a/server/src/workspace/workspace-manager/standard-objects/activity-target.ts b/server/src/workspace/workspace-manager/standard-objects/activity-target.ts index 98b0efc02..16af730e9 100644 --- a/server/src/workspace/workspace-manager/standard-objects/activity-target.ts +++ b/server/src/workspace/workspace-manager/standard-objects/activity-target.ts @@ -18,12 +18,10 @@ const activityTargetMetadata = { type: FieldMetadataType.RELATION, name: 'activity', label: 'Activity', - targetColumnMap: { - value: 'activityId', - }, + targetColumnMap: {}, description: 'ActivityTarget activity', icon: 'IconCheckbox', - isNullable: false, + isNullable: true, }, { isCustom: false, @@ -51,6 +49,45 @@ const activityTargetMetadata = { icon: 'IconBuildingSkyscraper', isNullable: true, }, + { + isCustom: false, + isActive: true, + type: FieldMetadataType.UUID, + name: 'activityId', + label: 'Activity id (foreign key)', + targetColumnMap: {}, + description: 'ActivityTarget activity id foreign key', + icon: undefined, + isNullable: false, + isSystem: true, + defaultValue: undefined, + }, + { + isCustom: false, + isActive: true, + type: FieldMetadataType.UUID, + name: 'personId', + label: 'Person id (foreign key)', + targetColumnMap: {}, + description: 'ActivityTarget person id foreign key', + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + }, + { + isCustom: false, + isActive: true, + type: FieldMetadataType.UUID, + name: 'companyId', + label: 'Company id (foreign key)', + targetColumnMap: {}, + description: 'ActivityTarget company id foreign key', + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + }, ], }; diff --git a/server/src/workspace/workspace-manager/standard-objects/activity.ts b/server/src/workspace/workspace-manager/standard-objects/activity.ts index 43f7b3b35..ff508859a 100644 --- a/server/src/workspace/workspace-manager/standard-objects/activity.ts +++ b/server/src/workspace/workspace-manager/standard-objects/activity.ts @@ -1,3 +1,4 @@ +import { SeedWorkspaceId } from 'src/database/seeds/metadata'; import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; const activityMetadata = { @@ -129,28 +130,52 @@ const activityMetadata = { type: FieldMetadataType.RELATION, name: 'author', label: 'Author', - targetColumnMap: { - value: 'authorId', - }, + targetColumnMap: {}, description: 'Activity author. This is the person who created the activity', icon: 'IconUserCircle', isNullable: false, }, + { + isCustom: false, + workspaceId: SeedWorkspaceId, + isActive: true, + type: FieldMetadataType.UUID, + name: 'authorId', + label: 'Author id (foreign key)', + targetColumnMap: {}, + description: 'Activity author id foreign key', + icon: undefined, + isNullable: false, + isSystem: true, + defaultValue: undefined, + }, { isCustom: false, isActive: true, type: FieldMetadataType.RELATION, name: 'assignee', label: 'Assignee', - targetColumnMap: { - value: 'assigneeId', - }, + targetColumnMap: {}, description: 'Acitivity assignee. This is the workspace member assigned to the activity ', icon: 'IconUserCircle', isNullable: true, }, + { + isCustom: false, + workspaceId: SeedWorkspaceId, + isActive: true, + type: FieldMetadataType.UUID, + name: 'assigneeId', + label: 'Assignee id (foreign key)', + targetColumnMap: {}, + description: 'Acitivity assignee id foreign key', + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + }, ], };