feat: rename comment thread into activity (#939)

* feat: rename commentThread into activity server

* feat: rename commentThread into activity front

* feat: migration only create tables


feat: migration only create tables

* Update activities

* fix: rebase partial fix

* fix: all rebase problems and drop activity target alter

* fix: lint

* Update migration

* Update migration

* Fix conflicts

* Fix conflicts

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Jérémy M
2023-07-28 08:22:16 +02:00
committed by GitHub
parent fcdde024a3
commit d0641084f9
95 changed files with 2112 additions and 1725 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { CommentThreadActionBar } from '../../right-drawer/components/CommentThreadActionBar';
import { ActivityActionBar } from '../../right-drawer/components/ActivityActionBar';
import { Comment } from '../Comment';
import { mockComment, mockCommentWithLongValues } from './mock-comment';
@ -15,7 +15,7 @@ const meta: Meta<typeof Comment> = {
actionBar: {
type: 'boolean',
mapping: {
true: <CommentThreadActionBar commentThreadId="test-id" />,
true: <ActivityActionBar activityId="test-id" />,
false: undefined,
},
},

View File

@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react';
import { DateTime } from 'luxon';
import { CommentThreadActionBar } from '@/activities/right-drawer/components/CommentThreadActionBar';
import { ActivityActionBar } from '@/activities/right-drawer/components/ActivityActionBar';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { avatarUrl } from '~/testing/mock-data/users';
@ -17,7 +17,7 @@ const meta: Meta<typeof CommentHeader> = {
actionBar: {
type: 'boolean',
mapping: {
true: <CommentThreadActionBar commentThreadId="test-id" />,
true: <ActivityActionBar activityId="test-id" />,
false: undefined,
},
},

View File

@ -6,24 +6,21 @@ import styled from '@emotion/styled';
import debounce from 'lodash.debounce';
import { BlockEditor } from '@/ui/editor/components/BlockEditor';
import {
CommentThread,
useUpdateCommentThreadMutation,
} from '~/generated/graphql';
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
import { GET_COMMENT_THREADS_BY_TARGETS } from '../queries/select';
import { GET_ACTIVITIES_BY_TARGETS } from '../queries/select';
const BlockNoteStyledContainer = styled.div`
width: 100%;
`;
type OwnProps = {
commentThread: Pick<CommentThread, 'id' | 'body'>;
onChange?: (commentThreadBody: string) => void;
activity: Pick<Activity, 'id' | 'body'>;
onChange?: (activityBody: string) => void;
};
export function CommentThreadBodyEditor({ commentThread, onChange }: OwnProps) {
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
const [updateActivityMutation] = useUpdateActivityMutation();
const [body, setBody] = useState<string | null>(null);
@ -34,26 +31,22 @@ export function CommentThreadBodyEditor({ commentThread, onChange }: OwnProps) {
}, [body, onChange]);
const debounceOnChange = useMemo(() => {
function onInternalChange(commentThreadBody: string) {
setBody(commentThreadBody);
updateCommentThreadMutation({
function onInternalChange(activityBody: string) {
setBody(activityBody);
updateActivityMutation({
variables: {
id: commentThread.id,
body: commentThreadBody,
id: activity.id,
body: activityBody,
},
refetchQueries: [
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
],
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
});
}
return debounce(onInternalChange, 200);
}, [commentThread, updateCommentThreadMutation, setBody]);
}, [activity, updateActivityMutation, setBody]);
const editor: BlockNoteEditor | null = useBlockNote({
initialContent: commentThread.body
? JSON.parse(commentThread.body)
: undefined,
initialContent: activity.body ? JSON.parse(activity.body) : undefined,
editorDOMAttributes: { class: 'editor' },
onEditorContentChange: (editor) => {
debounceOnChange(JSON.stringify(editor.topLevelBlocks) ?? '');

View File

@ -6,15 +6,15 @@ import { v4 } from 'uuid';
import { currentUserState } from '@/auth/states/currentUserState';
import { useIsMobile } from '@/ui/hooks/useIsMobile';
import { AutosizeTextInput } from '@/ui/input/components/AutosizeTextInput';
import { CommentThread, useCreateCommentMutation } from '~/generated/graphql';
import { Activity, useCreateCommentMutation } from '~/generated/graphql';
import { isNonEmptyString } from '~/utils/isNonEmptyString';
import { Comment } from '../comment/Comment';
import { GET_COMMENT_THREAD } from '../queries';
import { GET_ACTIVITY } from '../queries';
import { CommentForDrawer } from '../types/CommentForDrawer';
type OwnProps = {
commentThread: Pick<CommentThread, 'id'> & {
activity: Pick<Activity, 'id'> & {
comments: Array<CommentForDrawer>;
};
};
@ -52,7 +52,7 @@ const StyledThreadCommentTitle = styled.div`
text-transform: uppercase;
`;
export function CommentThreadComments({ commentThread }: OwnProps) {
export function ActivityComments({ activity }: OwnProps) {
const [createCommentMutation] = useCreateCommentMutation();
const currentUser = useRecoilValue(currentUserState);
@ -69,21 +69,21 @@ export function CommentThreadComments({ commentThread }: OwnProps) {
variables: {
commentId: v4(),
authorId: currentUser?.id ?? '',
commentThreadId: commentThread?.id ?? '',
activityId: activity?.id ?? '',
commentText: commentText,
createdAt: new Date().toISOString(),
},
refetchQueries: [getOperationName(GET_COMMENT_THREAD) ?? ''],
refetchQueries: [getOperationName(GET_ACTIVITY) ?? ''],
});
}
return (
<>
{commentThread?.comments.length > 0 && (
{activity?.comments.length > 0 && (
<>
<StyledThreadItemListContainer>
<StyledThreadCommentTitle>Comments</StyledThreadCommentTitle>
{commentThread?.comments?.map((comment) => (
{activity?.comments?.map((comment) => (
<Comment key={comment.id} comment={comment} />
))}
</StyledThreadItemListContainer>

View File

@ -4,17 +4,17 @@ import { Button, ButtonVariant } from '@/ui/button/components/Button';
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon/index';
type CommentThreadCreateButtonProps = {
type ActivityCreateButtonProps = {
onNoteClick?: () => void;
onTaskClick?: () => void;
onActivityClick?: () => void;
};
export function CommentThreadCreateButton({
export function ActivityCreateButton({
onNoteClick,
onTaskClick,
onActivityClick,
}: CommentThreadCreateButtonProps) {
}: ActivityCreateButtonProps) {
const theme = useTheme();
return (
<ButtonGroup variant={ButtonVariant.Secondary}>

View File

@ -2,26 +2,26 @@ import React, { useCallback, useState } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { CommentThreadBodyEditor } from '@/activities/components/CommentThreadBodyEditor';
import { CommentThreadComments } from '@/activities/components/CommentThreadComments';
import { CommentThreadRelationPicker } from '@/activities/components/CommentThreadRelationPicker';
import { CommentThreadTypeDropdown } from '@/activities/components/CommentThreadTypeDropdown';
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
import { ActivityComments } from '@/activities/components/ActivityComments';
import { ActivityRelationPicker } from '@/activities/components/ActivityRelationPicker';
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
import { useIsMobile } from '@/ui/hooks/useIsMobile';
import { IconArrowUpRight } from '@/ui/icon/index';
import {
CommentThread,
CommentThreadTarget,
useUpdateCommentThreadMutation,
Activity,
ActivityTarget,
useUpdateActivityMutation,
} from '~/generated/graphql';
import { debounce } from '~/utils/debounce';
import { CommentThreadActionBar } from '../right-drawer/components/CommentThreadActionBar';
import { ActivityActionBar } from '../right-drawer/components/ActivityActionBar';
import { CommentForDrawer } from '../types/CommentForDrawer';
import { CommentThreadTitle } from './CommentThreadTitle';
import { ActivityTitle } from './ActivityTitle';
import '@blocknote/core/style.css';
@ -65,64 +65,57 @@ const StyledTopActionsContainer = styled.div`
`;
type OwnProps = {
commentThread: Pick<
CommentThread,
'id' | 'title' | 'body' | 'type' | 'completedAt'
> & {
activity: Pick<Activity, 'id' | 'title' | 'body' | 'type' | 'completedAt'> & {
comments?: Array<CommentForDrawer> | null;
} & {
commentThreadTargets?: Array<
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
activityTargets?: Array<
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
> | null;
};
showComment?: boolean;
autoFillTitle?: boolean;
};
export function CommentThreadEditor({
commentThread,
export function ActivityEditor({
activity,
showComment = true,
autoFillTitle = false,
}: OwnProps) {
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
useState<boolean>(false);
const [title, setTitle] = useState<string | null>(commentThread.title ?? '');
const [title, setTitle] = useState<string | null>(activity.title ?? '');
const [completedAt, setCompletedAt] = useState<string | null>(
commentThread.completedAt ?? '',
activity.completedAt ?? '',
);
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
const [updateActivityMutation] = useUpdateActivityMutation();
const updateTitle = useCallback(
(newTitle: string) => {
updateCommentThreadMutation({
updateActivityMutation({
variables: {
id: commentThread.id,
id: activity.id,
title: newTitle ?? '',
},
refetchQueries: [
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
],
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
});
},
[commentThread, updateCommentThreadMutation],
[activity, updateActivityMutation],
);
const handleActivityCompletionChange = useCallback(
(value: boolean) => {
updateCommentThreadMutation({
updateActivityMutation({
variables: {
id: commentThread.id,
id: activity.id,
completedAt: value ? new Date().toISOString() : null,
},
refetchQueries: [
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
],
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
});
setCompletedAt(value ? new Date().toISOString() : null);
},
[commentThread, updateCommentThreadMutation],
[activity, updateActivityMutation],
);
const debouncedUpdateTitle = debounce(updateTitle, 200);
@ -135,7 +128,7 @@ export function CommentThreadEditor({
}
}
if (!commentThread) {
if (!activity) {
return <></>;
}
@ -144,13 +137,13 @@ export function CommentThreadEditor({
<StyledUpperPartContainer>
<StyledTopContainer>
<StyledTopActionsContainer>
<CommentThreadTypeDropdown commentThread={commentThread} />
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
<ActivityTypeDropdown activity={activity} />
<ActivityActionBar activityId={activity?.id ?? ''} />
</StyledTopActionsContainer>
<CommentThreadTitle
<ActivityTitle
title={title ?? ''}
completed={!!completedAt}
type={commentThread.type}
type={activity.type}
onTitleChange={(newTitle) => {
setTitle(newTitle);
setHasUserManuallySetTitle(true);
@ -162,11 +155,10 @@ export function CommentThreadEditor({
<PropertyBoxItem
icon={<IconArrowUpRight />}
value={
<CommentThreadRelationPicker
commentThread={{
id: commentThread.id,
commentThreadTargets:
commentThread.commentThreadTargets ?? [],
<ActivityRelationPicker
activity={{
id: activity.id,
activityTargets: activity.activityTargets ?? [],
}}
/>
}
@ -174,16 +166,16 @@ export function CommentThreadEditor({
/>
</PropertyBox>
</StyledTopContainer>
<CommentThreadBodyEditor
commentThread={commentThread}
<ActivityBodyEditor
activity={activity}
onChange={updateTitleFromBody}
/>
</StyledUpperPartContainer>
{showComment && (
<CommentThreadComments
commentThread={{
id: commentThread.id,
comments: commentThread.comments ?? [],
<ActivityComments
activity={{
id: activity.id,
comments: activity.comments ?? [],
}}
/>
)}

View File

@ -18,19 +18,15 @@ import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
import { MultipleEntitySelect } from '@/ui/relation-picker/components/MultipleEntitySelect';
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
import {
CommentableType,
CommentThread,
CommentThreadTarget,
} from '~/generated/graphql';
import { Activity, ActivityTarget, CommentableType } from '~/generated/graphql';
import { useHandleCheckableCommentThreadTargetChange } from '../hooks/useHandleCheckableCommentThreadTargetChange';
import { useHandleCheckableActivityTargetChange } from '../hooks/useHandleCheckableActivityTargetChange';
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '../utils/flatMapAndSortEntityForSelectArrayByName';
type OwnProps = {
commentThread?: Pick<CommentThread, 'id'> & {
commentThreadTargets: Array<
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
activity?: Pick<Activity, 'id'> & {
activityTargets: Array<
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
>;
};
};
@ -75,7 +71,7 @@ const StyledMenuWrapper = styled.div`
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
export function ActivityRelationPicker({ activity }: OwnProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [searchFilter, setSearchFilter] = useState('');
const [selectedEntityIds, setSelectedEntityIds] = useState<
@ -88,17 +84,18 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
const initialPeopleIds = useMemo(
() =>
commentThread?.commentThreadTargets
activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Person')
.map((relation) => relation.commentableId) ?? [],
[commentThread?.commentThreadTargets],
[activity?.activityTargets],
);
const initialCompanyIds = useMemo(
() =>
commentThread?.commentThreadTargets
activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Company')
.map((relation) => relation.commentableId) ?? [],
[commentThread?.commentThreadTargets],
[activity?.activityTargets],
);
const initialSelectedEntityIds = useMemo(
@ -135,8 +132,8 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
companiesForMultiSelect.entitiesToSelect,
]);
const handleCheckItemsChange = useHandleCheckableCommentThreadTargetChange({
commentThread,
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
activity,
});
const exitEditMode = useCallback(() => {

View File

@ -51,7 +51,7 @@ type OwnProps = {
onCompletionChange: (value: boolean) => void;
};
export function CommentThreadTitle({
export function ActivityTitle({
title,
completed,
type,

View File

@ -7,17 +7,17 @@ import {
ChipVariant,
} from '@/ui/chip/components/Chip';
import { IconPhone } from '@/ui/icon';
import { CommentThread } from '~/generated/graphql';
import { Activity } from '~/generated/graphql';
type OwnProps = {
commentThread: Pick<CommentThread, 'type'>;
activity: Pick<Activity, 'type'>;
};
export function CommentThreadTypeDropdown({ commentThread }: OwnProps) {
export function ActivityTypeDropdown({ activity }: OwnProps) {
const theme = useTheme();
return (
<Chip
label={commentThread.type}
label={activity.type}
leftComponent={<IconPhone size={theme.icon.size.md} />}
size={ChipSize.Large}
accent={ChipAccent.TextSecondary}

View File

@ -4,17 +4,17 @@ import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';
import { mockedActivities } from '~/testing/mock-data/activities';
import { CommentThreadRelationPicker } from '../CommentThreadRelationPicker';
import { ActivityRelationPicker } from '../ActivityRelationPicker';
const StyledContainer = styled.div`
width: 400px;
`;
const meta: Meta<typeof CommentThreadRelationPicker> = {
title: 'Modules/Comments/CommentThreadRelationPicker',
component: CommentThreadRelationPicker,
const meta: Meta<typeof ActivityRelationPicker> = {
title: 'Modules/Comments/ActivityRelationPicker',
component: ActivityRelationPicker,
decorators: [
(Story) => (
<MemoryRouter>
@ -25,13 +25,13 @@ const meta: Meta<typeof CommentThreadRelationPicker> = {
),
ComponentDecorator,
],
args: { commentThread: mockedCommentThreads[0] },
args: { activity: mockedActivities[0] },
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof CommentThreadRelationPicker>;
type Story = StoryObj<typeof ActivityRelationPicker>;
export const Default: Story = {};

View File

@ -0,0 +1,84 @@
import { getOperationName } from '@apollo/client/utilities';
import { v4 } from 'uuid';
import { GET_COMPANIES } from '@/companies/queries';
import { GET_PEOPLE } from '@/people/queries';
import {
Activity,
ActivityTarget,
useAddActivityTargetsOnActivityMutation,
useRemoveActivityTargetsOnActivityMutation,
} from '~/generated/graphql';
import { GET_ACTIVITIES_BY_TARGETS } from '../queries';
import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect';
export function useHandleCheckableActivityTargetChange({
activity,
}: {
activity?: Pick<Activity, 'id'> & {
activityTargets: Array<
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
>;
};
}) {
const [addActivityTargetsOnActivity] =
useAddActivityTargetsOnActivityMutation({
refetchQueries: [
getOperationName(GET_COMPANIES) ?? '',
getOperationName(GET_PEOPLE) ?? '',
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
],
});
const [removeActivityTargetsOnActivity] =
useRemoveActivityTargetsOnActivityMutation({
refetchQueries: [
getOperationName(GET_COMPANIES) ?? '',
getOperationName(GET_PEOPLE) ?? '',
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
],
});
return async function handleCheckItemsChange(
entityValues: Record<string, boolean>,
entities: CommentableEntityForSelect[],
) {
if (!activity) {
return;
}
const currentEntityIds = activity.activityTargets.map(
({ commentableId }) => commentableId,
);
const entitiesToAdd = entities.filter(
({ id }) => entityValues[id] && !currentEntityIds.includes(id),
);
if (entitiesToAdd.length)
await addActivityTargetsOnActivity({
variables: {
activityId: activity.id,
activityTargetInputs: entitiesToAdd.map((entity) => ({
id: v4(),
createdAt: new Date().toISOString(),
commentableType: entity.entityType,
commentableId: entity.id,
})),
},
});
const activityTargetIdsToDelete = activity.activityTargets
.filter(({ commentableId }) => !entityValues[commentableId])
.map(({ id }) => id);
if (activityTargetIdsToDelete.length)
await removeActivityTargetsOnActivity({
variables: {
activityId: activity.id,
activityTargetIds: activityTargetIdsToDelete,
},
});
};
}

View File

@ -1,84 +0,0 @@
import { getOperationName } from '@apollo/client/utilities';
import { v4 } from 'uuid';
import { GET_COMPANIES } from '@/companies/queries';
import { GET_PEOPLE } from '@/people/queries';
import {
CommentThread,
CommentThreadTarget,
useAddCommentThreadTargetsOnCommentThreadMutation,
useRemoveCommentThreadTargetsOnCommentThreadMutation,
} from '~/generated/graphql';
import { GET_COMMENT_THREADS_BY_TARGETS } from '../queries';
import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect';
export function useHandleCheckableCommentThreadTargetChange({
commentThread,
}: {
commentThread?: Pick<CommentThread, 'id'> & {
commentThreadTargets: Array<
Pick<CommentThreadTarget, 'id' | 'commentableId'>
>;
};
}) {
const [addCommentThreadTargetsOnCommentThread] =
useAddCommentThreadTargetsOnCommentThreadMutation({
refetchQueries: [
getOperationName(GET_COMPANIES) ?? '',
getOperationName(GET_PEOPLE) ?? '',
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
],
});
const [removeCommentThreadTargetsOnCommentThread] =
useRemoveCommentThreadTargetsOnCommentThreadMutation({
refetchQueries: [
getOperationName(GET_COMPANIES) ?? '',
getOperationName(GET_PEOPLE) ?? '',
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
],
});
return async function handleCheckItemsChange(
entityValues: Record<string, boolean>,
entities: CommentableEntityForSelect[],
) {
if (!commentThread) {
return;
}
const currentEntityIds = commentThread.commentThreadTargets.map(
({ commentableId }) => commentableId,
);
const entitiesToAdd = entities.filter(
({ id }) => entityValues[id] && !currentEntityIds.includes(id),
);
if (entitiesToAdd.length)
await addCommentThreadTargetsOnCommentThread({
variables: {
commentThreadId: commentThread.id,
commentThreadTargetInputs: entitiesToAdd.map((entity) => ({
id: v4(),
createdAt: new Date().toISOString(),
commentableType: entity.entityType,
commentableId: entity.id,
})),
},
});
const commentThreadTargetIdsToDelete = commentThread.commentThreadTargets
.filter(({ commentableId }) => !entityValues[commentableId])
.map(({ id }) => id);
if (commentThreadTargetIdsToDelete.length)
await removeCommentThreadTargetsOnCommentThread({
variables: {
commentThreadId: commentThread.id,
commentThreadTargetIds: commentThreadTargetIdsToDelete,
},
});
};
}

View File

@ -5,18 +5,16 @@ import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
import { viewableActivityIdState } from '../states/viewableActivityIdState';
export function useOpenCommentThreadRightDrawer() {
export function useOpenActivityRightDrawer() {
const { openRightDrawer } = useRightDrawer();
const [, setViewableCommentThreadId] = useRecoilState(
viewableCommentThreadIdState,
);
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
const setHotkeyScope = useSetHotkeyScope();
return function openCommentThreadRightDrawer(commentThreadId: string) {
return function openActivityRightDrawer(activityId: string) {
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
setViewableCommentThreadId(commentThreadId);
openRightDrawer(RightDrawerPages.EditCommentThread);
setViewableActivityId(activityId);
openRightDrawer(RightDrawerPages.EditActivity);
};
}

View File

@ -9,40 +9,35 @@ import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
import {
ActivityType,
useCreateCommentThreadMutation,
} from '~/generated/graphql';
import { ActivityType, useCreateActivityMutation } from '~/generated/graphql';
import { GET_COMMENT_THREAD, GET_COMMENT_THREADS_BY_TARGETS } from '../queries';
import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '../queries';
import { commentableEntityArrayState } from '../states/commentableEntityArrayState';
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
import { viewableActivityIdState } from '../states/viewableActivityIdState';
import { CommentableEntity } from '../types/CommentableEntity';
export function useOpenCreateCommentThreadDrawer() {
export function useOpenCreateActivityDrawer() {
const { openRightDrawer } = useRightDrawer();
const [createCommentThreadMutation] = useCreateCommentThreadMutation();
const [createActivityMutation] = useCreateActivityMutation();
const currentUser = useRecoilValue(currentUserState);
const setHotkeyScope = useSetHotkeyScope();
const [, setCommentableEntityArray] = useRecoilState(
commentableEntityArrayState,
);
const [, setViewableCommentThreadId] = useRecoilState(
viewableCommentThreadIdState,
);
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
return function openCreateCommentThreadDrawer(
return function openCreateActivityDrawer(
entity: CommentableEntity,
type: ActivityType,
) {
createCommentThreadMutation({
createActivityMutation({
variables: {
authorId: currentUser?.id ?? '',
commentThreadId: v4(),
activityId: v4(),
createdAt: new Date().toISOString(),
type: type,
commentThreadTargetArray: [
activityTargetArray: [
{
commentableId: entity.id,
commentableType: entity.type,
@ -54,14 +49,14 @@ export function useOpenCreateCommentThreadDrawer() {
refetchQueries: [
getOperationName(GET_COMPANIES) ?? '',
getOperationName(GET_PEOPLE) ?? '',
getOperationName(GET_COMMENT_THREAD) ?? '',
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
getOperationName(GET_ACTIVITY) ?? '',
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
],
onCompleted(data) {
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
setViewableCommentThreadId(data.createOneCommentThread.id);
setViewableActivityId(data.createOneActivity.id);
setCommentableEntityArray([entity]);
openRightDrawer(RightDrawerPages.CreateCommentThread);
openRightDrawer(RightDrawerPages.CreateActivity);
},
});
};

View File

@ -13,21 +13,19 @@ import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector
import {
ActivityType,
CommentableType,
useCreateCommentThreadMutation,
useCreateActivityMutation,
} from '~/generated/graphql';
import { GET_COMMENT_THREAD, GET_COMMENT_THREADS_BY_TARGETS } from '../queries';
import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '../queries';
import { commentableEntityArrayState } from '../states/commentableEntityArrayState';
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
import { viewableActivityIdState } from '../states/viewableActivityIdState';
import { CommentableEntity } from '../types/CommentableEntity';
export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
export function useOpenCreateActivityDrawerForSelectedRowIds() {
const { openRightDrawer } = useRightDrawer();
const [createCommentThreadMutation] = useCreateCommentThreadMutation();
const [createActivityMutation] = useCreateActivityMutation();
const currentUser = useRecoilValue(currentUserState);
const [, setViewableCommentThreadId] = useRecoilState(
viewableCommentThreadIdState,
);
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
const setHotkeyScope = useSetHotkeyScope();
@ -47,13 +45,13 @@ export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
}),
);
createCommentThreadMutation({
createActivityMutation({
variables: {
authorId: currentUser?.id ?? '',
commentThreadId: v4(),
activityId: v4(),
createdAt: new Date().toISOString(),
type: ActivityType.Note,
commentThreadTargetArray: commentableEntityArray.map((entity) => ({
activityTargetArray: commentableEntityArray.map((entity) => ({
commentableId: entity.id,
commentableType: entity.type,
id: v4(),
@ -63,14 +61,14 @@ export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
refetchQueries: [
getOperationName(GET_COMPANIES) ?? '',
getOperationName(GET_PEOPLE) ?? '',
getOperationName(GET_COMMENT_THREAD) ?? '',
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
getOperationName(GET_ACTIVITY) ?? '',
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
],
onCompleted(data) {
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
setViewableCommentThreadId(data.createOneCommentThread.id);
setViewableActivityId(data.createOneActivity.id);
setCommentableEntityArray(commentableEntityArray);
openRightDrawer(RightDrawerPages.CreateCommentThread);
openRightDrawer(RightDrawerPages.CreateActivity);
},
});
};

View File

@ -5,7 +5,7 @@ export const CREATE_COMMENT = gql`
$commentId: String!
$commentText: String!
$authorId: String!
$commentThreadId: String!
$activityId: String!
$createdAt: DateTime!
) {
createOneComment(
@ -14,7 +14,7 @@ export const CREATE_COMMENT = gql`
createdAt: $createdAt
body: $commentText
author: { connect: { id: $authorId } }
commentThread: { connect: { id: $commentThreadId } }
activity: { connect: { id: $activityId } }
}
) {
id
@ -27,32 +27,32 @@ export const CREATE_COMMENT = gql`
lastName
avatarUrl
}
commentThreadId
activityId
}
}
`;
export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql`
mutation CreateCommentThread(
$commentThreadId: String!
export const CREATE_ACTIVITY_WITH_COMMENT = gql`
mutation CreateActivity(
$activityId: String!
$body: String
$title: String
$type: ActivityType!
$authorId: String!
$createdAt: DateTime!
$commentThreadTargetArray: [CommentThreadTargetCreateManyCommentThreadInput!]!
$activityTargetArray: [ActivityTargetCreateManyActivityInput!]!
) {
createOneCommentThread(
createOneActivity(
data: {
id: $commentThreadId
id: $activityId
createdAt: $createdAt
updatedAt: $createdAt
author: { connect: { id: $authorId } }
body: $body
title: $title
type: $type
commentThreadTargets: {
createMany: { data: $commentThreadTargetArray, skipDuplicates: true }
activityTargets: {
createMany: { data: $activityTargetArray, skipDuplicates: true }
}
}
) {
@ -61,11 +61,11 @@ export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql`
updatedAt
authorId
type
commentThreadTargets {
activityTargets {
id
createdAt
updatedAt
commentThreadId
activityId
commentableType
commentableId
}

View File

@ -1,16 +1,14 @@
import { gql } from '@apollo/client';
export const GET_COMMENT_THREADS_BY_TARGETS = gql`
query GetCommentThreadsByTargets(
$commentThreadTargetIds: [String!]!
$orderBy: [CommentThreadOrderByWithRelationInput!]
export const GET_ACTIVITIES_BY_TARGETS = gql`
query GetActivitiesByTargets(
$activityTargetIds: [String!]!
$orderBy: [ActivityOrderByWithRelationInput!]
) {
findManyCommentThreads(
findManyActivities(
orderBy: $orderBy
where: {
commentThreadTargets: {
some: { commentableId: { in: $commentThreadTargetIds } }
}
activityTargets: { some: { commentableId: { in: $activityTargetIds } } }
}
) {
id
@ -38,18 +36,18 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql`
avatarUrl
}
}
commentThreadTargets {
activityTargets {
id
commentableId
commentableType
commentableId
}
}
}
`;
export const GET_COMMENT_THREAD = gql`
query GetCommentThread($commentThreadId: String!) {
findManyCommentThreads(where: { id: { equals: $commentThreadId } }) {
export const GET_ACTIVITY = gql`
query GetActivity($activityId: String!) {
findManyActivities(where: { id: { equals: $activityId } }) {
id
createdAt
body
@ -75,10 +73,10 @@ export const GET_COMMENT_THREAD = gql`
avatarUrl
}
}
commentThreadTargets {
activityTargets {
id
commentableId
commentableType
commentableId
}
}
}

View File

@ -1,22 +1,18 @@
import { gql } from '@apollo/client';
export const ADD_COMMENT_THREAD_TARGETS = gql`
mutation AddCommentThreadTargetsOnCommentThread(
$commentThreadId: String!
$commentThreadTargetInputs: [CommentThreadTargetCreateManyCommentThreadInput!]!
export const ADD_ACTIVITY_TARGETS = gql`
mutation AddActivityTargetsOnActivity(
$activityId: String!
$activityTargetInputs: [ActivityTargetCreateManyActivityInput!]!
) {
updateOneCommentThread(
where: { id: $commentThreadId }
data: {
commentThreadTargets: {
createMany: { data: $commentThreadTargetInputs }
}
}
updateOneActivity(
where: { id: $activityId }
data: { activityTargets: { createMany: { data: $activityTargetInputs } } }
) {
id
createdAt
updatedAt
commentThreadTargets {
activityTargets {
id
createdAt
updatedAt
@ -27,23 +23,21 @@ export const ADD_COMMENT_THREAD_TARGETS = gql`
}
`;
export const REMOVE_COMMENT_THREAD_TARGETS = gql`
mutation RemoveCommentThreadTargetsOnCommentThread(
$commentThreadId: String!
$commentThreadTargetIds: [String!]!
export const REMOVE_ACTIVITY_TARGETS = gql`
mutation RemoveActivityTargetsOnActivity(
$activityId: String!
$activityTargetIds: [String!]!
) {
updateOneCommentThread(
where: { id: $commentThreadId }
updateOneActivity(
where: { id: $activityId }
data: {
commentThreadTargets: {
deleteMany: { id: { in: $commentThreadTargetIds } }
}
activityTargets: { deleteMany: { id: { in: $activityTargetIds } } }
}
) {
id
createdAt
updatedAt
commentThreadTargets {
activityTargets {
id
createdAt
updatedAt
@ -54,23 +48,23 @@ export const REMOVE_COMMENT_THREAD_TARGETS = gql`
}
`;
export const DELETE_COMMENT_THREAD = gql`
mutation DeleteCommentThread($commentThreadId: String!) {
deleteManyCommentThreads(where: { id: { equals: $commentThreadId } }) {
export const DELETE_ACTIVITY = gql`
mutation DeleteActivity($activityId: String!) {
deleteManyActivities(where: { id: { equals: $activityId } }) {
count
}
}
`;
export const UPDATE_COMMENT_THREAD = gql`
mutation UpdateCommentThread(
export const UPDATE_ACTIVITY = gql`
mutation UpdateActivity(
$id: String!
$body: String
$title: String
$type: ActivityType
$completedAt: DateTime
) {
updateOneCommentThread(
updateOneActivity(
where: { id: $id }
data: {
body: $body

View File

@ -0,0 +1,201 @@
import React, { useEffect, useMemo, useState } from 'react';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
import { ActivityComments } from '@/activities/components/ActivityComments';
import { ActivityRelationPicker } from '@/activities/components/ActivityRelationPicker';
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
import { GET_ACTIVITY } from '@/activities/queries';
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
import { useIsMobile } from '@/ui/hooks/useIsMobile';
import { IconArrowUpRight } from '@/ui/icon/index';
import {
useGetActivityQuery,
useUpdateActivityMutation,
} from '~/generated/graphql';
import { debounce } from '~/utils/debounce';
import { ActivityActionBar } from './ActivityActionBar';
import '@blocknote/core/style.css';
const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
overflow-y: auto;
`;
const StyledUpperPartContainer = styled.div`
align-items: flex-start;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(4)};
justify-content: flex-start;
`;
const StyledTopContainer = styled.div`
align-items: flex-start;
align-self: stretch;
background: ${({ theme }) => theme.background.secondary};
border-bottom: ${({ theme }) =>
useIsMobile() ? 'none' : `1px solid ${theme.border.color.medium}`};
display: flex;
flex-direction: column;
gap: 24px;
padding: 24px 24px 24px 48px;
`;
const StyledEditableTitleInput = styled.input`
background: transparent;
border: none;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
flex: 1 0 0;
flex-direction: column;
font-family: Inter;
font-size: ${({ theme }) => theme.font.size.xl};
font-style: normal;
font-weight: ${({ theme }) => theme.font.weight.semiBold};
justify-content: center;
line-height: ${({ theme }) => theme.text.lineHeight.md};
outline: none;
width: calc(100% - ${({ theme }) => theme.spacing(2)});
&::placeholder {
color: ${({ theme }) => theme.font.color.light};
}
`;
const StyledTopActionsContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
`;
type OwnProps = {
activityId: string;
showComment?: boolean;
autoFillTitle?: boolean;
};
export function Activity({
activityId,
showComment = true,
autoFillTitle = false,
}: OwnProps) {
const { data } = useGetActivityQuery({
variables: {
activityId: activityId ?? '',
},
skip: !activityId,
});
const activity = data?.findManyActivities[0];
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
useState<boolean>(false);
const [title, setTitle] = useState<string | null>(null);
useEffect(() => {
if (!hasUserManuallySetTitle) {
setTitle(activity?.title ?? '');
}
}, [setTitle, activity?.title, hasUserManuallySetTitle]);
const [updateActivityMutation] = useUpdateActivityMutation();
const debounceUpdateTitle = useMemo(() => {
function updateTitle(title: string) {
if (activity) {
updateActivityMutation({
variables: {
id: activityId,
title: title ?? '',
},
refetchQueries: [getOperationName(GET_ACTIVITY) ?? ''],
optimisticResponse: {
__typename: 'Mutation',
updateOneActivity: {
__typename: 'Activity',
id: activityId,
title: title,
type: activity.type,
},
},
});
}
}
return debounce(updateTitle, 200);
}, [activityId, updateActivityMutation, activity]);
function updateTitleFromBody(body: string) {
const parsedTitle = JSON.parse(body)[0]?.content[0]?.text;
if (!hasUserManuallySetTitle && autoFillTitle) {
setTitle(parsedTitle);
debounceUpdateTitle(parsedTitle);
}
}
if (!activity) {
return <></>;
}
return (
<StyledContainer>
<StyledUpperPartContainer>
<StyledTopContainer>
<StyledTopActionsContainer>
<ActivityTypeDropdown activity={activity} />
<ActivityActionBar activityId={activity?.id ?? ''} />
</StyledTopActionsContainer>
<StyledEditableTitleInput
autoFocus
placeholder={`${activity.type} title (optional)`}
onChange={(event) => {
setHasUserManuallySetTitle(true);
setTitle(event.target.value);
debounceUpdateTitle(event.target.value);
}}
value={title ?? ''}
/>
<PropertyBox>
<PropertyBoxItem
icon={<IconArrowUpRight />}
value={
<ActivityRelationPicker
activity={{
id: activity.id,
activityTargets: activity.activityTargets ?? [],
}}
/>
}
label="Relations"
/>
</PropertyBox>
</StyledTopContainer>
<ActivityBodyEditor
activity={activity}
onChange={updateTitleFromBody}
/>
</StyledUpperPartContainer>
{showComment && (
<ActivityComments
activity={{
id: activity.id,
comments: activity.comments ?? [],
}}
/>
)}
</StyledContainer>
);
}

View File

@ -3,13 +3,13 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
import { GET_COMPANIES } from '@/companies/queries';
import { GET_PEOPLE } from '@/people/queries';
import { Button, ButtonVariant } from '@/ui/button/components/Button';
import { IconTrash } from '@/ui/icon';
import { isRightDrawerOpenState } from '@/ui/right-drawer/states/isRightDrawerOpenState';
import { useDeleteCommentThreadMutation } from '~/generated/graphql';
import { useDeleteActivityMutation } from '~/generated/graphql';
const StyledContainer = styled.div`
color: ${({ theme }) => theme.font.color.tertiary};
@ -17,21 +17,21 @@ const StyledContainer = styled.div`
`;
type OwnProps = {
commentThreadId: string;
activityId: string;
};
export function CommentThreadActionBar({ commentThreadId }: OwnProps) {
export function ActivityActionBar({ activityId }: OwnProps) {
const theme = useTheme();
const [createCommentMutation] = useDeleteCommentThreadMutation();
const [createCommentMutation] = useDeleteActivityMutation();
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
function deleteCommentThread() {
function deleteActivity() {
createCommentMutation({
variables: { commentThreadId },
variables: { activityId },
refetchQueries: [
getOperationName(GET_COMPANIES) ?? '',
getOperationName(GET_PEOPLE) ?? '',
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
],
});
setIsRightDrawerOpen(false);
@ -43,7 +43,7 @@ export function CommentThreadActionBar({ commentThreadId }: OwnProps) {
icon={
<IconTrash size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
}
onClick={deleteCommentThread}
onClick={deleteActivity}
variant={ButtonVariant.Tertiary}
/>
</StyledContainer>

View File

@ -1,34 +0,0 @@
import { CommentThreadEditor } from '@/activities/components/CommentThreadEditor';
import { useGetCommentThreadQuery } from '~/generated/graphql';
import '@blocknote/core/style.css';
type OwnProps = {
commentThreadId: string;
showComment?: boolean;
autoFillTitle?: boolean;
};
export function CommentThread({
commentThreadId,
showComment = true,
autoFillTitle = false,
}: OwnProps) {
const { data } = useGetCommentThreadQuery({
variables: {
commentThreadId: commentThreadId ?? '',
},
skip: !commentThreadId,
});
const commentThread = data?.findManyCommentThreads[0];
return commentThread ? (
<CommentThreadEditor
commentThread={commentThread}
showComment={showComment}
autoFillTitle={autoFillTitle}
/>
) : (
<></>
);
}

View File

@ -1,22 +1,22 @@
import { useRecoilValue } from 'recoil';
import { viewableCommentThreadIdState } from '@/activities/states/viewableCommentThreadIdState';
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
import { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
import { CommentThread } from '../CommentThread';
import { Activity } from '../Activity';
export function RightDrawerCreateCommentThread() {
const commentThreadId = useRecoilValue(viewableCommentThreadIdState);
export function RightDrawerCreateActivity() {
const activityId = useRecoilValue(viewableActivityIdState);
return (
<RightDrawerPage>
<RightDrawerTopBar />
<RightDrawerBody>
{commentThreadId && (
<CommentThread
commentThreadId={commentThreadId}
{activityId && (
<Activity
activityId={activityId}
showComment={false}
autoFillTitle={true}
/>

View File

@ -1,20 +1,20 @@
import { useRecoilValue } from 'recoil';
import { viewableCommentThreadIdState } from '@/activities/states/viewableCommentThreadIdState';
import { viewableActivityIdState } from '@/activities/states/viewableActivityIdState';
import { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
import { CommentThread } from '../CommentThread';
import { Activity } from '../Activity';
export function RightDrawerEditCommentThread() {
const commentThreadId = useRecoilValue(viewableCommentThreadIdState);
export function RightDrawerEditActivity() {
const activityId = useRecoilValue(viewableActivityIdState);
return (
<RightDrawerPage>
<RightDrawerTopBar />
<RightDrawerBody>
{commentThreadId && <CommentThread commentThreadId={commentThreadId} />}
{activityId && <Activity activityId={activityId} />}
</RightDrawerBody>
</RightDrawerPage>
);

View File

@ -3,6 +3,6 @@ import { atom } from 'recoil';
import { CommentableEntity } from '../types/CommentableEntity';
export const commentableEntityArrayState = atom<CommentableEntity[]>({
key: 'comments/commentable-entity-array',
key: 'activities/commentable-entity-array',
default: [],
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const viewableActivityIdState = atom<string | null>({
key: 'activities/viewable-activity-id',
default: null,
});

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const viewableCommentThreadIdState = atom<string | null>({
key: 'comments/viewable-comment-thread-id',
default: null,
});

View File

@ -2,16 +2,16 @@ import React from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { CommentThreadCreateButton } from '@/activities/components/CommentThreadCreateButton';
import { useOpenCreateCommentThreadDrawer } from '@/activities/hooks/useOpenCreateCommentThreadDrawer';
import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
import { CommentableEntity } from '@/activities/types/CommentableEntity';
import { CommentThreadForDrawer } from '@/activities/types/CommentThreadForDrawer';
import { useIsMobile } from '@/ui/hooks/useIsMobile';
import { IconCircleDot } from '@/ui/icon';
import {
ActivityType,
SortOrder,
useGetCommentThreadsByTargetsQuery,
useGetActivitiesByTargetsQuery,
} from '~/generated/graphql';
import { TimelineActivity } from './TimelineActivity';
@ -96,9 +96,9 @@ const StyledStartIcon = styled.div`
export function Timeline({ entity }: { entity: CommentableEntity }) {
const theme = useTheme();
const { data: queryResult, loading } = useGetCommentThreadsByTargetsQuery({
const { data: queryResult, loading } = useGetActivitiesByTargetsQuery({
variables: {
commentThreadTargetIds: [entity.id],
activityTargetIds: [entity.id],
orderBy: [
{
createdAt: SortOrder.Desc,
@ -107,21 +107,20 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
},
});
const openCreateCommandThread = useOpenCreateCommentThreadDrawer();
const openCreateCommandThread = useOpenCreateActivityDrawer();
const commentThreads: CommentThreadForDrawer[] =
queryResult?.findManyCommentThreads ?? [];
const activities: ActivityForDrawer[] = queryResult?.findManyActivities ?? [];
if (loading) {
return <></>;
}
if (!commentThreads.length) {
if (!activities.length) {
return (
<StyledTimelineEmptyContainer>
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle>
<StyledEmptyTimelineSubTitle>Create one:</StyledEmptyTimelineSubTitle>
<CommentThreadCreateButton
<ActivityCreateButton
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
/>
@ -132,17 +131,14 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
return (
<StyledMainContainer>
<StyledTopActionBar>
<CommentThreadCreateButton
<ActivityCreateButton
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
/>
</StyledTopActionBar>
<StyledTimelineContainer>
{commentThreads.map((commentThread) => (
<TimelineActivity
key={commentThread.id}
commentThread={commentThread}
/>
{activities.map((activity) => (
<TimelineActivity key={activity.id} activity={activity} />
))}
<StyledStartIcon>
<IconCircleDot size={theme.icon.size.lg} />

View File

@ -3,14 +3,11 @@ import { Tooltip } from 'react-tooltip';
import { getOperationName } from '@apollo/client/utilities';
import styled from '@emotion/styled';
import { useOpenCommentThreadRightDrawer } from '@/activities/hooks/useOpenCommentThreadRightDrawer';
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
import { IconNotes } from '@/ui/icon';
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
import {
CommentThread,
useUpdateCommentThreadMutation,
} from '~/generated/graphql';
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
import {
beautifyExactDate,
beautifyPastDateRelativeToNow,
@ -115,35 +112,31 @@ const StyledTimelineItemContainer = styled.div`
`;
type OwnProps = {
commentThread: Pick<
CommentThread,
activity: Pick<
Activity,
'id' | 'title' | 'body' | 'createdAt' | 'completedAt' | 'type'
> & { author: Pick<CommentThread['author'], 'displayName'> };
> & { author: Pick<Activity['author'], 'displayName'> };
};
export function TimelineActivity({ commentThread }: OwnProps) {
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(
commentThread.createdAt,
);
const exactCreatedAt = beautifyExactDate(commentThread.createdAt);
const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0]?.text;
export function TimelineActivity({ activity }: OwnProps) {
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(activity.createdAt);
const exactCreatedAt = beautifyExactDate(activity.createdAt);
const body = JSON.parse(activity.body ?? '{}')[0]?.content[0]?.text;
const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer();
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
const openActivityRightDrawer = useOpenActivityRightDrawer();
const [updateActivityMutation] = useUpdateActivityMutation();
const handleActivityCompletionChange = useCallback(
(value: boolean) => {
updateCommentThreadMutation({
updateActivityMutation({
variables: {
id: commentThread.id,
id: activity.id,
completedAt: value ? new Date().toISOString() : null,
},
refetchQueries: [
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
],
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
});
},
[commentThread, updateCommentThreadMutation],
[activity, updateActivityMutation],
);
return (
@ -153,14 +146,14 @@ export function TimelineActivity({ commentThread }: OwnProps) {
<IconNotes />
</StyledIconContainer>
<StyledItemTitleContainer>
<span>{commentThread.author.displayName}</span>
created a {commentThread.type.toLowerCase()}
<span>{activity.author.displayName}</span>
created a note created a {activity.type.toLowerCase()}
</StyledItemTitleContainer>
<StyledItemTitleDate id={`id-${commentThread.id}`}>
<StyledItemTitleDate id={`id-${activity.id}`}>
{beautifiedCreatedAt} ago
</StyledItemTitleDate>
<StyledTooltip
anchorSelect={`#id-${commentThread.id}`}
anchorSelect={`#id-${activity.id}`}
content={exactCreatedAt}
clickable
noArrow
@ -171,13 +164,11 @@ export function TimelineActivity({ commentThread }: OwnProps) {
<StyledVerticalLine></StyledVerticalLine>
</StyledVerticalLineContainer>
<StyledCardContainer>
<StyledCard
onClick={() => openCommentThreadRightDrawer(commentThread.id)}
>
<StyledCard onClick={() => openActivityRightDrawer(activity.id)}>
<TimelineActivityTitle
title={commentThread.title ?? ''}
completed={!!commentThread.completedAt}
type={commentThread.type}
title={activity.title ?? ''}
completed={!!activity.completedAt}
type={activity.type}
onCompletionChange={handleActivityCompletionChange}
/>
<StyledCardContent>

View File

@ -0,0 +1,4 @@
import { GetActivitiesByTargetsQuery } from '~/generated/graphql';
export type ActivityForDrawer =
GetActivitiesByTargetsQuery['findManyActivities'][0];

View File

@ -1,5 +1,3 @@
import { CommentThreadForDrawer } from './CommentThreadForDrawer';
import { ActivityForDrawer } from './ActivityForDrawer';
export type CommentForDrawer = NonNullable<
CommentThreadForDrawer['comments']
>[0];
export type CommentForDrawer = NonNullable<ActivityForDrawer['comments']>[0];

View File

@ -1,4 +0,0 @@
import { GetCommentThreadsByTargetsQuery } from '~/generated/graphql';
export type CommentThreadForDrawer =
GetCommentThreadsByTargetsQuery['findManyCommentThreads'][0];

View File

@ -6,7 +6,7 @@ import { useRecoilState } from 'recoil';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { AppPath } from '@/types/AppPath';
import { CommentThreadTarget } from '~/generated/graphql';
import { ActivityTarget } from '~/generated/graphql';
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
@ -25,12 +25,12 @@ export function useApolloFactory() {
uri: `${process.env.REACT_APP_API_URL}`,
cache: new InMemoryCache({
typePolicies: {
CommentThread: {
Activity: {
fields: {
commentThreadTargets: {
activityTargets: {
merge(
_existing: CommentThreadTarget[] = [],
incoming: CommentThreadTarget[],
_existing: ActivityTarget[] = [],
incoming: ActivityTarget[],
) {
return [...incoming];
},

View File

@ -14,7 +14,7 @@ import { CompanyChip } from './CompanyChip';
type OwnProps = {
company: Pick<
GetCompaniesQuery['companies'][0],
'id' | 'name' | 'domainName' | '_commentThreadCount'
'id' | 'name' | 'domainName' | '_activityCount'
>;
};

View File

@ -28,7 +28,7 @@ export const GET_COMPANIES = gql`
address
linkedinUrl
employees
_commentThreadCount
_activityCount
accountOwner {
id
email

View File

@ -12,7 +12,7 @@ export const GET_COMPANY = gql`
address
linkedinUrl
employees
_commentThreadCount
_activityCount
accountOwner {
id
email

View File

@ -25,7 +25,7 @@ export function EditableCompanyNameCell() {
id: currentRowEntityId ?? '',
name: name ?? '',
domainName: domainName ?? '',
_commentThreadCount: commentCount ?? 0,
_activityCount: commentCount ?? 0,
}}
/>
);

View File

@ -0,0 +1,14 @@
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
import { CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateActivityCompany() {
const openCreateActivityRightDrawer =
useOpenCreateActivityDrawerForSelectedRowIds();
async function handleButtonClick() {
openCreateActivityRightDrawer(CommentableType.Company);
}
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
}

View File

@ -1,14 +0,0 @@
import { useOpenCreateCommentThreadDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateCommentDrawerForSelectedRowIds';
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
import { CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateCommentThreadCompany() {
const openCreateCommentThreadRightDrawer =
useOpenCreateCommentThreadDrawerForSelectedRowIds();
async function handleButtonClick() {
openCreateCommentThreadRightDrawer(CommentableType.Company);
}
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
}

View File

@ -10,7 +10,7 @@ type MockedCompany = Pick<
| 'address'
| 'employees'
| 'linkedinUrl'
| '_commentThreadCount'
| '_activityCount'
> & {
accountOwner: Pick<
User,
@ -33,7 +33,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
createdAt: '2023-04-26T10:08:54.724515+00:00',
address: 'San Francisco, CA',
employees: 5000,
_commentThreadCount: 0,
_activityCount: 0,
accountOwner: {
email: 'charles@test.com',
displayName: 'Charles Test',
@ -53,7 +53,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
createdAt: '2023-04-26T10:12:42.33625+00:00',
address: 'Paris, France',
employees: 800,
_commentThreadCount: 0,
_activityCount: 0,
accountOwner: null,
__typename: 'Company',
},
@ -65,7 +65,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
createdAt: '2023-04-26T10:10:32.530184+00:00',
address: 'San Francisco, CA',
employees: 8000,
_commentThreadCount: 0,
_activityCount: 0,
accountOwner: null,
__typename: 'Company',
},
@ -77,7 +77,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
createdAt: '2023-03-21T06:30:25.39474+00:00',
address: 'San Francisco, CA',
employees: 800,
_commentThreadCount: 0,
_activityCount: 0,
accountOwner: null,
__typename: 'Company',
},
@ -89,7 +89,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
createdAt: '2023-04-26T10:13:29.712485+00:00',
address: 'San Francisco, CA',
employees: 400,
_commentThreadCount: 0,
_activityCount: 0,
accountOwner: null,
__typename: 'Company',
},

View File

@ -83,10 +83,10 @@ export function useSetCompanyEntityTable() {
.getLoadable(companyCommentCountFamilyState(company.id))
.valueOrThrow();
if (currentCommentCount !== company._commentThreadCount) {
if (currentCommentCount !== company._activityCount) {
set(
companyCommentCountFamilyState(company.id),
company._commentThreadCount,
company._activityCount,
);
}

View File

@ -10,11 +10,7 @@ type OwnProps = {
| Partial<
Pick<
Person,
| 'id'
| 'firstName'
| 'lastName'
| 'displayName'
| '_commentThreadCount'
'id' | 'firstName' | 'lastName' | 'displayName' | '_activityCount'
>
>
| null

View File

@ -101,12 +101,12 @@ export function useSetPeopleEntityTable() {
if (
currentNameCell.firstName !== person.firstName ||
currentNameCell.lastName !== person.lastName ||
currentNameCell.commentCount !== person._commentThreadCount
currentNameCell.commentCount !== person._activityCount
) {
set(peopleNameCellFamilyState(person.id), {
firstName: person.firstName ?? null,
lastName: person.lastName ?? null,
commentCount: person._commentThreadCount,
commentCount: person._activityCount,
displayName: person.displayName ?? null,
});
}

View File

@ -31,7 +31,7 @@ export const GET_PEOPLE = gql`
jobTitle
linkedinUrl
createdAt
_commentThreadCount
_activityCount
company {
id
name
@ -107,7 +107,7 @@ export const GET_PERSON_NAMES_AND_COMMENT_COUNT = gql`
firstName
lastName
displayName
_commentThreadCount
_activityCount
}
}
`;
@ -129,7 +129,7 @@ export const GET_PERSON_COMMENT_COUNT = gql`
query GetPersonCommentCountById($id: String!) {
person: findUniquePerson(id: $id) {
id
_commentThreadCount
_activityCount
}
}
`;

View File

@ -15,7 +15,7 @@ export const GET_PERSON = gql`
jobTitle
linkedinUrl
phone
_commentThreadCount
_activityCount
company {
id
name

View File

@ -21,7 +21,7 @@ export function EditablePeopleFullNameCell() {
<EditablePeopleFullName
person={{
id: currentRowEntityId ?? undefined,
_commentThreadCount: commentCount ?? undefined,
_activityCount: commentCount ?? undefined,
firstName,
lastName,
displayName: displayName ?? undefined,

View File

@ -0,0 +1,14 @@
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
import { CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateActivityPeople() {
const openCreateActivityRightDrawer =
useOpenCreateActivityDrawerForSelectedRowIds();
async function handleButtonClick() {
openCreateActivityRightDrawer(CommentableType.Person);
}
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
}

View File

@ -1,14 +0,0 @@
import { useOpenCreateCommentThreadDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateCommentDrawerForSelectedRowIds';
import { TableActionBarButtonToggleComments } from '@/ui/table/action-bar/components/TableActionBarButtonOpenComments';
import { CommentableType } from '~/generated/graphql';
export function TableActionBarButtonCreateCommentThreadPeople() {
const openCreateCommentThreadRightDrawer =
useOpenCreateCommentThreadDrawerForSelectedRowIds();
async function handleButtonClick() {
openCreateCommentThreadRightDrawer(CommentableType.Person);
}
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
}

View File

@ -0,0 +1,144 @@
import React, { useEffect, useState } from 'react';
import styled from '@emotion/styled';
import { IconChevronDown } from '@/ui/icon/index';
type ButtonProps = React.ComponentProps<'button'>;
export type DropdownOptionType = {
key: string;
label: string;
icon: React.ReactNode;
};
type OwnProps = {
options: DropdownOptionType[];
selectedOptionKey?: string;
onSelection: (value: DropdownOptionType) => void;
} & ButtonProps;
const StyledButton = styled.button<ButtonProps & { isOpen: boolean }>`
align-items: center;
background: ${({ theme }) => theme.background.tertiary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-bottom-left-radius: ${({ isOpen, theme }) =>
isOpen ? 0 : theme.border.radius.sm};
border-bottom-right-radius: ${({ isOpen, theme }) =>
isOpen ? 0 : theme.border.radius.sm};
border-top-left-radius: ${({ theme }) => theme.border.radius.sm};
border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
svg {
align-items: center;
display: flex;
height: 14px;
justify-content: center;
width: 14px;
}
`;
const StyledDropdownItem = styled.button<ButtonProps>`
align-items: center;
background: ${({ theme }) => theme.background.tertiary};
border: 1px solid ${({ theme }) => theme.border.color.medium};
border-radius: ${({ theme }) => theme.border.radius.sm};
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
color: ${({ theme }) => theme.font.color.secondary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(2)};
padding: ${({ theme }) => theme.spacing(1)} ${({ theme }) => theme.spacing(2)};
svg {
align-items: center;
display: flex;
height: 14px;
justify-content: center;
width: 14px;
}
`;
const DropdownContainer = styled.div`
position: relative;
`;
const DropdownMenu = styled.div`
display: flex;
flex-direction: column;
position: absolute;
width: 100%;
`;
export function DropdownButton({
options,
selectedOptionKey,
onSelection,
...buttonProps
}: OwnProps) {
const [isOpen, setIsOpen] = useState(false);
const [selectedOption, setSelectedOption] = useState<
DropdownOptionType | undefined
>(undefined);
useEffect(() => {
if (selectedOptionKey) {
const option = options.find((option) => option.key === selectedOptionKey);
setSelectedOption(option);
} else {
setSelectedOption(options[0]);
}
}, [selectedOptionKey, options]);
if (!options.length) {
throw new Error('You must provide at least one option.');
}
const handleSelect =
(option: DropdownOptionType) =>
(event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
onSelection(option);
setSelectedOption(option);
setIsOpen(false);
};
return (
<>
{selectedOption && (
<DropdownContainer>
<StyledButton
onClick={() => setIsOpen(!isOpen)}
{...buttonProps}
isOpen={isOpen}
>
{selectedOption.icon}
{selectedOption.label}
{options.length > 1 && <IconChevronDown />}
</StyledButton>
{isOpen && (
<DropdownMenu>
{options
.filter((option) => option.label !== selectedOption.label)
.map((option, index) => (
<StyledDropdownItem
key={index}
onClick={handleSelect(option)}
>
{option.icon}
{option.label}
</StyledDropdownItem>
))}
</DropdownMenu>
)}
</DropdownContainer>
)}
</>
);
}

View File

@ -1,7 +1,7 @@
import { useRecoilState } from 'recoil';
import { RightDrawerCreateCommentThread } from '@/activities/right-drawer/components/create/RightDrawerCreateCommentThread';
import { RightDrawerEditCommentThread } from '@/activities/right-drawer/components/edit/RightDrawerEditCommentThread';
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
import { RightDrawerTimeline } from '@/activities/right-drawer/components/RightDrawerTimeline';
import { isDefined } from '~/utils/isDefined';
@ -18,10 +18,10 @@ export function RightDrawerRouter() {
switch (rightDrawerPage) {
case RightDrawerPages.Timeline:
return <RightDrawerTimeline />;
case RightDrawerPages.CreateCommentThread:
return <RightDrawerCreateCommentThread />;
case RightDrawerPages.EditCommentThread:
return <RightDrawerEditCommentThread />;
case RightDrawerPages.CreateActivity:
return <RightDrawerCreateActivity />;
case RightDrawerPages.EditActivity:
return <RightDrawerEditActivity />;
default:
return <></>;
}

View File

@ -1,5 +1,5 @@
export enum RightDrawerPages {
Timeline = 'timeline',
CreateCommentThread = 'create-comment-thread',
EditCommentThread = 'edit-comment-thread',
CreateActivity = 'create-activity',
EditActivity = 'edit-activity',
}

View File

@ -9,7 +9,7 @@ export type EditableChipProps = {
value: string;
editModeHorizontalAlign?: 'left' | 'right';
ChipComponent: React.ReactNode;
commentThreadCount?: number;
activityCount?: number;
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
rightEndContents?: ReactNode[];
onSubmit?: (newValue: string) => void;

View File

@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import { GET_COMPANIES } from '@/companies/queries';
import { CompanyTable } from '@/companies/table/components/CompanyTable';
import { TableActionBarButtonCreateCommentThreadCompany } from '@/companies/table/components/TableActionBarButtonCreateCommentThreadCompany';
import { TableActionBarButtonCreateActivityCompany } from '@/companies/table/components/TableActionBarButtonCreateActivityCompany';
import { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies';
import { IconBuildingSkyscraper } from '@/ui/icon';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
@ -53,7 +53,7 @@ export function Companies() {
<CompanyTable />
</StyledTableContainer>
<EntityTableActionBar>
<TableActionBarButtonCreateCommentThreadCompany />
<TableActionBarButtonCreateActivityCompany />
<TableActionBarButtonDeleteCompanies />
</EntityTableActionBar>
</RecoilScope>

View File

@ -2,7 +2,7 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { CompanyTableMockMode } from '@/companies/table/components/CompanyTableMockMode';
import { TableActionBarButtonCreateCommentThreadCompany } from '@/companies/table/components/TableActionBarButtonCreateCommentThreadCompany';
import { TableActionBarButtonCreateActivityCompany } from '@/companies/table/components/TableActionBarButtonCreateActivityCompany';
import { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies';
import { IconBuildingSkyscraper } from '@/ui/icon';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
@ -29,7 +29,7 @@ export function CompaniesMockMode() {
<CompanyTableMockMode />
</StyledTableContainer>
<EntityTableActionBar>
<TableActionBarButtonCreateCommentThreadCompany />
<TableActionBarButtonCreateActivityCompany />
<TableActionBarButtonDeleteCompanies />
</EntityTableActionBar>
</RecoilScope>

View File

@ -4,18 +4,15 @@ import type { Meta, StoryObj } from '@storybook/react';
import { fireEvent, within } from '@storybook/testing-library';
import { graphql } from 'msw';
import {
GET_COMMENT_THREAD,
GET_COMMENT_THREADS_BY_TARGETS,
} from '@/activities/queries';
import { CREATE_COMMENT_THREAD_WITH_COMMENT } from '@/activities/queries/create';
import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '@/activities/queries';
import { CREATE_ACTIVITY_WITH_COMMENT } from '@/activities/queries/create';
import { GET_COMPANY } from '@/companies/queries';
import {
PageDecorator,
type PageDecoratorArgs,
} from '~/testing/decorators/PageDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';
import { mockedActivities } from '~/testing/mock-data/activities';
import { mockedCompaniesData } from '~/testing/mock-data/companies';
import { CompanyShow } from '../CompanyShow';
@ -30,11 +27,11 @@ const meta: Meta<PageDecoratorArgs> = {
msw: [
...graphqlMocks,
graphql.query(
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
(req, res, ctx) => {
return res(
ctx.data({
findManyCommentThreads: mockedCommentThreads,
findManyActivities: mockedActivities,
}),
);
},
@ -82,25 +79,22 @@ export const EditNote: Story = {
msw: [
...meta.parameters?.msw,
graphql.mutation(
getOperationName(CREATE_COMMENT_THREAD_WITH_COMMENT) ?? '',
getOperationName(CREATE_ACTIVITY_WITH_COMMENT) ?? '',
(req, res, ctx) => {
return res(
ctx.data({
createOneCommentThread: mockedCommentThreads[0],
}),
);
},
),
graphql.query(
getOperationName(GET_COMMENT_THREAD) ?? '',
(req, res, ctx) => {
return res(
ctx.data({
findManyCommentThreads: [mockedCommentThreads[0]],
createOneActivity: mockedActivities[0],
}),
);
},
),
graphql.query(getOperationName(GET_ACTIVITY) ?? '', (req, res, ctx) => {
return res(
ctx.data({
findManyActivitys: [mockedActivities[0]],
}),
);
}),
],
},
};

View File

@ -4,7 +4,7 @@ import styled from '@emotion/styled';
import { GET_PEOPLE } from '@/people/queries';
import { PeopleTable } from '@/people/table/components/PeopleTable';
import { TableActionBarButtonCreateCommentThreadPeople } from '@/people/table/components/TableActionBarButtonCreateCommentThreadPeople';
import { TableActionBarButtonCreateActivityPeople } from '@/people/table/components/TableActionBarButtonCreateActivityPeople';
import { TableActionBarButtonDeletePeople } from '@/people/table/components/TableActionBarButtonDeletePeople';
import { IconUser } from '@/ui/icon';
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
@ -46,7 +46,7 @@ export function People() {
<PeopleTable />
</StyledTableContainer>
<EntityTableActionBar>
<TableActionBarButtonCreateCommentThreadPeople />
<TableActionBarButtonCreateActivityPeople />
<TableActionBarButtonDeletePeople />
</EntityTableActionBar>
</WithTopBarContainer>

View File

@ -1,13 +1,13 @@
import {
Activity,
ActivityTarget,
ActivityType,
Comment,
CommentableType,
CommentThread,
CommentThreadTarget,
} from '~/generated/graphql';
type MockedCommentThread = Pick<
CommentThread,
type MockedActivity = Pick<
Activity,
| 'id'
| 'createdAt'
| 'updatedAt'
@ -25,21 +25,21 @@ type MockedCommentThread = Pick<
displayName: string;
};
comments: Array<Pick<Comment, 'body'>>;
commentThreadTargets: Array<
activityTargets: Array<
Pick<
CommentThreadTarget,
ActivityTarget,
| 'id'
| '__typename'
| 'createdAt'
| 'updatedAt'
| 'commentableType'
| 'activityId'
| 'commentableId'
| 'commentThreadId'
> & { commentThread: Pick<CommentThread, 'id' | 'createdAt' | 'updatedAt'> }
| 'commentableType'
> & { activity: Pick<Activity, 'id' | 'createdAt' | 'updatedAt'> }
>;
};
export const mockedCommentThreads: Array<MockedCommentThread> = [
export const mockedActivities: Array<MockedActivity> = [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
createdAt: '2023-04-26T10:12:42.33625+00:00',
@ -55,20 +55,20 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
},
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
comments: [],
commentThreadTargets: [
activityTargets: [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
commentableType: CommentableType.Company,
commentableId: '89bb825c-171e-4bcc-9cf7-43448d6fb278', // airbnb
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
commentThread: {
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
activity: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
},
__typename: 'CommentThreadTarget',
__typename: 'ActivityTarget',
},
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb301',
@ -76,16 +76,16 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
updatedAt: new Date().toISOString(),
commentableType: CommentableType.Company,
commentableId: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae', // aircall
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
commentThread: {
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
activity: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
__typename: 'CommentThreadTarget',
__typename: 'ActivityTarget',
},
],
__typename: 'CommentThread',
__typename: 'Activity',
},
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
@ -102,20 +102,20 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
},
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
comments: [],
commentThreadTargets: [
activityTargets: [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
commentableType: CommentableType.Person,
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', // Alexandre
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
commentThread: {
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
activity: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
},
__typename: 'CommentThreadTarget',
__typename: 'ActivityTarget',
},
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
@ -123,15 +123,15 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
updatedAt: new Date().toISOString(),
commentableType: CommentableType.Person,
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d', // Jean d'Eau
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
commentThread: {
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
activity: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
__typename: 'CommentThreadTarget',
__typename: 'ActivityTarget',
},
],
__typename: 'CommentThread',
__typename: 'Activity',
},
];

View File

@ -10,7 +10,7 @@ type MockedCompany = Pick<
| 'address'
| 'employees'
| 'linkedinUrl'
| '_commentThreadCount'
| '_activityCount'
> & {
accountOwner: Pick<
User,
@ -33,7 +33,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
address: '17 rue de clignancourt',
employees: 12,
linkedinUrl: 'https://www.linkedin.com/company/airbnb/',
_commentThreadCount: 1,
_activityCount: 1,
accountOwner: {
email: 'charles@test.com',
displayName: 'Charles Test',
@ -53,7 +53,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
address: '',
employees: 1,
linkedinUrl: 'https://www.linkedin.com/company/aircall/',
_commentThreadCount: 1,
_activityCount: 1,
accountOwner: null,
__typename: 'Company',
},
@ -65,7 +65,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
address: '',
employees: 1,
linkedinUrl: 'https://www.linkedin.com/company/algolia/',
_commentThreadCount: 1,
_activityCount: 1,
accountOwner: null,
__typename: 'Company',
},
@ -77,7 +77,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
address: '',
employees: 10,
linkedinUrl: 'https://www.linkedin.com/company/apple/',
_commentThreadCount: 0,
_activityCount: 0,
accountOwner: null,
__typename: 'Company',
},
@ -89,7 +89,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
address: '10 rue de la Paix',
employees: 1,
linkedinUrl: 'https://www.linkedin.com/company/qonto/',
_commentThreadCount: 2,
_activityCount: 2,
accountOwner: null,
__typename: 'Company',
},
@ -101,7 +101,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
address: '',
employees: 1,
linkedinUrl: 'https://www.linkedin.com/company/facebook/',
_commentThreadCount: 13,
_activityCount: 13,
accountOwner: null,
__typename: 'Company',
},
@ -113,7 +113,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
address: '',
employees: 1,
linkedinUrl: 'https://www.linkedin.com/company/sequoia/',
_commentThreadCount: 1,
_activityCount: 1,
accountOwner: null,
__typename: 'Company',
},

View File

@ -17,7 +17,7 @@ type MockedPerson = RequiredAndNotNull<
| '__typename'
| 'phone'
| 'city'
| '_commentThreadCount'
| '_activityCount'
| 'createdAt'
> & {
company: Pick<Company, 'id' | 'name' | 'domainName' | '__typename'>;
@ -41,7 +41,7 @@ export const mockedPeopleData: MockedPerson[] = [
__typename: 'Company',
},
phone: '06 12 34 56 78',
_commentThreadCount: 1,
_activityCount: 1,
createdAt: '2023-04-20T13:20:09.158312+00:00',
city: 'Paris',
@ -62,7 +62,7 @@ export const mockedPeopleData: MockedPerson[] = [
__typename: 'Company',
},
phone: '06 12 34 56 78',
_commentThreadCount: 1,
_activityCount: 1,
createdAt: '2023-04-20T13:20:09.158312+00:00',
city: 'Paris',
@ -83,7 +83,7 @@ export const mockedPeopleData: MockedPerson[] = [
__typename: 'Company',
},
phone: '06 12 34 56 78',
_commentThreadCount: 1,
_activityCount: 1,
createdAt: '2023-04-20T13:20:09.158312+00:00',
city: 'Paris',
@ -104,7 +104,7 @@ export const mockedPeopleData: MockedPerson[] = [
__typename: 'Company',
},
phone: '06 12 34 56 78',
_commentThreadCount: 2,
_activityCount: 2,
createdAt: '2023-04-20T13:20:09.158312+00:00',
city: 'Paris',

View File

@ -3,7 +3,8 @@ import { Injectable } from '@nestjs/common';
import { PureAbility, AbilityBuilder } from '@casl/ability';
import { createPrismaAbility, PrismaQuery, Subjects } from '@casl/prisma';
import {
CommentThread,
Attachment,
Activity,
Company,
Comment,
Person,
@ -11,11 +12,10 @@ import {
User,
Workspace,
WorkspaceMember,
CommentThreadTarget,
ActivityTarget,
Pipeline,
PipelineStage,
PipelineProgress,
Attachment,
UserSettings,
ViewField,
} from '@prisma/client';
@ -29,9 +29,9 @@ type SubjectsAbility = Subjects<{
Company: Company;
Person: Person;
RefreshToken: RefreshToken;
CommentThread: CommentThread;
Activity: Activity;
Comment: Comment;
CommentThreadTarget: CommentThreadTarget;
ActivityTarget: ActivityTarget;
Pipeline: Pipeline;
PipelineStage: PipelineStage;
PipelineProgress: PipelineProgress;
@ -86,11 +86,11 @@ export class AbilityFactory {
// RefreshToken
cannot(AbilityAction.Manage, 'RefreshToken');
// CommentThread
can(AbilityAction.Read, 'CommentThread', { workspaceId: workspace.id });
can(AbilityAction.Create, 'CommentThread');
can(AbilityAction.Update, 'CommentThread', { workspaceId: workspace.id });
can(AbilityAction.Delete, 'CommentThread', { workspaceId: workspace.id });
// Activity
can(AbilityAction.Read, 'Activity', { workspaceId: workspace.id });
can(AbilityAction.Create, 'Activity');
can(AbilityAction.Update, 'Activity', { workspaceId: workspace.id });
can(AbilityAction.Delete, 'Activity', { workspaceId: workspace.id });
// Comment
can(AbilityAction.Read, 'Comment', { workspaceId: workspace.id });
@ -104,9 +104,9 @@ export class AbilityFactory {
authorId: user.id,
});
// CommentThreadTarget
can(AbilityAction.Read, 'CommentThreadTarget');
can(AbilityAction.Create, 'CommentThreadTarget');
// ActivityTarget
can(AbilityAction.Read, 'ActivityTarget');
can(AbilityAction.Create, 'ActivityTarget');
// Attachment
can(AbilityAction.Read, 'Attachment', { workspaceId: workspace.id });

View File

@ -46,12 +46,12 @@ import {
DeleteRefreshTokenAbilityHandler,
} from './handlers/refresh-token.ability-handler';
import {
ManageCommentThreadAbilityHandler,
ReadCommentThreadAbilityHandler,
CreateCommentThreadAbilityHandler,
UpdateCommentThreadAbilityHandler,
DeleteCommentThreadAbilityHandler,
} from './handlers/comment-thread.ability-handler';
ManageActivityAbilityHandler,
ReadActivityAbilityHandler,
CreateActivityAbilityHandler,
UpdateActivityAbilityHandler,
DeleteActivityAbilityHandler,
} from './handlers/activity.ability-handler';
import {
ManageCommentAbilityHandler,
ReadCommentAbilityHandler,
@ -60,12 +60,12 @@ import {
DeleteCommentAbilityHandler,
} from './handlers/comment.ability-handler';
import {
ManageCommentThreadTargetAbilityHandler,
ReadCommentThreadTargetAbilityHandler,
CreateCommentThreadTargetAbilityHandler,
UpdateCommentThreadTargetAbilityHandler,
DeleteCommentThreadTargetAbilityHandler,
} from './handlers/comment-thread-target.ability-handler';
ManageActivityTargetAbilityHandler,
ReadActivityTargetAbilityHandler,
CreateActivityTargetAbilityHandler,
UpdateActivityTargetAbilityHandler,
DeleteActivityTargetAbilityHandler,
} from './handlers/activity-target.ability-handler';
import {
ManagePipelineAbilityHandler,
ReadPipelineAbilityHandler,
@ -140,24 +140,24 @@ import {
CreateRefreshTokenAbilityHandler,
UpdateRefreshTokenAbilityHandler,
DeleteRefreshTokenAbilityHandler,
// CommentThread
ManageCommentThreadAbilityHandler,
ReadCommentThreadAbilityHandler,
CreateCommentThreadAbilityHandler,
UpdateCommentThreadAbilityHandler,
DeleteCommentThreadAbilityHandler,
// Activity
ManageActivityAbilityHandler,
ReadActivityAbilityHandler,
CreateActivityAbilityHandler,
UpdateActivityAbilityHandler,
DeleteActivityAbilityHandler,
// Comment
ManageCommentAbilityHandler,
ReadCommentAbilityHandler,
CreateCommentAbilityHandler,
UpdateCommentAbilityHandler,
DeleteCommentAbilityHandler,
// CommentThreadTarget
ManageCommentThreadTargetAbilityHandler,
ReadCommentThreadTargetAbilityHandler,
CreateCommentThreadTargetAbilityHandler,
UpdateCommentThreadTargetAbilityHandler,
DeleteCommentThreadTargetAbilityHandler,
// ActivityTarget
ManageActivityTargetAbilityHandler,
ReadActivityTargetAbilityHandler,
CreateActivityTargetAbilityHandler,
UpdateActivityTargetAbilityHandler,
DeleteActivityTargetAbilityHandler,
//Attachment
ManageAttachmentAbilityHandler,
ReadAttachmentAbilityHandler,
@ -224,24 +224,24 @@ import {
CreateRefreshTokenAbilityHandler,
UpdateRefreshTokenAbilityHandler,
DeleteRefreshTokenAbilityHandler,
// CommentThread
ManageCommentThreadAbilityHandler,
ReadCommentThreadAbilityHandler,
CreateCommentThreadAbilityHandler,
UpdateCommentThreadAbilityHandler,
DeleteCommentThreadAbilityHandler,
// Activity
ManageActivityAbilityHandler,
ReadActivityAbilityHandler,
CreateActivityAbilityHandler,
UpdateActivityAbilityHandler,
DeleteActivityAbilityHandler,
// Comment
ManageCommentAbilityHandler,
ReadCommentAbilityHandler,
CreateCommentAbilityHandler,
UpdateCommentAbilityHandler,
DeleteCommentAbilityHandler,
// CommentThreadTarget
ManageCommentThreadTargetAbilityHandler,
ReadCommentThreadTargetAbilityHandler,
CreateCommentThreadTargetAbilityHandler,
UpdateCommentThreadTargetAbilityHandler,
DeleteCommentThreadTargetAbilityHandler,
// ActivityTarget
ManageActivityTargetAbilityHandler,
ReadActivityTargetAbilityHandler,
CreateActivityTargetAbilityHandler,
UpdateActivityTargetAbilityHandler,
DeleteActivityTargetAbilityHandler,
//Attachment
ManageAttachmentAbilityHandler,
ReadAttachmentAbilityHandler,

View File

@ -0,0 +1,79 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { assert } from 'src/utils/assert';
import { ActivityTargetWhereInput } from 'src/core/@generated/activity-target/activity-target-where.input';
class ActivityTargetArgs {
where?: ActivityTargetWhereInput;
}
@Injectable()
export class ManageActivityTargetAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'ActivityTarget');
}
}
@Injectable()
export class ReadActivityTargetAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'ActivityTarget');
}
}
@Injectable()
export class CreateActivityTargetAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Create, 'ActivityTarget');
}
}
@Injectable()
export class UpdateActivityTargetAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityTargetArgs>();
const ActivityTarget = await this.prismaService.client.activityTarget.findFirst({
where: args.where,
});
assert(ActivityTarget, '', NotFoundException);
return ability.can(
AbilityAction.Update,
subject('ActivityTarget', ActivityTarget),
);
}
}
@Injectable()
export class DeleteActivityTargetAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityTargetArgs>();
const ActivityTarget = await this.prismaService.client.activityTarget.findFirst({
where: args.where,
});
assert(ActivityTarget, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('ActivityTarget', ActivityTarget),
);
}
}

View File

@ -0,0 +1,73 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { assert } from 'src/utils/assert';
import { ActivityWhereInput } from 'src/core/@generated/activity/activity-where.input';
class ActivityArgs {
where?: ActivityWhereInput;
}
@Injectable()
export class ManageActivityAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'Activity');
}
}
@Injectable()
export class ReadActivityAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'Activity');
}
}
@Injectable()
export class CreateActivityAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Create, 'Activity');
}
}
@Injectable()
export class UpdateActivityAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityArgs>();
const Activity = await this.prismaService.client.activity.findFirst({
where: args.where,
});
assert(Activity, '', NotFoundException);
return ability.can(AbilityAction.Update, subject('Activity', Activity));
}
}
@Injectable()
export class DeleteActivityAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<ActivityArgs>();
const Activity = await this.prismaService.client.activity.findFirst({
where: args.where,
});
assert(Activity, '', NotFoundException);
return ability.can(AbilityAction.Delete, subject('Activity', Activity));
}
}

View File

@ -42,16 +42,12 @@ export class CreateAttachmentAbilityHandler implements IAbilityHandler {
const args = gqlContext.getArgs<AttachmentArgs>();
assert(args.activityId, '', ForbiddenException);
const activity = await this.prismaService.client.commentThread.findUnique({
const activity = await this.prismaService.client.activity.findUnique({
where: { id: args.activityId },
include: { workspace: true },
});
assert(activity, '', NotFoundException);
return ability.can(
AbilityAction.Update,
subject('Workspace', activity.workspace),
);
return ability.can(AbilityAction.Update, subject('Activity', activity));
}
}

View File

@ -1,118 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { CommentThreadTargetWhereInput } from 'src/core/@generated/comment-thread-target/comment-thread-target-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class CommentThreadTargetArgs {
where?: CommentThreadTargetWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageCommentThreadTargetAbilityHandler
implements IAbilityHandler
{
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'CommentThreadTarget');
}
}
@Injectable()
export class ReadCommentThreadTargetAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'CommentThreadTarget');
}
}
@Injectable()
export class CreateCommentThreadTargetAbilityHandler
implements IAbilityHandler
{
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'CommentThreadTarget',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'CommentThreadTarget');
}
}
@Injectable()
export class UpdateCommentThreadTargetAbilityHandler
implements IAbilityHandler
{
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CommentThreadTargetArgs>();
const commentThreadTarget =
await this.prismaService.client.commentThreadTarget.findFirst({
where: args.where,
});
assert(commentThreadTarget, '', NotFoundException);
const allowed = await relationAbilityChecker(
'CommentThreadTarget',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(
AbilityAction.Update,
subject('CommentThreadTarget', commentThreadTarget),
);
}
}
@Injectable()
export class DeleteCommentThreadTargetAbilityHandler
implements IAbilityHandler
{
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CommentThreadTargetArgs>();
const commentThreadTarget =
await this.prismaService.client.commentThreadTarget.findFirst({
where: args.where,
});
assert(commentThreadTarget, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('CommentThreadTarget', commentThreadTarget),
);
}
}

View File

@ -1,110 +0,0 @@
import {
ExecutionContext,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { subject } from '@casl/ability';
import { IAbilityHandler } from 'src/ability/interfaces/ability-handler.interface';
import { PrismaService } from 'src/database/prisma.service';
import { AbilityAction } from 'src/ability/ability.action';
import { AppAbility } from 'src/ability/ability.factory';
import { CommentThreadWhereInput } from 'src/core/@generated/comment-thread/comment-thread-where.input';
import { relationAbilityChecker } from 'src/ability/ability.util';
import { assert } from 'src/utils/assert';
class CommentThreadArgs {
where?: CommentThreadWhereInput;
[key: string]: any;
}
@Injectable()
export class ManageCommentThreadAbilityHandler implements IAbilityHandler {
async handle(ability: AppAbility) {
return ability.can(AbilityAction.Manage, 'CommentThread');
}
}
@Injectable()
export class ReadCommentThreadAbilityHandler implements IAbilityHandler {
handle(ability: AppAbility) {
return ability.can(AbilityAction.Read, 'CommentThread');
}
}
@Injectable()
export class CreateCommentThreadAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs();
const allowed = await relationAbilityChecker(
'CommentThread',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(AbilityAction.Create, 'CommentThread');
}
}
@Injectable()
export class UpdateCommentThreadAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CommentThreadArgs>();
const commentThread =
await this.prismaService.client.commentThread.findFirst({
where: args.where,
});
assert(commentThread, '', NotFoundException);
const allowed = await relationAbilityChecker(
'CommentThread',
ability,
this.prismaService.client,
args,
);
if (!allowed) {
return false;
}
return ability.can(
AbilityAction.Update,
subject('CommentThread', commentThread),
);
}
}
@Injectable()
export class DeleteCommentThreadAbilityHandler implements IAbilityHandler {
constructor(private readonly prismaService: PrismaService) {}
async handle(ability: AppAbility, context: ExecutionContext) {
const gqlContext = GqlExecutionContext.create(context);
const args = gqlContext.getArgs<CommentThreadArgs>();
const commentThread =
await this.prismaService.client.commentThread.findFirst({
where: args.where,
});
assert(commentThread, '', NotFoundException);
return ability.can(
AbilityAction.Delete,
subject('CommentThread', commentThread),
);
}
}

View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { ActivityResolver } from './resolvers/activity.resolver';
import { ActivityService } from './services/activity.service';
import { ActivityTargetService } from './services/activity-target.service';
@Module({
providers: [ActivityResolver, ActivityService, ActivityTargetService],
exports: [ActivityService, ActivityTargetService],
})
export class ActivityModule {}

View File

@ -1,19 +1,19 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
import { ActivityService } from 'src/core/activity/services/activity.service';
import { AbilityFactory } from 'src/ability/ability.factory';
import { CommentThreadResolver } from './comment-thread.resolver';
import { ActivityResolver } from './activity.resolver';
describe('CommentThreadResolver', () => {
let resolver: CommentThreadResolver;
describe('ActivityResolver', () => {
let resolver: ActivityResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CommentThreadResolver,
ActivityResolver,
{
provide: CommentThreadService,
provide: ActivityService,
useValue: {},
},
{
@ -23,7 +23,7 @@ describe('CommentThreadResolver', () => {
],
}).compile();
resolver = module.get<CommentThreadResolver>(CommentThreadResolver);
resolver = module.get<ActivityResolver>(ActivityResolver);
});
it('should be defined', () => {

View File

@ -0,0 +1,156 @@
import { Resolver, Args, Mutation, Query } from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common';
import { accessibleBy } from '@casl/prisma';
import { Prisma } from '@prisma/client';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import {
PrismaSelector,
PrismaSelect,
} from 'src/decorators/prisma-select.decorator';
import { AbilityGuard } from 'src/guards/ability.guard';
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
import {
CreateActivityAbilityHandler,
DeleteActivityAbilityHandler,
ReadActivityAbilityHandler,
UpdateActivityAbilityHandler,
} from 'src/ability/handlers/activity.ability-handler';
import { UserAbility } from 'src/decorators/user-ability.decorator';
import { AppAbility } from 'src/ability/ability.factory';
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
import { Activity } from 'src/core/@generated/activity/activity.model';
import { ActivityService } from 'src/core/activity/services/activity.service';
import { CreateOneActivityArgs } from 'src/core/@generated/activity/create-one-activity.args';
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
import { UpdateOneActivityArgs } from 'src/core/@generated/activity/update-one-activity.args';
import { FindManyActivityArgs } from 'src/core/@generated/activity/find-many-activity.args';
import { DeleteManyActivityArgs } from 'src/core/@generated/activity/delete-many-activity.args';
@UseGuards(JwtAuthGuard)
@Resolver(() => Activity)
export class ActivityResolver {
constructor(private readonly activityService: ActivityService) {}
@Mutation(() => Activity, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(CreateActivityAbilityHandler)
async createOneActivity(
@Args() args: CreateOneActivityArgs,
@AuthWorkspace() workspace: Workspace,
@PrismaSelector({ modelName: 'Activity' })
prismaSelect: PrismaSelect<'Activity'>,
): Promise<Partial<Activity>> {
const createdActivity = await this.activityService.create({
data: {
...args.data,
...{ workspace: { connect: { id: workspace.id } } },
activityTargets: args.data?.activityTargets?.createMany
? {
createMany: {
data: args.data.activityTargets.createMany.data.map(
(target) => ({ ...target, workspaceId: workspace.id }),
),
},
}
: undefined,
},
select: prismaSelect.value,
} as Prisma.ActivityCreateArgs);
return createdActivity;
}
@Mutation(() => Activity, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(UpdateActivityAbilityHandler)
async updateOneActivity(
@Args() args: UpdateOneActivityArgs,
@AuthWorkspace() workspace: Workspace,
@PrismaSelector({ modelName: 'Activity' })
prismaSelect: PrismaSelect<'Activity'>,
): Promise<Partial<Activity>> {
// TODO: Do a proper check with recursion testing on args in a more generic place
for (const key in args.data) {
if (args.data[key]) {
for (const subKey in args.data[key]) {
if (JSON.stringify(args.data[key][subKey]) === '{}') {
delete args.data[key][subKey];
}
}
}
if (JSON.stringify(args.data[key]) === '{}') {
delete args.data[key];
}
}
const updatedActivity = await this.activityService.update({
where: args.where,
data: {
...args.data,
activityTargets: args.data?.activityTargets
? {
createMany: args.data.activityTargets.createMany
? {
data: args.data.activityTargets.createMany.data.map(
(target) => ({
...target,
workspaceId: workspace.id,
}),
),
}
: undefined,
deleteMany: args.data.activityTargets.deleteMany ?? undefined,
}
: undefined,
},
select: prismaSelect.value,
} as Prisma.ActivityUpdateArgs);
return updatedActivity;
}
@Query(() => [Activity])
@UseGuards(AbilityGuard)
@CheckAbilities(ReadActivityAbilityHandler)
async findManyActivities(
@Args() args: FindManyActivityArgs,
@UserAbility() ability: AppAbility,
@PrismaSelector({ modelName: 'Activity' })
prismaSelect: PrismaSelect<'Activity'>,
): Promise<Partial<Activity>[]> {
const result = await this.activityService.findMany({
where: {
...args.where,
AND: [accessibleBy(ability).Activity],
},
orderBy: args.orderBy,
cursor: args.cursor,
take: args.take,
skip: args.skip,
distinct: args.distinct,
select: prismaSelect.value,
});
return result;
}
@Mutation(() => AffectedRows, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(DeleteActivityAbilityHandler)
async deleteManyActivities(
@Args() args: DeleteManyActivityArgs,
): Promise<AffectedRows> {
return this.activityService.deleteMany({
where: args.where,
});
}
}

View File

@ -3,15 +3,15 @@ import { Test, TestingModule } from '@nestjs/testing';
import { PrismaService } from 'src/database/prisma.service';
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
import { CommentThreadTargetService } from './comment-thread-target.service';
import { ActivityTargetService } from './activity-target.service';
describe('CommentThreadTargetService', () => {
let service: CommentThreadTargetService;
describe('ActivityTargetService', () => {
let service: ActivityTargetService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CommentThreadTargetService,
ActivityTargetService,
{
provide: PrismaService,
useValue: prismaMock,
@ -19,9 +19,7 @@ describe('CommentThreadTargetService', () => {
],
}).compile();
service = module.get<CommentThreadTargetService>(
CommentThreadTargetService,
);
service = module.get<ActivityTargetService>(ActivityTargetService);
});
it('should be defined', () => {

View File

@ -0,0 +1,40 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class ActivityTargetService {
constructor(private readonly prismaService: PrismaService) {}
// Find
findFirst = this.prismaService.client.activityTarget.findFirst;
findFirstOrThrow = this.prismaService.client.activityTarget.findFirstOrThrow;
findUnique = this.prismaService.client.activityTarget.findUnique;
findUniqueOrThrow =
this.prismaService.client.activityTarget.findUniqueOrThrow;
findMany = this.prismaService.client.activityTarget.findMany;
// Create
create = this.prismaService.client.activityTarget.create;
createMany = this.prismaService.client.activityTarget.createMany;
// Update
update = this.prismaService.client.activityTarget.update;
upsert = this.prismaService.client.activityTarget.upsert;
updateMany = this.prismaService.client.activityTarget.updateMany;
// Delete
delete = this.prismaService.client.activityTarget.delete;
deleteMany = this.prismaService.client.activityTarget.deleteMany;
// Aggregate
aggregate = this.prismaService.client.activityTarget.aggregate;
// Count
count = this.prismaService.client.activityTarget.count;
// GroupBy
groupBy = this.prismaService.client.activityTarget.groupBy;
}

View File

@ -3,15 +3,15 @@ import { Test, TestingModule } from '@nestjs/testing';
import { PrismaService } from 'src/database/prisma.service';
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
import { CommentThreadService } from './comment-thread.service';
import { ActivityService } from './activity.service';
describe('CommentThreadService', () => {
let service: CommentThreadService;
describe('ActivityService', () => {
let service: ActivityService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CommentThreadService,
ActivityService,
{
provide: PrismaService,
useValue: prismaMock,
@ -19,7 +19,7 @@ describe('CommentThreadService', () => {
],
}).compile();
service = module.get<CommentThreadService>(CommentThreadService);
service = module.get<ActivityService>(ActivityService);
});
it('should be defined', () => {

View File

@ -0,0 +1,39 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class ActivityService {
constructor(private readonly prismaService: PrismaService) {}
// Find
findFirst = this.prismaService.client.activity.findFirst;
findFirstOrThrow = this.prismaService.client.activity.findFirstOrThrow;
findUnique = this.prismaService.client.activity.findUnique;
findUniqueOrThrow = this.prismaService.client.activity.findUniqueOrThrow;
findMany = this.prismaService.client.activity.findMany;
// Create
create = this.prismaService.client.activity.create;
createMany = this.prismaService.client.activity.createMany;
// Update
update = this.prismaService.client.activity.update;
upsert = this.prismaService.client.activity.upsert;
updateMany = this.prismaService.client.activity.updateMany;
// Delete
delete = this.prismaService.client.activity.delete;
deleteMany = this.prismaService.client.activity.deleteMany;
// Aggregate
aggregate = this.prismaService.client.activity.aggregate;
// Count
count = this.prismaService.client.activity.count;
// GroupBy
groupBy = this.prismaService.client.activity.groupBy;
}

View File

@ -1,19 +1,10 @@
import { Module } from '@nestjs/common';
import { CommentService } from './services/comment.service';
import { CommentResolver } from './resolvers/comment.resolver';
import { CommentThreadTargetService } from './services/comment-thread-target.service';
import { CommentThreadResolver } from './resolvers/comment-thread.resolver';
import { CommentThreadService } from './services/comment-thread.service';
import { CommentService } from './comment.service';
import { CommentResolver } from './comment.resolver';
@Module({
providers: [
CommentService,
CommentThreadService,
CommentThreadTargetService,
CommentResolver,
CommentThreadResolver,
],
exports: [CommentService, CommentThreadService, CommentThreadTargetService],
providers: [CommentService, CommentResolver],
exports: [CommentService],
})
export class CommentModule {}

View File

@ -1,6 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentService } from 'src/core/comment/services/comment.service';
import { CommentService } from 'src/core/comment/comment.service';
import { AbilityFactory } from 'src/ability/ability.factory';
import { CommentResolver } from './comment.resolver';

View File

@ -8,7 +8,7 @@ import { Workspace } from 'src/core/@generated/workspace/workspace.model';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import { CreateOneCommentArgs } from 'src/core/@generated/comment/create-one-comment.args';
import { Comment } from 'src/core/@generated/comment/comment.model';
import { CommentService } from 'src/core/comment/services/comment.service';
import { CommentService } from 'src/core/comment/comment.service';
import {
PrismaSelector,
PrismaSelect,

View File

@ -1,150 +0,0 @@
import { Resolver, Args, Mutation, Query } from '@nestjs/graphql';
import { UseGuards } from '@nestjs/common';
import { accessibleBy } from '@casl/prisma';
import { Prisma } from '@prisma/client';
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import { CommentThread } from 'src/core/@generated/comment-thread/comment-thread.model';
import { CreateOneCommentThreadArgs } from 'src/core/@generated/comment-thread/create-one-comment-thread.args';
import { FindManyCommentThreadArgs } from 'src/core/@generated/comment-thread/find-many-comment-thread.args';
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
import { UpdateOneCommentThreadArgs } from 'src/core/@generated/comment-thread/update-one-comment-thread.args';
import {
PrismaSelector,
PrismaSelect,
} from 'src/decorators/prisma-select.decorator';
import { AbilityGuard } from 'src/guards/ability.guard';
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
import {
CreateCommentThreadAbilityHandler,
DeleteCommentThreadAbilityHandler,
ReadCommentThreadAbilityHandler,
UpdateCommentThreadAbilityHandler,
} from 'src/ability/handlers/comment-thread.ability-handler';
import { UserAbility } from 'src/decorators/user-ability.decorator';
import { AppAbility } from 'src/ability/ability.factory';
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
import { DeleteManyCommentThreadArgs } from 'src/core/@generated/comment-thread/delete-many-comment-thread.args';
@UseGuards(JwtAuthGuard)
@Resolver(() => CommentThread)
export class CommentThreadResolver {
constructor(private readonly commentThreadService: CommentThreadService) {}
@Mutation(() => CommentThread, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(CreateCommentThreadAbilityHandler)
async createOneCommentThread(
@Args() args: CreateOneCommentThreadArgs,
@AuthWorkspace() workspace: Workspace,
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>> {
const createdCommentThread = await this.commentThreadService.create({
data: {
...args.data,
...{ workspace: { connect: { id: workspace.id } } },
commentThreadTargets: args.data?.commentThreadTargets?.createMany
? {
createMany: {
data: args.data.commentThreadTargets.createMany.data.map(
(target) => ({ ...target, workspaceId: workspace.id }),
),
},
}
: undefined,
},
select: prismaSelect.value,
} as Prisma.CommentThreadCreateArgs);
return createdCommentThread;
}
@Mutation(() => CommentThread, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(UpdateCommentThreadAbilityHandler)
async updateOneCommentThread(
@Args() args: UpdateOneCommentThreadArgs,
@AuthWorkspace() workspace: Workspace,
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>> {
// TODO: Do a proper check with recursion testing on args in a more generic place
for (const key in args.data) {
if (args.data[key]) {
for (const subKey in args.data[key]) {
if (JSON.stringify(args.data[key][subKey]) === '{}') {
delete args.data[key][subKey];
}
}
}
if (JSON.stringify(args.data[key]) === '{}') {
delete args.data[key];
}
}
const updatedCommentThread = await this.commentThreadService.update({
where: args.where,
data: {
...args.data,
commentThreadTargets: args.data?.commentThreadTargets?.createMany
? {
createMany: {
data: args.data.commentThreadTargets.createMany.data.map(
(target) => ({ ...target, workspaceId: workspace.id }),
),
},
}
: undefined,
},
select: prismaSelect.value,
} as Prisma.CommentThreadUpdateArgs);
return updatedCommentThread;
}
@Query(() => [CommentThread])
@UseGuards(AbilityGuard)
@CheckAbilities(ReadCommentThreadAbilityHandler)
async findManyCommentThreads(
@Args() args: FindManyCommentThreadArgs,
@UserAbility() ability: AppAbility,
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>[]> {
const result = await this.commentThreadService.findMany({
where: {
...args.where,
AND: [accessibleBy(ability).CommentThread],
},
orderBy: args.orderBy,
cursor: args.cursor,
take: args.take,
skip: args.skip,
distinct: args.distinct,
select: prismaSelect.value,
});
return result;
}
@Mutation(() => AffectedRows, {
nullable: false,
})
@UseGuards(AbilityGuard)
@CheckAbilities(DeleteCommentThreadAbilityHandler)
async deleteManyCommentThreads(
@Args() args: DeleteManyCommentThreadArgs,
): Promise<AffectedRows> {
return this.commentThreadService.deleteMany({
where: args.where,
});
}
}

View File

@ -1,41 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class CommentThreadTargetService {
constructor(private readonly prismaService: PrismaService) {}
// Find
findFirst = this.prismaService.client.commentThreadTarget.findFirst;
findFirstOrThrow =
this.prismaService.client.commentThreadTarget.findFirstOrThrow;
findUnique = this.prismaService.client.commentThreadTarget.findUnique;
findUniqueOrThrow =
this.prismaService.client.commentThreadTarget.findUniqueOrThrow;
findMany = this.prismaService.client.commentThreadTarget.findMany;
// Create
create = this.prismaService.client.commentThreadTarget.create;
createMany = this.prismaService.client.commentThreadTarget.createMany;
// Update
update = this.prismaService.client.commentThreadTarget.update;
upsert = this.prismaService.client.commentThreadTarget.upsert;
updateMany = this.prismaService.client.commentThreadTarget.updateMany;
// Delete
delete = this.prismaService.client.commentThreadTarget.delete;
deleteMany = this.prismaService.client.commentThreadTarget.deleteMany;
// Aggregate
aggregate = this.prismaService.client.commentThreadTarget.aggregate;
// Count
count = this.prismaService.client.commentThreadTarget.count;
// GroupBy
groupBy = this.prismaService.client.commentThreadTarget.groupBy;
}

View File

@ -1,39 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from 'src/database/prisma.service';
@Injectable()
export class CommentThreadService {
constructor(private readonly prismaService: PrismaService) {}
// Find
findFirst = this.prismaService.client.commentThread.findFirst;
findFirstOrThrow = this.prismaService.client.commentThread.findFirstOrThrow;
findUnique = this.prismaService.client.commentThread.findUnique;
findUniqueOrThrow = this.prismaService.client.commentThread.findUniqueOrThrow;
findMany = this.prismaService.client.commentThread.findMany;
// Create
create = this.prismaService.client.commentThread.create;
createMany = this.prismaService.client.commentThread.createMany;
// Update
update = this.prismaService.client.commentThread.update;
upsert = this.prismaService.client.commentThread.upsert;
updateMany = this.prismaService.client.commentThread.updateMany;
// Delete
delete = this.prismaService.client.commentThread.delete;
deleteMany = this.prismaService.client.commentThread.deleteMany;
// Aggregate
aggregate = this.prismaService.client.commentThread.aggregate;
// Count
count = this.prismaService.client.commentThread.count;
// GroupBy
groupBy = this.prismaService.client.commentThread.groupBy;
}

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
import { CommentService } from 'src/core/comment/services/comment.service';
import { CommentService } from 'src/core/comment/comment.service';
import { ActivityService } from 'src/core/activity/services/activity.service';
import { CompanyRelationsResolver } from './company-relations.resolver';
import { CompanyService } from './company.service';
@ -18,7 +18,7 @@ describe('CompanyRelationsResolver', () => {
useValue: {},
},
{
provide: CommentThreadService,
provide: ActivityService,
useValue: {},
},
{

View File

@ -1,36 +1,36 @@
import { Resolver, ResolveField, Root, Int } from '@nestjs/graphql';
import { CommentThread } from 'src/core/@generated/comment-thread/comment-thread.model';
import { Comment } from 'src/core/@generated/comment/comment.model';
import { Company } from 'src/core/@generated/company/company.model';
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
import { CommentService } from 'src/core/comment/services/comment.service';
import { CommentService } from 'src/core/comment/comment.service';
import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
import { ActivityService } from 'src/core/activity/services/activity.service';
import { Activity } from 'src/core/@generated/activity/activity.model';
@Resolver(() => Company)
export class CompanyRelationsResolver {
constructor(
private readonly commentThreadService: CommentThreadService,
private readonly activityService: ActivityService,
private readonly commentService: CommentService,
) {}
@ResolveField(() => [CommentThread], {
@ResolveField(() => [Activity], {
nullable: false,
})
async commentThreads(
async activities(
@Root() company: Company,
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>[]> {
return this.commentThreadService.findMany({
@PrismaSelector({ modelName: 'Activity' })
prismaSelect: PrismaSelect<'Activity'>,
): Promise<Partial<Activity>[]> {
return this.activityService.findMany({
where: {
commentThreadTargets: {
activityTargets: {
some: {
commentableId: company.id,
commentableType: 'Company',
commentableId: company.id,
},
},
},
@ -48,11 +48,11 @@ export class CompanyRelationsResolver {
): Promise<Partial<Comment>[]> {
return this.commentService.findMany({
where: {
commentThread: {
commentThreadTargets: {
activity: {
activityTargets: {
some: {
commentableId: company.id,
commentableType: 'Company',
commentableId: company.id,
},
},
},
@ -64,13 +64,13 @@ export class CompanyRelationsResolver {
@ResolveField(() => Int, {
nullable: false,
})
async _commentThreadCount(@Root() company: Company): Promise<number> {
return this.commentThreadService.count({
async _activityCount(@Root() company: Company): Promise<number> {
return this.activityService.count({
where: {
commentThreadTargets: {
activityTargets: {
some: {
commentableId: company.id,
commentableType: 'Company',
commentableId: company.id,
},
},
},

View File

@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { CommentModule } from 'src/core/comment/comment.module';
import { ActivityModule } from 'src/core/activity/activity.module';
import { CompanyService } from './company.service';
import { CompanyResolver } from './company.resolver';
import { CompanyRelationsResolver } from './company-relations.resolver';
@Module({
imports: [CommentModule],
imports: [CommentModule, ActivityModule],
providers: [CompanyService, CompanyResolver, CompanyRelationsResolver],
exports: [CompanyService],
})

View File

@ -11,6 +11,7 @@ import { AnalyticsModule } from './analytics/analytics.module';
import { FileModule } from './file/file.module';
import { ClientConfigModule } from './client-config/client-config.module';
import { AttachmentModule } from './attachment/attachment.module';
import { ActivityModule } from './activity/activity.module';
import { ViewModule } from './view/view.module';
@Module({
@ -26,6 +27,7 @@ import { ViewModule } from './view/view.module';
FileModule,
ClientConfigModule,
AttachmentModule,
ActivityModule,
ViewModule,
],
exports: [

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
import { CommentService } from 'src/core/comment/services/comment.service';
import { CommentService } from 'src/core/comment/comment.service';
import { ActivityService } from 'src/core/activity/services/activity.service';
import { PersonRelationsResolver } from './person-relations.resolver';
import { PersonService } from './person.service';
@ -18,7 +18,7 @@ describe('PersonRelationsResolver', () => {
useValue: {},
},
{
provide: CommentThreadService,
provide: ActivityService,
useValue: {},
},
{

View File

@ -1,36 +1,36 @@
import { Resolver, Root, ResolveField, Int } from '@nestjs/graphql';
import { CommentThread } from 'src/core/@generated/comment-thread/comment-thread.model';
import { Comment } from 'src/core/@generated/comment/comment.model';
import { Person } from 'src/core/@generated/person/person.model';
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
import { CommentService } from 'src/core/comment/services/comment.service';
import { CommentService } from 'src/core/comment/comment.service';
import {
PrismaSelect,
PrismaSelector,
} from 'src/decorators/prisma-select.decorator';
import { Activity } from 'src/core/@generated/activity/activity.model';
import { ActivityService } from 'src/core/activity/services/activity.service';
@Resolver(() => Person)
export class PersonRelationsResolver {
constructor(
private readonly commentThreadService: CommentThreadService,
private readonly activityService: ActivityService,
private readonly commentService: CommentService,
) {}
@ResolveField(() => [CommentThread], {
@ResolveField(() => [Activity], {
nullable: false,
})
async commentThreads(
async activities(
@Root() person: Person,
@PrismaSelector({ modelName: 'CommentThread' })
prismaSelect: PrismaSelect<'CommentThread'>,
): Promise<Partial<CommentThread>[]> {
return await this.commentThreadService.findMany({
@PrismaSelector({ modelName: 'Activity' })
prismaSelect: PrismaSelect<'Activity'>,
): Promise<Partial<Activity>[]> {
return await this.activityService.findMany({
where: {
commentThreadTargets: {
activityTargets: {
some: {
commentableId: person.id,
commentableType: 'Person',
commentableId: person.id,
},
},
},
@ -48,11 +48,11 @@ export class PersonRelationsResolver {
): Promise<Partial<Comment>[]> {
return this.commentService.findMany({
where: {
commentThread: {
commentThreadTargets: {
activity: {
activityTargets: {
some: {
commentableId: person.id,
commentableType: 'Person',
commentableId: person.id,
},
},
},
@ -64,13 +64,13 @@ export class PersonRelationsResolver {
@ResolveField(() => Int, {
nullable: false,
})
async _commentThreadCount(@Root() person: Person): Promise<number> {
return this.commentThreadService.count({
async _activityCount(@Root() person: Person): Promise<number> {
return this.activityService.count({
where: {
commentThreadTargets: {
activityTargets: {
some: {
commentableId: person.id,
commentableType: 'Person',
commentableId: person.id,
},
},
},

View File

@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { CommentModule } from 'src/core/comment/comment.module';
import { ActivityModule } from 'src/core/activity/activity.module';
import { PersonService } from './person.service';
import { PersonResolver } from './person.resolver';
import { PersonRelationsResolver } from './person-relations.resolver';
@Module({
imports: [CommentModule, CommentModule],
imports: [CommentModule, ActivityModule],
providers: [PersonService, PersonResolver, PersonRelationsResolver],
exports: [PersonService],
})

View File

@ -18,7 +18,6 @@ import { UpdateOnePersonArgs } from 'src/core/@generated/person/update-one-perso
import { CreateOnePersonArgs } from 'src/core/@generated/person/create-one-person.args';
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
import { DeleteManyPersonArgs } from 'src/core/@generated/person/delete-many-person.args';
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
import {
PrismaSelect,
@ -34,6 +33,7 @@ import {
} from 'src/ability/handlers/person.ability-handler';
import { UserAbility } from 'src/decorators/user-ability.decorator';
import { AppAbility } from 'src/ability/ability.factory';
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
import { PersonService } from './person.service';

View File

@ -109,11 +109,11 @@ export class WorkspaceService {
refreshToken,
attachment,
comment,
commentThreadTarget,
commentThread,
activityTarget,
activity,
} = this.prismaService.client;
const commentThreads = await commentThread.findMany({
const activitys = await activity.findMany({
where: { authorId: userId },
});
@ -142,12 +142,12 @@ export class WorkspaceService {
comment.deleteMany({
where,
}),
...commentThreads.map(({ id: commentThreadId }) =>
commentThreadTarget.deleteMany({
where: { commentThreadId },
...activitys.map(({ id: activityId }) =>
activityTarget.deleteMany({
where: { activityId },
}),
),
commentThread.deleteMany({
activity.deleteMany({
where,
}),
refreshToken.deleteMany({

View File

@ -1,4 +1,3 @@
-- Create the new tables first, without any foreign key constraints
-- Activities Table
CREATE TABLE "activities" (
"id" TEXT NOT NULL,

View File

@ -0,0 +1,69 @@
/*
Warnings:
- You are about to drop the `comment_thread_targets` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `comment_threads` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "attachments" DROP CONSTRAINT "attachments_activityId_fkey";
-- DropForeignKey
ALTER TABLE "comment_thread_targets" DROP CONSTRAINT "comment_thread_targets_commentThreadId_fkey";
-- DropForeignKey
ALTER TABLE "comment_thread_targets" DROP CONSTRAINT "comment_thread_targets_workspaceId_fkey";
-- DropForeignKey
ALTER TABLE "comment_threads" DROP CONSTRAINT "comment_threads_assigneeId_fkey";
-- DropForeignKey
ALTER TABLE "comment_threads" DROP CONSTRAINT "comment_threads_authorId_fkey";
-- DropForeignKey
ALTER TABLE "comment_threads" DROP CONSTRAINT "comment_threads_workspaceId_fkey";
-- DropForeignKey
ALTER TABLE "comments" DROP CONSTRAINT "comments_commentThreadId_fkey";
-- AlterTable
ALTER TABLE "activity_targets" ADD COLUMN "commentableId" TEXT,
ADD COLUMN "commentableType" "CommentableType",
ALTER COLUMN "personId" DROP NOT NULL,
ALTER COLUMN "companyId" DROP NOT NULL;
-- AlterTable
ALTER TABLE "comments" ADD COLUMN "activityId" TEXT;
-- DropTable
DROP TABLE "comment_thread_targets";
-- DropTable
DROP TABLE "comment_threads";
-- AddForeignKey
ALTER TABLE "activities" ADD CONSTRAINT "activities_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activities" ADD CONSTRAINT "activities_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activities" ADD CONSTRAINT "activities_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "comments" ADD CONSTRAINT "comments_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "activities"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activity_targets" ADD CONSTRAINT "activity_targets_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "activities"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activity_targets" ADD CONSTRAINT "activity_targets_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activity_targets" ADD CONSTRAINT "activity_targets_personId_fkey" FOREIGN KEY ("personId") REFERENCES "people"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "activity_targets" ADD CONSTRAINT "activity_targets_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "attachments" ADD CONSTRAINT "attachments_activityId_fkey" FOREIGN KEY ("activityId") REFERENCES "activities"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -20,20 +20,20 @@ generator nestgraphql {
fields_Validator_from = "class-validator"
// All relations, only allow connect
decorate_all_type = "!(CommentThreadTarget*Input|UserSettingsUpdateOneRequiredWithoutUserNestedInput)"
decorate_all_type = "!(ActivityTarget*Input|UserSettingsUpdateOneRequiredWithoutUserNestedInput)"
decorate_all_field = "*(create|connectOrCreate|update|upsert|delete|createMany|updateMany|deleteMany)"
decorate_all_name = "HideField"
decorate_all_from = "@nestjs/graphql"
decorate_all_arguments = "[]"
// CommentThread: Only Allow targets createOrConnect / createMany
decorate_commentThreadTargets_type = "*CommentThreadTarget*Input"
decorate_commentThreadTargets_field = "*(update|upsert|updateMany)"
decorate_commentThreadTargets_name = "HideField"
decorate_commentThreadTargets_from = "@nestjs/graphql"
decorate_commentThreadTargets_arguments = "[]"
// Activity: Only Allow targets createOrConnect / createMany
decorate_activityTargets_type = "*ActivityTarget*Input"
decorate_activityTargets_field = "*(update|upsert|updateMany)"
decorate_activityTargets_name = "HideField"
decorate_activityTargets_from = "@nestjs/graphql"
decorate_activityTargets_arguments = "[]"
// CommentThread: Only Allow targets createOrConnect / createMany
// User Settings: Only Allow targets createOrConnect / createMany
decorate_userSettings_type = "*UserSettingsUpdateOneRequiredWithoutUserNestedInput"
decorate_userSettings_field = "!(update)"
decorate_userSettings_name = "HideField"
@ -106,10 +106,10 @@ model User {
refreshTokens RefreshToken[]
comments Comment[]
authoredCommentThreads CommentThread[] @relation(name: "authoredCommentThreads")
assignedCommentThreads CommentThread[] @relation(name: "assignedCommentThreads")
settings UserSettings @relation(fields: [settingsId], references: [id])
settingsId String @unique
authoredActivities Activity[] @relation(name: "authoredActivities")
assignedActivities Activity[] @relation(name: "assignedActivities")
settings UserSettings @relation(fields: [settingsId], references: [id])
settingsId String @unique
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
@ -164,20 +164,20 @@ model Workspace {
workspaceMember WorkspaceMember[]
companies Company[]
people Person[]
commentThreads CommentThread[]
activities Activity[]
comments Comment[]
pipelines Pipeline[]
pipelineStages PipelineStage[]
pipelineProgresses PipelineProgress[]
activityTargets ActivityTarget[]
viewFields ViewField[]
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Attachment Attachment[]
CommentThreadTarget CommentThreadTarget[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
Attachment Attachment[]
@@map("workspaces")
}
@ -234,8 +234,9 @@ model Company {
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ActivityTarget ActivityTarget[]
@@map("companies")
}
@ -277,8 +278,9 @@ model Person {
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ActivityTarget ActivityTarget[]
@@map("people")
}
@ -311,30 +313,28 @@ enum ActivityType {
Task
}
model CommentThread {
model Activity {
/// @Validator.IsString()
/// @Validator.IsOptional()
id String @id @default(uuid())
commentThreadTargets CommentThreadTarget[]
comments Comment[]
/// @TypeGraphQL.omit(input: true, output: true)
workspace Workspace @relation(fields: [workspaceId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
workspaceId String
authorId String
author User @relation(fields: [authorId], references: [id], name: "authoredCommentThreads")
body String?
title String?
type ActivityType @default(Note)
id String @id @default(uuid())
body String?
title String?
type ActivityType @default(Note)
reminderAt DateTime?
dueAt DateTime?
completedAt DateTime?
assignee User? @relation(fields: [assigneeId], references: [id], name: "assignedCommentThreads")
assigneeId String?
activityTargets ActivityTarget[]
comments Comment[]
attachments Attachment[]
author User @relation(fields: [authorId], references: [id], name: "authoredActivities")
authorId String
assignee User? @relation(fields: [assigneeId], references: [id], name: "assignedActivities")
assigneeId String?
/// @TypeGraphQL.omit(input: true, output: true)
workspace Workspace @relation(fields: [workspaceId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
workspaceId String
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
@ -342,9 +342,12 @@ model CommentThread {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
attachments Attachment[]
@@map("activities")
}
@@map("comment_threads")
enum CommentableType {
Person
Company
}
model Comment {
@ -354,12 +357,13 @@ model Comment {
/// @Validator.IsString()
body String
author User @relation(fields: [authorId], references: [id])
author User @relation(fields: [authorId], references: [id])
authorId String
commentThread CommentThread @relation(fields: [commentThreadId], references: [id], onDelete: Cascade)
activity Activity? @relation(fields: [activityId], references: [id], onDelete: Cascade)
activityId String?
commentThreadId String
/// @TypeGraphQL.omit(input: true, output: true)
workspace Workspace @relation(fields: [workspaceId], references: [id])
workspace Workspace @relation(fields: [workspaceId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
workspaceId String
@ -372,24 +376,25 @@ model Comment {
@@map("comments")
}
enum CommentableType {
Person
Company
}
model CommentThreadTarget {
model ActivityTarget {
/// @Validator.IsString()
/// @Validator.IsOptional()
id String @id @default(uuid())
commentThread CommentThread @relation(fields: [commentThreadId], references: [id], onDelete: Cascade)
commentThreadId String
/// @TypeGraphQL.omit(input: true, output: false)
workspace Workspace? @relation(fields: [workspaceId], references: [id])
activity Activity @relation(fields: [activityId], references: [id], onDelete: Cascade)
activityId String
commentableType CommentableType?
commentableId String?
/// @TypeGraphQL.omit(input: true, output: true)
workspaceId String?
commentableType CommentableType
commentableId String
workspace Workspace @relation(fields: [workspaceId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
workspaceId String
personId String?
person Person? @relation(fields: [personId], references: [id])
companyId String?
company Company? @relation(fields: [companyId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
@ -397,7 +402,7 @@ model CommentThreadTarget {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("comment_thread_targets")
@@map("activity_targets")
}
model Pipeline {
@ -515,17 +520,16 @@ model Attachment {
type AttachmentType
name String
authorId String
author User @relation(fields: [authorId], references: [id], name: "authoredAttachments")
activityId String
activity CommentThread @relation(fields: [activityId], references: [id])
/// @TypeGraphQL.omit(input: true, output: false)
workspace Workspace @relation(fields: [workspaceId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
workspaceId String
author User @relation(fields: [authorId], references: [id], name: "authoredAttachments")
authorId String
activity Activity @relation(fields: [activityId], references: [id])
activityId String
/// @TypeGraphQL.omit(input: true, output: false)
workspace Workspace @relation(fields: [workspaceId], references: [id])
/// @TypeGraphQL.omit(input: true, output: true)
deletedAt DateTime?
createdAt DateTime @default(now())

View File

@ -1,6 +1,6 @@
import { PrismaClient } from '@prisma/client';
export const seedComments = async (prisma: PrismaClient) => {
await prisma.commentThread.upsert({
await prisma.activity.upsert({
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400' },
update: {},
create: {
@ -13,15 +13,15 @@ export const seedComments = async (prisma: PrismaClient) => {
},
});
await prisma.commentThreadTarget.upsert({
await prisma.activityTarget.upsert({
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb600' },
update: {},
create: {
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb600',
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
commentableType: 'Company',
commentableId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
activityId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
},
});
@ -32,6 +32,7 @@ export const seedComments = async (prisma: PrismaClient) => {
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb200',
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
body: 'Hi Félix ! How do you like your Twenty workspace?',
activityId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
authorId: 'twenty-ge256b39-3ec3-4fe3-8997-b76aa0bfa408',
},
@ -44,12 +45,13 @@ export const seedComments = async (prisma: PrismaClient) => {
id: 'twenty-fe256b40-3ec3-4fe3-8997-b76aa0bfb200',
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
body: 'I love it!',
activityId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
authorId: 'twenty-gk256b39-3ec3-4fe3-8997-b76aa0bfa408',
},
});
await prisma.commentThread.upsert({
await prisma.activity.upsert({
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408' },
update: {},
create: {
@ -64,15 +66,15 @@ export const seedComments = async (prisma: PrismaClient) => {
},
});
await prisma.commentThreadTarget.upsert({
await prisma.activityTarget.upsert({
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-a76aa0bfb600' },
update: {},
create: {
id: 'twenty-fe256b39-3ec3-4fe3-8997-a76aa0bfb600',
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
commentableType: 'Person',
commentableId: 'twenty-755035db-623d-41fe-92e7-dd45b7c568e1',
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
activityId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
},
});
@ -83,12 +85,13 @@ export const seedComments = async (prisma: PrismaClient) => {
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb100',
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
body: 'I really like this comment thread feature!',
activityId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
authorId: 'twenty-ge256b39-3ec3-4fe3-8997-b76aa0bfa408',
},
});
await prisma.commentThread.upsert({
await prisma.activity.upsert({
where: { id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408' },
update: {},
create: {
@ -100,15 +103,15 @@ export const seedComments = async (prisma: PrismaClient) => {
},
});
await prisma.commentThreadTarget.upsert({
await prisma.activityTarget.upsert({
where: { id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-a76aa0bfba00' },
update: {},
create: {
id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-a76aa0bfba00',
workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
commentableType: 'Company',
commentableId: 'twenty-dev-a674fa6c-1455-4c57-afaf-dd5dc086361e',
commentThreadId: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408',
activityId: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408',
workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
},
});
@ -119,6 +122,7 @@ export const seedComments = async (prisma: PrismaClient) => {
id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aa0bfb000',
workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
body: 'I really like this comment thread feature!',
activityId: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408',
commentThreadId: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408',
authorId: 'twenty-dev-gk256b39-3ec3-4fe3-8997-b76aa0boa408',
},

View File

@ -9,9 +9,9 @@ export type ModelSelectMap = {
Company: Prisma.CompanySelect;
Person: Prisma.PersonSelect;
RefreshToken: Prisma.RefreshTokenSelect;
CommentThread: Prisma.CommentThreadSelect;
Activity: Prisma.ActivitySelect;
Comment: Prisma.CommentSelect;
CommentThreadTarget: Prisma.CommentThreadTargetSelect;
ActivityTarget: Prisma.ActivityTargetSelect;
Pipeline: Prisma.PipelineSelect;
PipelineStage: Prisma.PipelineStageSelect;
PipelineProgress: Prisma.PipelineProgressSelect;