Fix Activities and Tasks modules (#2561)

* Fix activities

* Fix Timeline

* Refactor useCreateOne and useUpdateOne records

* Fix seeds
This commit is contained in:
Charles Bochet
2023-11-17 16:24:58 +01:00
committed by GitHub
parent a6d8cdb116
commit baf1260443
23 changed files with 259 additions and 222 deletions

View File

@ -63,7 +63,7 @@ export const ActivityComments = ({
}: ActivityCommentsProps) => { }: ActivityCommentsProps) => {
const currentUser = useRecoilValue(currentUserState); const currentUser = useRecoilValue(currentUserState);
const { createOneObject } = useCreateOneObjectRecord({ const { createOneObject } = useCreateOneObjectRecord({
objectNamePlural: 'commentsV2', objectNameSingular: 'commentV2',
}); });
if (!currentUser) { if (!currentUser) {

View File

@ -86,8 +86,8 @@ export const ActivityEditor = ({
activity.completedAt ?? '', activity.completedAt ?? '',
); );
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const { updateOneObject } = useUpdateOneObjectRecord({ const { updateOneObject } = useUpdateOneObjectRecord<Activity>({
objectNamePlural: 'activitiesV2', objectNameSingular: 'activityV2',
}); });
const updateTitle = useCallback( const updateTitle = useCallback(

View File

@ -16,8 +16,8 @@ export const useHandleCheckableActivityTargetChange = ({
> | null; > | null;
}; };
}) => { }) => {
const { createOneObject } = useCreateOneObjectRecord({ const { createOneObject } = useCreateOneObjectRecord<ActivityTarget>({
objectNamePlural: 'activityTargetV2', objectNameSingular: 'activityTargetV2',
}); });
const { deleteOneObject } = useDeleteOneObjectRecord({ const { deleteOneObject } = useDeleteOneObjectRecord({
objectNamePlural: 'activityTargetV2', objectNamePlural: 'activityTargetV2',

View File

@ -1,8 +1,7 @@
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { v4 } from 'uuid';
import { Activity, ActivityType } from '@/activities/types/Activity'; 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 { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord'; import { useCreateOneObjectRecord } from '@/object-record/hooks/useCreateOneObjectRecord';
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; 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 { activityTargetableEntityArrayState } from '../states/activityTargetableEntityArrayState';
import { viewableActivityIdState } from '../states/viewableActivityIdState'; import { viewableActivityIdState } from '../states/viewableActivityIdState';
import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity'; import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity';
import { getRelationData } from '../utils/getRelationData'; import { getTargetableEntitiesWithParents } from '../utils/getTargetableEntitiesWithParents';
export const useOpenCreateActivityDrawer = () => { export const useOpenCreateActivityDrawer = () => {
const { openRightDrawer } = useRightDrawer(); const { openRightDrawer } = useRightDrawer();
const { createOneObject } = useCreateOneObjectRecord({ const { createOneObject: createOneActivityTarget } =
objectNamePlural: 'activitiesV2', useCreateOneObjectRecord<ActivityTarget>({
}); objectNameSingular: 'activityTargetV2',
const currentUser = useRecoilValue(currentUserState); });
const { createOneObject: createOneActivity } =
useCreateOneObjectRecord<Activity>({
objectNameSingular: 'activityV2',
});
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
@ -29,7 +32,7 @@ export const useOpenCreateActivityDrawer = () => {
); );
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState); const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
return ({ return async ({
type, type,
targetableEntities, targetableEntities,
assigneeId, assigneeId,
@ -38,33 +41,35 @@ export const useOpenCreateActivityDrawer = () => {
targetableEntities?: ActivityTargetableEntity[]; targetableEntities?: ActivityTargetableEntity[];
assigneeId?: string; assigneeId?: string;
}) => { }) => {
const now = new Date().toISOString(); const targetableEntitiesWithRelations = targetableEntities
? getTargetableEntitiesWithParents(targetableEntities)
: [];
createOneObject?.({ const createdActivity = await createOneActivity?.({
id: v4(), authorId: currentWorkspaceMember?.id,
createdAt: now, assigneeId: assigneeId ?? currentWorkspaceMember?.id,
updatedAt: now,
author: { connect: { id: currentUser?.id ?? '' } },
workspaceMemberAuthor: {
connect: { id: currentWorkspaceMember?.id ?? '' },
},
assignee: { connect: { id: assigneeId ?? currentUser?.id ?? '' } },
workspaceMemberAssignee: {
connect: { id: currentWorkspaceMember?.id ?? '' },
},
type: type, 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);
}; };
}; };

View File

@ -1,45 +1,20 @@
import { DateTime } from 'luxon'; 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 { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; 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'; import { parseDate } from '~/utils/date-utils';
export const useCurrentUserTaskCount = () => { export const useCurrentUserTaskCount = () => {
const [currentUser] = useRecoilState(currentUserState);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objects } = useFindManyObjectRecords({ const { objects } = useFindManyObjectRecords({
objectNamePlural: 'activitiesV2', objectNamePlural: 'activitiesV2',
filter: { filter: {
type: { equals: ActivityType.Task }, type: { eq: 'Task' },
completedAt: { eq: null }, completedAt: { eq: null },
...(currentUser assigneeId: { eq: currentWorkspaceMember?.id },
? turnFilterIntoWhereClause({
fieldMetadataId: 'assigneeId',
value: currentUser.id,
operand: ViewFilterOperand.Is,
displayValue:
currentWorkspaceMember?.firstName +
' ' +
currentWorkspaceMember?.lastName,
displayAvatarUrl: currentWorkspaceMember?.avatarUrl ?? undefined,
definition: {
type: 'ENTITY',
},
})
: {}),
}, },
orderBy: [
{
createdAt: SortOrder.Desc,
},
],
}); });
const currentUserDueTaskCount = objects.filter((task) => { const currentUserDueTaskCount = objects.filter((task) => {

View File

@ -4,11 +4,9 @@ import styled from '@emotion/styled';
import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton'; import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { Activity } from '@/activities/types/Activity'; import { Activity } from '@/activities/types/Activity';
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity'; import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords'; import { useFindManyObjectRecords } from '@/object-record/hooks/useFindManyObjectRecords';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { SortOrder } from '~/generated/graphql';
import { TimelineItemsContainer } from './TimelineItemsContainer'; import { TimelineItemsContainer } from './TimelineItemsContainer';
@ -50,22 +48,29 @@ const StyledEmptyTimelineSubTitle = styled.div`
`; `;
export const Timeline = ({ entity }: { entity: ActivityTargetableEntity }) => { 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', objectNamePlural: 'activitiesV2',
filter: { filter: {
companyId: { eq: entity.id }, activityTargets: { in: activityTargets?.map((at) => at.id) },
},
orderBy: {
createdAt: 'AscNullsFirst',
}, },
orderBy: [
{
createdAt: SortOrder.Desc,
},
],
}); });
const openCreateActivity = useOpenCreateActivityDrawer(); const openCreateActivity = useOpenCreateActivityDrawer();
const activities: ActivityForDrawer[] = (objects ?? []) as Activity[];
if (loading) { if (loading) {
return <></>; return <></>;
} }
@ -95,7 +100,7 @@ export const Timeline = ({ entity }: { entity: ActivityTargetableEntity }) => {
return ( return (
<StyledMainContainer> <StyledMainContainer>
<TimelineItemsContainer activities={activities} /> <TimelineItemsContainer activities={activities as Activity[]} />
</StyledMainContainer> </StyledMainContainer>
); );
}; };

View File

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

View File

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

View File

@ -8,7 +8,10 @@ import { isNonEmptyArray } from '@sniptt/guards';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { GET_COMPANIES } from '@/companies/graphql/queries/getCompanies'; 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 { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { GET_PEOPLE } from '@/people/graphql/queries/getPeople'; import { GET_PEOPLE } from '@/people/graphql/queries/getPeople';
import { GET_API_KEYS } from '@/settings/developers/graphql/queries/getApiKeys'; 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 { OptimisticEffect } from '../types/internal/OptimisticEffect';
import { OptimisticEffectDefinition } from '../types/OptimisticEffectDefinition'; import { OptimisticEffectDefinition } from '../types/OptimisticEffectDefinition';
export const useOptimisticEffect = (objectNameSingular: string) => { export const useOptimisticEffect = (objectNameSingular?: string) => {
const apolloClient = useApolloClient(); const apolloClient = useApolloClient();
const { findManyQuery } = useFindOneObjectMetadataItem({ const { findManyQuery } = useFindOneObjectMetadataItem({
objectNameSingular, objectNameSingular,
@ -37,6 +40,12 @@ export const useOptimisticEffect = (objectNameSingular: string) => {
variables: OperationVariables; variables: OperationVariables;
definition: OptimisticEffectDefinition; definition: OptimisticEffectDefinition;
}) => { }) => {
if (findManyQuery === EMPTY_QUERY) {
throw new Error(
`Trying to register an optimistic effect for unknown object ${objectNameSingular}`,
);
}
const optimisticEffects = snapshot const optimisticEffects = snapshot
.getLoadable(optimisticEffectState) .getLoadable(optimisticEffectState)
.getValue(); .getValue();

View File

@ -38,7 +38,6 @@ export const RecordTableContainer = ({
}); });
const { updateOneObject } = useUpdateOneObjectRecord({ const { updateOneObject } = useUpdateOneObjectRecord({
objectNamePlural,
objectNameSingular: foundObjectMetadataItem?.nameSingular, objectNameSingular: foundObjectMetadataItem?.nameSingular,
}); });

View File

@ -30,9 +30,10 @@ export type RecordTablePageProps = Pick<
export const RecordTablePage = () => { export const RecordTablePage = () => {
const objectNamePlural = useParams().objectNamePlural ?? ''; const objectNamePlural = useParams().objectNamePlural ?? '';
const { objectNotFoundInMetadata, loading } = useFindOneObjectMetadataItem({ const { objectNotFoundInMetadata, loading, foundObjectMetadataItem } =
objectNamePlural, useFindOneObjectMetadataItem({
}); objectNamePlural,
});
const navigate = useNavigate(); const navigate = useNavigate();
@ -43,7 +44,7 @@ export const RecordTablePage = () => {
}, [objectNotFoundInMetadata, loading, navigate]); }, [objectNotFoundInMetadata, loading, navigate]);
const { createOneObject } = useCreateOneObjectRecord({ const { createOneObject } = useCreateOneObjectRecord({
objectNamePlural, objectNameSingular: foundObjectMetadataItem?.nameSingular,
}); });
const handleAddButtonClick = async () => { const handleAddButtonClick = async () => {

View File

@ -4,60 +4,44 @@ import { v4 } from 'uuid';
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { CurrencyCode, FieldMetadataType } from '~/generated-metadata/graphql';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
const defaultFieldValues: Record<FieldMetadataType, unknown> = { export const useCreateOneObjectRecord = <T>({
[FieldMetadataType.Currency]: { objectNameSingular,
amountMicros: null, }: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => {
currencyCode: CurrencyCode.Usd, const { triggerOptimisticEffects } = useOptimisticEffect(objectNameSingular);
},
[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<ObjectMetadataItemIdentifier, 'objectNamePlural'>) => {
const { triggerOptimisticEffects } = useOptimisticEffect('CompanyV2');
const { const {
foundObjectMetadataItem, foundObjectMetadataItem,
objectNotFoundInMetadata, objectNotFoundInMetadata,
createOneMutation, createOneMutation,
} = useFindOneObjectMetadataItem({ } = useFindOneObjectMetadataItem({
objectNamePlural, objectNameSingular,
}); });
// TODO: type this with a minimal type at least with Record<string, any> // TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(createOneMutation); const [mutate] = useMutation(createOneMutation);
const createOneObject = foundObjectMetadataItem const createOneObject =
? async (input: Record<string, any>) => { objectNameSingular && foundObjectMetadataItem
const createdObject = await mutate({ ? async (input: Record<string, any>) => {
variables: { const createdObject = await mutate({
input: { ...input, id: v4() }, variables: {
}, input: { ...input, id: v4() },
}); },
});
triggerOptimisticEffects( triggerOptimisticEffects(
`${capitalize(foundObjectMetadataItem.nameSingular)}Edge`, `${capitalize(foundObjectMetadataItem.nameSingular)}Edge`,
createdObject.data[ createdObject.data[
`create${capitalize(foundObjectMetadataItem.nameSingular)}` `create${capitalize(foundObjectMetadataItem.nameSingular)}`
], ],
); );
return createdObject.data; return createdObject.data[
} `create${capitalize(objectNameSingular)}`
: undefined; ] as T;
}
: undefined;
return { return {
createOneObject, createOneObject,

View File

@ -2,41 +2,45 @@ import { useMutation } from '@apollo/client';
import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem'; import { useFindOneObjectMetadataItem } from '@/object-metadata/hooks/useFindOneObjectMetadataItem';
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier'; import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
import { capitalize } from '~/utils/string/capitalize';
export const useUpdateOneObjectRecord = ({ export const useUpdateOneObjectRecord = <T>({
objectNamePlural,
objectNameSingular, objectNameSingular,
}: ObjectMetadataItemIdentifier) => { }: Pick<ObjectMetadataItemIdentifier, 'objectNameSingular'>) => {
const { const {
foundObjectMetadataItem, foundObjectMetadataItem,
objectNotFoundInMetadata, objectNotFoundInMetadata,
updateOneMutation, updateOneMutation,
} = useFindOneObjectMetadataItem({ } = useFindOneObjectMetadataItem({
objectNamePlural,
objectNameSingular, objectNameSingular,
}); });
// TODO: type this with a minimal type at least with Record<string, any> // TODO: type this with a minimal type at least with Record<string, any>
const [mutate] = useMutation(updateOneMutation); const [mutate] = useMutation(updateOneMutation);
const updateOneObject = foundObjectMetadataItem const updateOneObject =
? ({ objectNameSingular && foundObjectMetadataItem
idToUpdate, ? async ({
input, idToUpdate,
}: { input,
idToUpdate: string; }: {
input: Record<string, any>; idToUpdate: string;
}) => { input: Record<string, any>;
return mutate({ }) => {
variables: { const updatedObject = await mutate({
idToUpdate: idToUpdate, variables: {
input: { idToUpdate: idToUpdate,
...input, input: {
...input,
},
}, },
}, });
});
} return updatedObject.data[
: undefined; `update${capitalize(objectNameSingular)}`
] as T;
}
: undefined;
return { return {
updateOneObject, updateOneObject,

View File

@ -10,7 +10,7 @@ export const useColorScheme = () => {
const { updateOneObject: updateOneWorkspaceMember } = const { updateOneObject: updateOneWorkspaceMember } =
useUpdateOneObjectRecord({ useUpdateOneObjectRecord({
objectNamePlural: 'workspaceMembersV2', objectNameSingular: 'workspaceMemberV2',
}); });
const colorScheme = currentWorkspaceMember?.colorScheme ?? 'System'; const colorScheme = currentWorkspaceMember?.colorScheme ?? 'System';

View File

@ -18,6 +18,7 @@ import { useSnackBar } from '@/ui/feedback/snack-bar/hooks/useSnackBar';
import { MainButton } from '@/ui/input/button/components/MainButton'; import { MainButton } from '@/ui/input/button/components/MainButton';
import { TextInput } from '@/ui/input/components/TextInput'; import { TextInput } from '@/ui/input/components/TextInput';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
const StyledContentContainer = styled.div` const StyledContentContainer = styled.div`
width: 100%; width: 100%;
@ -59,7 +60,7 @@ export const CreateProfile = () => {
); );
const { updateOneObject, objectNotFoundInMetadata } = const { updateOneObject, objectNotFoundInMetadata } =
useUpdateOneObjectRecord({ useUpdateOneObjectRecord<WorkspaceMember>({
objectNameSingular: 'workspaceMemberV2', objectNameSingular: 'workspaceMemberV2',
}); });
@ -91,7 +92,7 @@ export const CreateProfile = () => {
throw new Error('Object not found in metadata'); throw new Error('Object not found in metadata');
} }
const result = await updateOneObject({ await updateOneObject({
idToUpdate: currentWorkspaceMember?.id, idToUpdate: currentWorkspaceMember?.id,
input: { input: {
firstName: data.firstName, firstName: data.firstName,
@ -99,10 +100,6 @@ export const CreateProfile = () => {
}, },
}); });
if (result.errors || !result.data?.updateWorkspaceMemberV2) {
throw result.errors ?? new Error('Unknown error');
}
setCurrentWorkspaceMember( setCurrentWorkspaceMember(
(current) => (current) =>
({ ({

View File

@ -33,7 +33,7 @@ export const SettingsNewObject = () => {
} = useObjectMetadataItemForSettings(); } = useObjectMetadataItemForSettings();
const { createOneObject: createOneView } = useCreateOneObjectRecord({ const { createOneObject: createOneView } = useCreateOneObjectRecord({
objectNamePlural: 'viewsV2', objectNameSingular: 'viewV2',
}); });
const [ const [

View File

@ -45,7 +45,7 @@ export const SettingsObjectNewFieldStep2 = () => {
const [objectViews, setObjectViews] = useState<View[]>([]); const [objectViews, setObjectViews] = useState<View[]>([]);
const { createOneObject: createOneViewField } = useCreateOneObjectRecord({ const { createOneObject: createOneViewField } = useCreateOneObjectRecord({
objectNamePlural: 'viewFieldsV2', objectNameSingular: 'viewFieldV2',
}); });
useFindManyObjectRecords({ useFindManyObjectRecords({

View File

@ -21,7 +21,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useGenerateOneApiKeyTokenMutation } from '~/generated/graphql'; import { ApiKey, useGenerateOneApiKeyTokenMutation } from '~/generated/graphql';
const StyledInfo = styled.span` const StyledInfo = styled.span`
color: ${({ theme }) => theme.font.color.light}; color: ${({ theme }) => theme.font.color.light};
@ -47,11 +47,13 @@ export const SettingsDevelopersApiKeyDetail = () => {
); );
const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation(); const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation();
const { createOneObject: createOneApiKey } = useCreateOneObjectRecord({ const { createOneObject: createOneApiKey } = useCreateOneObjectRecord<ApiKey>(
objectNamePlural: 'apiKeysV2', {
}); objectNameSingular: 'apiKeyV2',
const { updateOneObject: updateApiKey } = useUpdateOneObjectRecord({ },
objectNamePlural: 'apiKeysV2', );
const { updateOneObject: updateApiKey } = useUpdateOneObjectRecord<ApiKey>({
objectNameSingular: 'apiKeyV2',
}); });
const { object: apiKeyData } = useFindOneObjectRecord({ const { object: apiKeyData } = useFindOneObjectRecord({
@ -77,17 +79,22 @@ export const SettingsDevelopersApiKeyDetail = () => {
name: name, name: name,
expiresAt: newExpiresAt, expiresAt: newExpiresAt,
}); });
if (!newApiKey) {
return;
}
const tokenData = await generateOneApiKeyToken({ const tokenData = await generateOneApiKeyToken({
variables: { variables: {
data: { data: {
id: newApiKey.createApiKeyV2.id, id: newApiKey.id,
expiresAt: newApiKey.createApiKeyV2.expiresAt, expiresAt: newApiKey.expiresAt,
name: newApiKey.createApiKeyV2.name, // TODO update typing to remove useless name param here name: newApiKey.name, // TODO update typing to remove useless name param here
}, },
}, },
}); });
return { return {
id: newApiKey.createApiKeyV2.id, id: newApiKey.id,
token: tokenData.data?.generateApiKeyV2Token.token, token: tokenData.data?.generateApiKeyV2Token.token,
}; };
}; };
@ -100,7 +107,8 @@ export const SettingsDevelopersApiKeyDetail = () => {
); );
const apiKey = await createIntegration(apiKeyData.name, newExpiresAt); const apiKey = await createIntegration(apiKeyData.name, newExpiresAt);
await deleteIntegration(false); await deleteIntegration(false);
if (apiKey.token) {
if (apiKey && apiKey.token) {
setGeneratedApi(apiKey.id, apiKey.token); setGeneratedApi(apiKey.id, apiKey.token);
navigate(`/settings/developers/api-keys/${apiKey.id}`); navigate(`/settings/developers/api-keys/${apiKey.id}`);
} }

View File

@ -15,7 +15,7 @@ import { TextInput } from '@/ui/input/components/TextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Section } from '@/ui/layout/section/components/Section'; import { Section } from '@/ui/layout/section/components/Section';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb'; import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useGenerateOneApiKeyTokenMutation } from '~/generated/graphql'; import { ApiKey, useGenerateOneApiKeyTokenMutation } from '~/generated/graphql';
export const SettingsDevelopersApiKeysNew = () => { export const SettingsDevelopersApiKeysNew = () => {
const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation(); const [generateOneApiKeyToken] = useGenerateOneApiKeyTokenMutation();
@ -29,9 +29,11 @@ export const SettingsDevelopersApiKeysNew = () => {
name: '', name: '',
}); });
const { createOneObject: createOneApiKey } = useCreateOneObjectRecord({ const { createOneObject: createOneApiKey } = useCreateOneObjectRecord<ApiKey>(
objectNamePlural: 'apiKeysV2', {
}); objectNameSingular: 'apiKeyV2',
},
);
const onSave = async () => { const onSave = async () => {
const expiresAt = formValues.expirationDate const expiresAt = formValues.expirationDate
? DateTime.now().plus({ days: formValues.expirationDate }).toString() ? DateTime.now().plus({ days: formValues.expirationDate }).toString()
@ -40,21 +42,23 @@ export const SettingsDevelopersApiKeysNew = () => {
name: formValues.name, name: formValues.name,
expiresAt, expiresAt,
}); });
if (!newApiKey) {
return;
}
const tokenData = await generateOneApiKeyToken({ const tokenData = await generateOneApiKeyToken({
variables: { variables: {
data: { data: {
id: newApiKey.createApiKeyV2.id, id: newApiKey.id,
expiresAt: newApiKey.createApiKeyV2.expiresAt, expiresAt: newApiKey.expiresAt,
name: newApiKey.createApiKeyV2.name, // TODO update typing to remove useless name param here name: newApiKey.name, // TODO update typing to remove useless name param here
}, },
}, },
}); });
if (tokenData.data?.generateApiKeyV2Token) { if (tokenData.data?.generateApiKeyV2Token) {
setGeneratedApi( setGeneratedApi(newApiKey.id, tokenData.data.generateApiKeyV2Token.token);
newApiKey.createApiKeyV2.id, navigate(`/settings/developers/api-keys/${newApiKey.id}`);
tokenData.data.generateApiKeyV2Token.token,
);
navigate(`/settings/developers/api-keys/${newApiKey.createApiKeyV2.id}`);
} }
}; };
const canSave = !!formValues.name && createOneApiKey; const canSave = !!formValues.name && createOneApiKey;

View File

@ -112,7 +112,7 @@ export const seedActivityTargetFieldMetadata = async (
targetColumnMap: {}, targetColumnMap: {},
description: 'ActivityTarget activity', description: 'ActivityTarget activity',
icon: 'IconNotes', icon: 'IconNotes',
isNullable: false, isNullable: true,
isSystem: false, isSystem: false,
defaultValue: undefined, defaultValue: undefined,
}, },

View File

@ -283,7 +283,7 @@ export const seedActivityFieldMetadata = async (
description: description:
'Activity author. This is the person who created the activity', 'Activity author. This is the person who created the activity',
icon: 'IconUserCircle', icon: 'IconUserCircle',
isNullable: false, isNullable: true,
isSystem: false, isSystem: false,
defaultValue: undefined, defaultValue: undefined,
}, },

View File

@ -18,12 +18,10 @@ const activityTargetMetadata = {
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
name: 'activity', name: 'activity',
label: 'Activity', label: 'Activity',
targetColumnMap: { targetColumnMap: {},
value: 'activityId',
},
description: 'ActivityTarget activity', description: 'ActivityTarget activity',
icon: 'IconCheckbox', icon: 'IconCheckbox',
isNullable: false, isNullable: true,
}, },
{ {
isCustom: false, isCustom: false,
@ -51,6 +49,45 @@ const activityTargetMetadata = {
icon: 'IconBuildingSkyscraper', icon: 'IconBuildingSkyscraper',
isNullable: true, 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,
},
], ],
}; };

View File

@ -1,3 +1,4 @@
import { SeedWorkspaceId } from 'src/database/seeds/metadata';
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
const activityMetadata = { const activityMetadata = {
@ -129,28 +130,52 @@ const activityMetadata = {
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
name: 'author', name: 'author',
label: 'Author', label: 'Author',
targetColumnMap: { targetColumnMap: {},
value: 'authorId',
},
description: description:
'Activity author. This is the person who created the activity', 'Activity author. This is the person who created the activity',
icon: 'IconUserCircle', icon: 'IconUserCircle',
isNullable: false, 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, isCustom: false,
isActive: true, isActive: true,
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
name: 'assignee', name: 'assignee',
label: 'Assignee', label: 'Assignee',
targetColumnMap: { targetColumnMap: {},
value: 'assigneeId',
},
description: description:
'Acitivity assignee. This is the workspace member assigned to the activity ', 'Acitivity assignee. This is the workspace member assigned to the activity ',
icon: 'IconUserCircle', icon: 'IconUserCircle',
isNullable: true, 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,
},
], ],
}; };