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:
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|||||||
|
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
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 { Comment } from '../Comment';
|
||||||
|
|
||||||
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
import { mockComment, mockCommentWithLongValues } from './mock-comment';
|
||||||
@ -15,7 +15,7 @@ const meta: Meta<typeof Comment> = {
|
|||||||
actionBar: {
|
actionBar: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
mapping: {
|
mapping: {
|
||||||
true: <CommentThreadActionBar commentThreadId="test-id" />,
|
true: <ActivityActionBar activityId="test-id" />,
|
||||||
false: undefined,
|
false: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react';
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
import { DateTime } from 'luxon';
|
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 { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
import { avatarUrl } from '~/testing/mock-data/users';
|
import { avatarUrl } from '~/testing/mock-data/users';
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ const meta: Meta<typeof CommentHeader> = {
|
|||||||
actionBar: {
|
actionBar: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
mapping: {
|
mapping: {
|
||||||
true: <CommentThreadActionBar commentThreadId="test-id" />,
|
true: <ActivityActionBar activityId="test-id" />,
|
||||||
false: undefined,
|
false: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,24 +6,21 @@ import styled from '@emotion/styled';
|
|||||||
import debounce from 'lodash.debounce';
|
import debounce from 'lodash.debounce';
|
||||||
|
|
||||||
import { BlockEditor } from '@/ui/editor/components/BlockEditor';
|
import { BlockEditor } from '@/ui/editor/components/BlockEditor';
|
||||||
import {
|
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||||
CommentThread,
|
|
||||||
useUpdateCommentThreadMutation,
|
|
||||||
} from '~/generated/graphql';
|
|
||||||
|
|
||||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '../queries/select';
|
import { GET_ACTIVITIES_BY_TARGETS } from '../queries/select';
|
||||||
|
|
||||||
const BlockNoteStyledContainer = styled.div`
|
const BlockNoteStyledContainer = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThread: Pick<CommentThread, 'id' | 'body'>;
|
activity: Pick<Activity, 'id' | 'body'>;
|
||||||
onChange?: (commentThreadBody: string) => void;
|
onChange?: (activityBody: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentThreadBodyEditor({ commentThread, onChange }: OwnProps) {
|
export function ActivityBodyEditor({ activity, onChange }: OwnProps) {
|
||||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||||
|
|
||||||
const [body, setBody] = useState<string | null>(null);
|
const [body, setBody] = useState<string | null>(null);
|
||||||
|
|
||||||
@ -34,26 +31,22 @@ export function CommentThreadBodyEditor({ commentThread, onChange }: OwnProps) {
|
|||||||
}, [body, onChange]);
|
}, [body, onChange]);
|
||||||
|
|
||||||
const debounceOnChange = useMemo(() => {
|
const debounceOnChange = useMemo(() => {
|
||||||
function onInternalChange(commentThreadBody: string) {
|
function onInternalChange(activityBody: string) {
|
||||||
setBody(commentThreadBody);
|
setBody(activityBody);
|
||||||
updateCommentThreadMutation({
|
updateActivityMutation({
|
||||||
variables: {
|
variables: {
|
||||||
id: commentThread.id,
|
id: activity.id,
|
||||||
body: commentThreadBody,
|
body: activityBody,
|
||||||
},
|
},
|
||||||
refetchQueries: [
|
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return debounce(onInternalChange, 200);
|
return debounce(onInternalChange, 200);
|
||||||
}, [commentThread, updateCommentThreadMutation, setBody]);
|
}, [activity, updateActivityMutation, setBody]);
|
||||||
|
|
||||||
const editor: BlockNoteEditor | null = useBlockNote({
|
const editor: BlockNoteEditor | null = useBlockNote({
|
||||||
initialContent: commentThread.body
|
initialContent: activity.body ? JSON.parse(activity.body) : undefined,
|
||||||
? JSON.parse(commentThread.body)
|
|
||||||
: undefined,
|
|
||||||
editorDOMAttributes: { class: 'editor' },
|
editorDOMAttributes: { class: 'editor' },
|
||||||
onEditorContentChange: (editor) => {
|
onEditorContentChange: (editor) => {
|
||||||
debounceOnChange(JSON.stringify(editor.topLevelBlocks) ?? '');
|
debounceOnChange(JSON.stringify(editor.topLevelBlocks) ?? '');
|
||||||
@ -6,15 +6,15 @@ import { v4 } from 'uuid';
|
|||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||||
import { AutosizeTextInput } from '@/ui/input/components/AutosizeTextInput';
|
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 { isNonEmptyString } from '~/utils/isNonEmptyString';
|
||||||
|
|
||||||
import { Comment } from '../comment/Comment';
|
import { Comment } from '../comment/Comment';
|
||||||
import { GET_COMMENT_THREAD } from '../queries';
|
import { GET_ACTIVITY } from '../queries';
|
||||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThread: Pick<CommentThread, 'id'> & {
|
activity: Pick<Activity, 'id'> & {
|
||||||
comments: Array<CommentForDrawer>;
|
comments: Array<CommentForDrawer>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -52,7 +52,7 @@ const StyledThreadCommentTitle = styled.div`
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function CommentThreadComments({ commentThread }: OwnProps) {
|
export function ActivityComments({ activity }: OwnProps) {
|
||||||
const [createCommentMutation] = useCreateCommentMutation();
|
const [createCommentMutation] = useCreateCommentMutation();
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
|
|
||||||
@ -69,21 +69,21 @@ export function CommentThreadComments({ commentThread }: OwnProps) {
|
|||||||
variables: {
|
variables: {
|
||||||
commentId: v4(),
|
commentId: v4(),
|
||||||
authorId: currentUser?.id ?? '',
|
authorId: currentUser?.id ?? '',
|
||||||
commentThreadId: commentThread?.id ?? '',
|
activityId: activity?.id ?? '',
|
||||||
commentText: commentText,
|
commentText: commentText,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
refetchQueries: [getOperationName(GET_COMMENT_THREAD) ?? ''],
|
refetchQueries: [getOperationName(GET_ACTIVITY) ?? ''],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{commentThread?.comments.length > 0 && (
|
{activity?.comments.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<StyledThreadItemListContainer>
|
<StyledThreadItemListContainer>
|
||||||
<StyledThreadCommentTitle>Comments</StyledThreadCommentTitle>
|
<StyledThreadCommentTitle>Comments</StyledThreadCommentTitle>
|
||||||
{commentThread?.comments?.map((comment) => (
|
{activity?.comments?.map((comment) => (
|
||||||
<Comment key={comment.id} comment={comment} />
|
<Comment key={comment.id} comment={comment} />
|
||||||
))}
|
))}
|
||||||
</StyledThreadItemListContainer>
|
</StyledThreadItemListContainer>
|
||||||
@ -4,17 +4,17 @@ import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
|||||||
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
|
||||||
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon/index';
|
import { IconCheckbox, IconNotes, IconTimelineEvent } from '@/ui/icon/index';
|
||||||
|
|
||||||
type CommentThreadCreateButtonProps = {
|
type ActivityCreateButtonProps = {
|
||||||
onNoteClick?: () => void;
|
onNoteClick?: () => void;
|
||||||
onTaskClick?: () => void;
|
onTaskClick?: () => void;
|
||||||
onActivityClick?: () => void;
|
onActivityClick?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentThreadCreateButton({
|
export function ActivityCreateButton({
|
||||||
onNoteClick,
|
onNoteClick,
|
||||||
onTaskClick,
|
onTaskClick,
|
||||||
onActivityClick,
|
onActivityClick,
|
||||||
}: CommentThreadCreateButtonProps) {
|
}: ActivityCreateButtonProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<ButtonGroup variant={ButtonVariant.Secondary}>
|
<ButtonGroup variant={ButtonVariant.Secondary}>
|
||||||
@ -2,26 +2,26 @@ import React, { useCallback, useState } from 'react';
|
|||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { CommentThreadBodyEditor } from '@/activities/components/CommentThreadBodyEditor';
|
import { ActivityBodyEditor } from '@/activities/components/ActivityBodyEditor';
|
||||||
import { CommentThreadComments } from '@/activities/components/CommentThreadComments';
|
import { ActivityComments } from '@/activities/components/ActivityComments';
|
||||||
import { CommentThreadRelationPicker } from '@/activities/components/CommentThreadRelationPicker';
|
import { ActivityRelationPicker } from '@/activities/components/ActivityRelationPicker';
|
||||||
import { CommentThreadTypeDropdown } from '@/activities/components/CommentThreadTypeDropdown';
|
import { ActivityTypeDropdown } from '@/activities/components/ActivityTypeDropdown';
|
||||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
|
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
|
||||||
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
|
||||||
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
import { PropertyBoxItem } from '@/ui/editable-field/property-box/components/PropertyBoxItem';
|
||||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||||
import { IconArrowUpRight } from '@/ui/icon/index';
|
import { IconArrowUpRight } from '@/ui/icon/index';
|
||||||
import {
|
import {
|
||||||
CommentThread,
|
Activity,
|
||||||
CommentThreadTarget,
|
ActivityTarget,
|
||||||
useUpdateCommentThreadMutation,
|
useUpdateActivityMutation,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import { debounce } from '~/utils/debounce';
|
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 { CommentForDrawer } from '../types/CommentForDrawer';
|
||||||
|
|
||||||
import { CommentThreadTitle } from './CommentThreadTitle';
|
import { ActivityTitle } from './ActivityTitle';
|
||||||
|
|
||||||
import '@blocknote/core/style.css';
|
import '@blocknote/core/style.css';
|
||||||
|
|
||||||
@ -65,64 +65,57 @@ const StyledTopActionsContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThread: Pick<
|
activity: Pick<Activity, 'id' | 'title' | 'body' | 'type' | 'completedAt'> & {
|
||||||
CommentThread,
|
|
||||||
'id' | 'title' | 'body' | 'type' | 'completedAt'
|
|
||||||
> & {
|
|
||||||
comments?: Array<CommentForDrawer> | null;
|
comments?: Array<CommentForDrawer> | null;
|
||||||
} & {
|
} & {
|
||||||
commentThreadTargets?: Array<
|
activityTargets?: Array<
|
||||||
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
|
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||||
> | null;
|
> | null;
|
||||||
};
|
};
|
||||||
showComment?: boolean;
|
showComment?: boolean;
|
||||||
autoFillTitle?: boolean;
|
autoFillTitle?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentThreadEditor({
|
export function ActivityEditor({
|
||||||
commentThread,
|
activity,
|
||||||
showComment = true,
|
showComment = true,
|
||||||
autoFillTitle = false,
|
autoFillTitle = false,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
|
const [hasUserManuallySetTitle, setHasUserManuallySetTitle] =
|
||||||
useState<boolean>(false);
|
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>(
|
const [completedAt, setCompletedAt] = useState<string | null>(
|
||||||
commentThread.completedAt ?? '',
|
activity.completedAt ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||||
|
|
||||||
const updateTitle = useCallback(
|
const updateTitle = useCallback(
|
||||||
(newTitle: string) => {
|
(newTitle: string) => {
|
||||||
updateCommentThreadMutation({
|
updateActivityMutation({
|
||||||
variables: {
|
variables: {
|
||||||
id: commentThread.id,
|
id: activity.id,
|
||||||
title: newTitle ?? '',
|
title: newTitle ?? '',
|
||||||
},
|
},
|
||||||
refetchQueries: [
|
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[commentThread, updateCommentThreadMutation],
|
[activity, updateActivityMutation],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleActivityCompletionChange = useCallback(
|
const handleActivityCompletionChange = useCallback(
|
||||||
(value: boolean) => {
|
(value: boolean) => {
|
||||||
updateCommentThreadMutation({
|
updateActivityMutation({
|
||||||
variables: {
|
variables: {
|
||||||
id: commentThread.id,
|
id: activity.id,
|
||||||
completedAt: value ? new Date().toISOString() : null,
|
completedAt: value ? new Date().toISOString() : null,
|
||||||
},
|
},
|
||||||
refetchQueries: [
|
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
setCompletedAt(value ? new Date().toISOString() : null);
|
setCompletedAt(value ? new Date().toISOString() : null);
|
||||||
},
|
},
|
||||||
[commentThread, updateCommentThreadMutation],
|
[activity, updateActivityMutation],
|
||||||
);
|
);
|
||||||
|
|
||||||
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
||||||
@ -135,7 +128,7 @@ export function CommentThreadEditor({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!commentThread) {
|
if (!activity) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,13 +137,13 @@ export function CommentThreadEditor({
|
|||||||
<StyledUpperPartContainer>
|
<StyledUpperPartContainer>
|
||||||
<StyledTopContainer>
|
<StyledTopContainer>
|
||||||
<StyledTopActionsContainer>
|
<StyledTopActionsContainer>
|
||||||
<CommentThreadTypeDropdown commentThread={commentThread} />
|
<ActivityTypeDropdown activity={activity} />
|
||||||
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
|
<ActivityActionBar activityId={activity?.id ?? ''} />
|
||||||
</StyledTopActionsContainer>
|
</StyledTopActionsContainer>
|
||||||
<CommentThreadTitle
|
<ActivityTitle
|
||||||
title={title ?? ''}
|
title={title ?? ''}
|
||||||
completed={!!completedAt}
|
completed={!!completedAt}
|
||||||
type={commentThread.type}
|
type={activity.type}
|
||||||
onTitleChange={(newTitle) => {
|
onTitleChange={(newTitle) => {
|
||||||
setTitle(newTitle);
|
setTitle(newTitle);
|
||||||
setHasUserManuallySetTitle(true);
|
setHasUserManuallySetTitle(true);
|
||||||
@ -162,11 +155,10 @@ export function CommentThreadEditor({
|
|||||||
<PropertyBoxItem
|
<PropertyBoxItem
|
||||||
icon={<IconArrowUpRight />}
|
icon={<IconArrowUpRight />}
|
||||||
value={
|
value={
|
||||||
<CommentThreadRelationPicker
|
<ActivityRelationPicker
|
||||||
commentThread={{
|
activity={{
|
||||||
id: commentThread.id,
|
id: activity.id,
|
||||||
commentThreadTargets:
|
activityTargets: activity.activityTargets ?? [],
|
||||||
commentThread.commentThreadTargets ?? [],
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -174,16 +166,16 @@ export function CommentThreadEditor({
|
|||||||
/>
|
/>
|
||||||
</PropertyBox>
|
</PropertyBox>
|
||||||
</StyledTopContainer>
|
</StyledTopContainer>
|
||||||
<CommentThreadBodyEditor
|
<ActivityBodyEditor
|
||||||
commentThread={commentThread}
|
activity={activity}
|
||||||
onChange={updateTitleFromBody}
|
onChange={updateTitleFromBody}
|
||||||
/>
|
/>
|
||||||
</StyledUpperPartContainer>
|
</StyledUpperPartContainer>
|
||||||
{showComment && (
|
{showComment && (
|
||||||
<CommentThreadComments
|
<ActivityComments
|
||||||
commentThread={{
|
activity={{
|
||||||
id: commentThread.id,
|
id: activity.id,
|
||||||
comments: commentThread.comments ?? [],
|
comments: activity.comments ?? [],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -18,19 +18,15 @@ import { useScopedHotkeys } from '@/ui/hotkey/hooks/useScopedHotkeys';
|
|||||||
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/recoil-scope/components/RecoilScope';
|
||||||
import { MultipleEntitySelect } from '@/ui/relation-picker/components/MultipleEntitySelect';
|
import { MultipleEntitySelect } from '@/ui/relation-picker/components/MultipleEntitySelect';
|
||||||
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
|
import { RelationPickerHotkeyScope } from '@/ui/relation-picker/types/RelationPickerHotkeyScope';
|
||||||
import {
|
import { Activity, ActivityTarget, CommentableType } from '~/generated/graphql';
|
||||||
CommentableType,
|
|
||||||
CommentThread,
|
|
||||||
CommentThreadTarget,
|
|
||||||
} from '~/generated/graphql';
|
|
||||||
|
|
||||||
import { useHandleCheckableCommentThreadTargetChange } from '../hooks/useHandleCheckableCommentThreadTargetChange';
|
import { useHandleCheckableActivityTargetChange } from '../hooks/useHandleCheckableActivityTargetChange';
|
||||||
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '../utils/flatMapAndSortEntityForSelectArrayByName';
|
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '../utils/flatMapAndSortEntityForSelectArrayByName';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThread?: Pick<CommentThread, 'id'> & {
|
activity?: Pick<Activity, 'id'> & {
|
||||||
commentThreadTargets: Array<
|
activityTargets: Array<
|
||||||
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
|
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -75,7 +71,7 @@ const StyledMenuWrapper = styled.div`
|
|||||||
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
z-index: ${({ theme }) => theme.lastLayerZIndex};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
export function ActivityRelationPicker({ activity }: OwnProps) {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [searchFilter, setSearchFilter] = useState('');
|
const [searchFilter, setSearchFilter] = useState('');
|
||||||
const [selectedEntityIds, setSelectedEntityIds] = useState<
|
const [selectedEntityIds, setSelectedEntityIds] = useState<
|
||||||
@ -88,17 +84,18 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
|||||||
|
|
||||||
const initialPeopleIds = useMemo(
|
const initialPeopleIds = useMemo(
|
||||||
() =>
|
() =>
|
||||||
commentThread?.commentThreadTargets
|
activity?.activityTargets
|
||||||
?.filter((relation) => relation.commentableType === 'Person')
|
?.filter((relation) => relation.commentableType === 'Person')
|
||||||
.map((relation) => relation.commentableId) ?? [],
|
.map((relation) => relation.commentableId) ?? [],
|
||||||
[commentThread?.commentThreadTargets],
|
[activity?.activityTargets],
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialCompanyIds = useMemo(
|
const initialCompanyIds = useMemo(
|
||||||
() =>
|
() =>
|
||||||
commentThread?.commentThreadTargets
|
activity?.activityTargets
|
||||||
?.filter((relation) => relation.commentableType === 'Company')
|
?.filter((relation) => relation.commentableType === 'Company')
|
||||||
.map((relation) => relation.commentableId) ?? [],
|
.map((relation) => relation.commentableId) ?? [],
|
||||||
[commentThread?.commentThreadTargets],
|
[activity?.activityTargets],
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialSelectedEntityIds = useMemo(
|
const initialSelectedEntityIds = useMemo(
|
||||||
@ -135,8 +132,8 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
|||||||
companiesForMultiSelect.entitiesToSelect,
|
companiesForMultiSelect.entitiesToSelect,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleCheckItemsChange = useHandleCheckableCommentThreadTargetChange({
|
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
|
||||||
commentThread,
|
activity,
|
||||||
});
|
});
|
||||||
|
|
||||||
const exitEditMode = useCallback(() => {
|
const exitEditMode = useCallback(() => {
|
||||||
@ -51,7 +51,7 @@ type OwnProps = {
|
|||||||
onCompletionChange: (value: boolean) => void;
|
onCompletionChange: (value: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentThreadTitle({
|
export function ActivityTitle({
|
||||||
title,
|
title,
|
||||||
completed,
|
completed,
|
||||||
type,
|
type,
|
||||||
@ -7,17 +7,17 @@ import {
|
|||||||
ChipVariant,
|
ChipVariant,
|
||||||
} from '@/ui/chip/components/Chip';
|
} from '@/ui/chip/components/Chip';
|
||||||
import { IconPhone } from '@/ui/icon';
|
import { IconPhone } from '@/ui/icon';
|
||||||
import { CommentThread } from '~/generated/graphql';
|
import { Activity } from '~/generated/graphql';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThread: Pick<CommentThread, 'type'>;
|
activity: Pick<Activity, 'type'>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentThreadTypeDropdown({ commentThread }: OwnProps) {
|
export function ActivityTypeDropdown({ activity }: OwnProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
return (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
label={commentThread.type}
|
label={activity.type}
|
||||||
leftComponent={<IconPhone size={theme.icon.size.md} />}
|
leftComponent={<IconPhone size={theme.icon.size.md} />}
|
||||||
size={ChipSize.Large}
|
size={ChipSize.Large}
|
||||||
accent={ChipAccent.TextSecondary}
|
accent={ChipAccent.TextSecondary}
|
||||||
@ -4,17 +4,17 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|||||||
|
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
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`
|
const StyledContainer = styled.div`
|
||||||
width: 400px;
|
width: 400px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const meta: Meta<typeof CommentThreadRelationPicker> = {
|
const meta: Meta<typeof ActivityRelationPicker> = {
|
||||||
title: 'Modules/Comments/CommentThreadRelationPicker',
|
title: 'Modules/Comments/ActivityRelationPicker',
|
||||||
component: CommentThreadRelationPicker,
|
component: ActivityRelationPicker,
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
@ -25,13 +25,13 @@ const meta: Meta<typeof CommentThreadRelationPicker> = {
|
|||||||
),
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
],
|
],
|
||||||
args: { commentThread: mockedCommentThreads[0] },
|
args: { activity: mockedActivities[0] },
|
||||||
parameters: {
|
parameters: {
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof CommentThreadRelationPicker>;
|
type Story = StoryObj<typeof ActivityRelationPicker>;
|
||||||
|
|
||||||
export const Default: Story = {};
|
export const Default: Story = {};
|
||||||
@ -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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -5,18 +5,16 @@ import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
|
|||||||
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
|
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
|
||||||
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
|
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 { openRightDrawer } = useRightDrawer();
|
||||||
const [, setViewableCommentThreadId] = useRecoilState(
|
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||||
viewableCommentThreadIdState,
|
|
||||||
);
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
return function openCommentThreadRightDrawer(commentThreadId: string) {
|
return function openActivityRightDrawer(activityId: string) {
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
setViewableCommentThreadId(commentThreadId);
|
setViewableActivityId(activityId);
|
||||||
openRightDrawer(RightDrawerPages.EditCommentThread);
|
openRightDrawer(RightDrawerPages.EditActivity);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -9,40 +9,35 @@ import { useSetHotkeyScope } from '@/ui/hotkey/hooks/useSetHotkeyScope';
|
|||||||
import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
|
import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
|
||||||
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
|
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
|
||||||
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
|
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
|
||||||
import {
|
import { ActivityType, useCreateActivityMutation } from '~/generated/graphql';
|
||||||
ActivityType,
|
|
||||||
useCreateCommentThreadMutation,
|
|
||||||
} 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 { commentableEntityArrayState } from '../states/commentableEntityArrayState';
|
||||||
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
|
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||||
import { CommentableEntity } from '../types/CommentableEntity';
|
import { CommentableEntity } from '../types/CommentableEntity';
|
||||||
|
|
||||||
export function useOpenCreateCommentThreadDrawer() {
|
export function useOpenCreateActivityDrawer() {
|
||||||
const { openRightDrawer } = useRightDrawer();
|
const { openRightDrawer } = useRightDrawer();
|
||||||
const [createCommentThreadMutation] = useCreateCommentThreadMutation();
|
const [createActivityMutation] = useCreateActivityMutation();
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
const [, setCommentableEntityArray] = useRecoilState(
|
const [, setCommentableEntityArray] = useRecoilState(
|
||||||
commentableEntityArrayState,
|
commentableEntityArrayState,
|
||||||
);
|
);
|
||||||
const [, setViewableCommentThreadId] = useRecoilState(
|
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||||
viewableCommentThreadIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
return function openCreateCommentThreadDrawer(
|
return function openCreateActivityDrawer(
|
||||||
entity: CommentableEntity,
|
entity: CommentableEntity,
|
||||||
type: ActivityType,
|
type: ActivityType,
|
||||||
) {
|
) {
|
||||||
createCommentThreadMutation({
|
createActivityMutation({
|
||||||
variables: {
|
variables: {
|
||||||
authorId: currentUser?.id ?? '',
|
authorId: currentUser?.id ?? '',
|
||||||
commentThreadId: v4(),
|
activityId: v4(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
type: type,
|
type: type,
|
||||||
commentThreadTargetArray: [
|
activityTargetArray: [
|
||||||
{
|
{
|
||||||
commentableId: entity.id,
|
commentableId: entity.id,
|
||||||
commentableType: entity.type,
|
commentableType: entity.type,
|
||||||
@ -54,14 +49,14 @@ export function useOpenCreateCommentThreadDrawer() {
|
|||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
getOperationName(GET_COMPANIES) ?? '',
|
getOperationName(GET_COMPANIES) ?? '',
|
||||||
getOperationName(GET_PEOPLE) ?? '',
|
getOperationName(GET_PEOPLE) ?? '',
|
||||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
getOperationName(GET_ACTIVITY) ?? '',
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||||
],
|
],
|
||||||
onCompleted(data) {
|
onCompleted(data) {
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
setViewableCommentThreadId(data.createOneCommentThread.id);
|
setViewableActivityId(data.createOneActivity.id);
|
||||||
setCommentableEntityArray([entity]);
|
setCommentableEntityArray([entity]);
|
||||||
openRightDrawer(RightDrawerPages.CreateCommentThread);
|
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -13,21 +13,19 @@ import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector
|
|||||||
import {
|
import {
|
||||||
ActivityType,
|
ActivityType,
|
||||||
CommentableType,
|
CommentableType,
|
||||||
useCreateCommentThreadMutation,
|
useCreateActivityMutation,
|
||||||
} from '~/generated/graphql';
|
} 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 { commentableEntityArrayState } from '../states/commentableEntityArrayState';
|
||||||
import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState';
|
import { viewableActivityIdState } from '../states/viewableActivityIdState';
|
||||||
import { CommentableEntity } from '../types/CommentableEntity';
|
import { CommentableEntity } from '../types/CommentableEntity';
|
||||||
|
|
||||||
export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
|
export function useOpenCreateActivityDrawerForSelectedRowIds() {
|
||||||
const { openRightDrawer } = useRightDrawer();
|
const { openRightDrawer } = useRightDrawer();
|
||||||
const [createCommentThreadMutation] = useCreateCommentThreadMutation();
|
const [createActivityMutation] = useCreateActivityMutation();
|
||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
const [, setViewableCommentThreadId] = useRecoilState(
|
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
|
||||||
viewableCommentThreadIdState,
|
|
||||||
);
|
|
||||||
|
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
@ -47,13 +45,13 @@ export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
createCommentThreadMutation({
|
createActivityMutation({
|
||||||
variables: {
|
variables: {
|
||||||
authorId: currentUser?.id ?? '',
|
authorId: currentUser?.id ?? '',
|
||||||
commentThreadId: v4(),
|
activityId: v4(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
type: ActivityType.Note,
|
type: ActivityType.Note,
|
||||||
commentThreadTargetArray: commentableEntityArray.map((entity) => ({
|
activityTargetArray: commentableEntityArray.map((entity) => ({
|
||||||
commentableId: entity.id,
|
commentableId: entity.id,
|
||||||
commentableType: entity.type,
|
commentableType: entity.type,
|
||||||
id: v4(),
|
id: v4(),
|
||||||
@ -63,14 +61,14 @@ export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
|
|||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
getOperationName(GET_COMPANIES) ?? '',
|
getOperationName(GET_COMPANIES) ?? '',
|
||||||
getOperationName(GET_PEOPLE) ?? '',
|
getOperationName(GET_PEOPLE) ?? '',
|
||||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
getOperationName(GET_ACTIVITY) ?? '',
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||||
],
|
],
|
||||||
onCompleted(data) {
|
onCompleted(data) {
|
||||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||||
setViewableCommentThreadId(data.createOneCommentThread.id);
|
setViewableActivityId(data.createOneActivity.id);
|
||||||
setCommentableEntityArray(commentableEntityArray);
|
setCommentableEntityArray(commentableEntityArray);
|
||||||
openRightDrawer(RightDrawerPages.CreateCommentThread);
|
openRightDrawer(RightDrawerPages.CreateActivity);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -5,7 +5,7 @@ export const CREATE_COMMENT = gql`
|
|||||||
$commentId: String!
|
$commentId: String!
|
||||||
$commentText: String!
|
$commentText: String!
|
||||||
$authorId: String!
|
$authorId: String!
|
||||||
$commentThreadId: String!
|
$activityId: String!
|
||||||
$createdAt: DateTime!
|
$createdAt: DateTime!
|
||||||
) {
|
) {
|
||||||
createOneComment(
|
createOneComment(
|
||||||
@ -14,7 +14,7 @@ export const CREATE_COMMENT = gql`
|
|||||||
createdAt: $createdAt
|
createdAt: $createdAt
|
||||||
body: $commentText
|
body: $commentText
|
||||||
author: { connect: { id: $authorId } }
|
author: { connect: { id: $authorId } }
|
||||||
commentThread: { connect: { id: $commentThreadId } }
|
activity: { connect: { id: $activityId } }
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
@ -27,32 +27,32 @@ export const CREATE_COMMENT = gql`
|
|||||||
lastName
|
lastName
|
||||||
avatarUrl
|
avatarUrl
|
||||||
}
|
}
|
||||||
commentThreadId
|
activityId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql`
|
export const CREATE_ACTIVITY_WITH_COMMENT = gql`
|
||||||
mutation CreateCommentThread(
|
mutation CreateActivity(
|
||||||
$commentThreadId: String!
|
$activityId: String!
|
||||||
$body: String
|
$body: String
|
||||||
$title: String
|
$title: String
|
||||||
$type: ActivityType!
|
$type: ActivityType!
|
||||||
$authorId: String!
|
$authorId: String!
|
||||||
$createdAt: DateTime!
|
$createdAt: DateTime!
|
||||||
$commentThreadTargetArray: [CommentThreadTargetCreateManyCommentThreadInput!]!
|
$activityTargetArray: [ActivityTargetCreateManyActivityInput!]!
|
||||||
) {
|
) {
|
||||||
createOneCommentThread(
|
createOneActivity(
|
||||||
data: {
|
data: {
|
||||||
id: $commentThreadId
|
id: $activityId
|
||||||
createdAt: $createdAt
|
createdAt: $createdAt
|
||||||
updatedAt: $createdAt
|
updatedAt: $createdAt
|
||||||
author: { connect: { id: $authorId } }
|
author: { connect: { id: $authorId } }
|
||||||
body: $body
|
body: $body
|
||||||
title: $title
|
title: $title
|
||||||
type: $type
|
type: $type
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
createMany: { data: $commentThreadTargetArray, skipDuplicates: true }
|
createMany: { data: $activityTargetArray, skipDuplicates: true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@ -61,11 +61,11 @@ export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql`
|
|||||||
updatedAt
|
updatedAt
|
||||||
authorId
|
authorId
|
||||||
type
|
type
|
||||||
commentThreadTargets {
|
activityTargets {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
commentThreadId
|
activityId
|
||||||
commentableType
|
commentableType
|
||||||
commentableId
|
commentableId
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const GET_COMMENT_THREADS_BY_TARGETS = gql`
|
export const GET_ACTIVITIES_BY_TARGETS = gql`
|
||||||
query GetCommentThreadsByTargets(
|
query GetActivitiesByTargets(
|
||||||
$commentThreadTargetIds: [String!]!
|
$activityTargetIds: [String!]!
|
||||||
$orderBy: [CommentThreadOrderByWithRelationInput!]
|
$orderBy: [ActivityOrderByWithRelationInput!]
|
||||||
) {
|
) {
|
||||||
findManyCommentThreads(
|
findManyActivities(
|
||||||
orderBy: $orderBy
|
orderBy: $orderBy
|
||||||
where: {
|
where: {
|
||||||
commentThreadTargets: {
|
activityTargets: { some: { commentableId: { in: $activityTargetIds } } }
|
||||||
some: { commentableId: { in: $commentThreadTargetIds } }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
@ -38,18 +36,18 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql`
|
|||||||
avatarUrl
|
avatarUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commentThreadTargets {
|
activityTargets {
|
||||||
id
|
id
|
||||||
commentableId
|
|
||||||
commentableType
|
commentableType
|
||||||
|
commentableId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GET_COMMENT_THREAD = gql`
|
export const GET_ACTIVITY = gql`
|
||||||
query GetCommentThread($commentThreadId: String!) {
|
query GetActivity($activityId: String!) {
|
||||||
findManyCommentThreads(where: { id: { equals: $commentThreadId } }) {
|
findManyActivities(where: { id: { equals: $activityId } }) {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
body
|
body
|
||||||
@ -75,10 +73,10 @@ export const GET_COMMENT_THREAD = gql`
|
|||||||
avatarUrl
|
avatarUrl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commentThreadTargets {
|
activityTargets {
|
||||||
id
|
id
|
||||||
commentableId
|
|
||||||
commentableType
|
commentableType
|
||||||
|
commentableId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,18 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const ADD_COMMENT_THREAD_TARGETS = gql`
|
export const ADD_ACTIVITY_TARGETS = gql`
|
||||||
mutation AddCommentThreadTargetsOnCommentThread(
|
mutation AddActivityTargetsOnActivity(
|
||||||
$commentThreadId: String!
|
$activityId: String!
|
||||||
$commentThreadTargetInputs: [CommentThreadTargetCreateManyCommentThreadInput!]!
|
$activityTargetInputs: [ActivityTargetCreateManyActivityInput!]!
|
||||||
) {
|
) {
|
||||||
updateOneCommentThread(
|
updateOneActivity(
|
||||||
where: { id: $commentThreadId }
|
where: { id: $activityId }
|
||||||
data: {
|
data: { activityTargets: { createMany: { data: $activityTargetInputs } } }
|
||||||
commentThreadTargets: {
|
|
||||||
createMany: { data: $commentThreadTargetInputs }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
commentThreadTargets {
|
activityTargets {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
@ -27,23 +23,21 @@ export const ADD_COMMENT_THREAD_TARGETS = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const REMOVE_COMMENT_THREAD_TARGETS = gql`
|
export const REMOVE_ACTIVITY_TARGETS = gql`
|
||||||
mutation RemoveCommentThreadTargetsOnCommentThread(
|
mutation RemoveActivityTargetsOnActivity(
|
||||||
$commentThreadId: String!
|
$activityId: String!
|
||||||
$commentThreadTargetIds: [String!]!
|
$activityTargetIds: [String!]!
|
||||||
) {
|
) {
|
||||||
updateOneCommentThread(
|
updateOneActivity(
|
||||||
where: { id: $commentThreadId }
|
where: { id: $activityId }
|
||||||
data: {
|
data: {
|
||||||
commentThreadTargets: {
|
activityTargets: { deleteMany: { id: { in: $activityTargetIds } } }
|
||||||
deleteMany: { id: { in: $commentThreadTargetIds } }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
commentThreadTargets {
|
activityTargets {
|
||||||
id
|
id
|
||||||
createdAt
|
createdAt
|
||||||
updatedAt
|
updatedAt
|
||||||
@ -54,23 +48,23 @@ export const REMOVE_COMMENT_THREAD_TARGETS = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DELETE_COMMENT_THREAD = gql`
|
export const DELETE_ACTIVITY = gql`
|
||||||
mutation DeleteCommentThread($commentThreadId: String!) {
|
mutation DeleteActivity($activityId: String!) {
|
||||||
deleteManyCommentThreads(where: { id: { equals: $commentThreadId } }) {
|
deleteManyActivities(where: { id: { equals: $activityId } }) {
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const UPDATE_COMMENT_THREAD = gql`
|
export const UPDATE_ACTIVITY = gql`
|
||||||
mutation UpdateCommentThread(
|
mutation UpdateActivity(
|
||||||
$id: String!
|
$id: String!
|
||||||
$body: String
|
$body: String
|
||||||
$title: String
|
$title: String
|
||||||
$type: ActivityType
|
$type: ActivityType
|
||||||
$completedAt: DateTime
|
$completedAt: DateTime
|
||||||
) {
|
) {
|
||||||
updateOneCommentThread(
|
updateOneActivity(
|
||||||
where: { id: $id }
|
where: { id: $id }
|
||||||
data: {
|
data: {
|
||||||
body: $body
|
body: $body
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -3,13 +3,13 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
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_COMPANIES } from '@/companies/queries';
|
||||||
import { GET_PEOPLE } from '@/people/queries';
|
import { GET_PEOPLE } from '@/people/queries';
|
||||||
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
import { Button, ButtonVariant } from '@/ui/button/components/Button';
|
||||||
import { IconTrash } from '@/ui/icon';
|
import { IconTrash } from '@/ui/icon';
|
||||||
import { isRightDrawerOpenState } from '@/ui/right-drawer/states/isRightDrawerOpenState';
|
import { isRightDrawerOpenState } from '@/ui/right-drawer/states/isRightDrawerOpenState';
|
||||||
import { useDeleteCommentThreadMutation } from '~/generated/graphql';
|
import { useDeleteActivityMutation } from '~/generated/graphql';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
@ -17,21 +17,21 @@ const StyledContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThreadId: string;
|
activityId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentThreadActionBar({ commentThreadId }: OwnProps) {
|
export function ActivityActionBar({ activityId }: OwnProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [createCommentMutation] = useDeleteCommentThreadMutation();
|
const [createCommentMutation] = useDeleteActivityMutation();
|
||||||
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||||
|
|
||||||
function deleteCommentThread() {
|
function deleteActivity() {
|
||||||
createCommentMutation({
|
createCommentMutation({
|
||||||
variables: { commentThreadId },
|
variables: { activityId },
|
||||||
refetchQueries: [
|
refetchQueries: [
|
||||||
getOperationName(GET_COMPANIES) ?? '',
|
getOperationName(GET_COMPANIES) ?? '',
|
||||||
getOperationName(GET_PEOPLE) ?? '',
|
getOperationName(GET_PEOPLE) ?? '',
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
setIsRightDrawerOpen(false);
|
setIsRightDrawerOpen(false);
|
||||||
@ -43,7 +43,7 @@ export function CommentThreadActionBar({ commentThreadId }: OwnProps) {
|
|||||||
icon={
|
icon={
|
||||||
<IconTrash size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
|
<IconTrash size={theme.icon.size.sm} stroke={theme.icon.stroke.md} />
|
||||||
}
|
}
|
||||||
onClick={deleteCommentThread}
|
onClick={deleteActivity}
|
||||||
variant={ButtonVariant.Tertiary}
|
variant={ButtonVariant.Tertiary}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
@ -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}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,22 +1,22 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
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 { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
|
||||||
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
||||||
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
||||||
|
|
||||||
import { CommentThread } from '../CommentThread';
|
import { Activity } from '../Activity';
|
||||||
|
|
||||||
export function RightDrawerCreateCommentThread() {
|
export function RightDrawerCreateActivity() {
|
||||||
const commentThreadId = useRecoilValue(viewableCommentThreadIdState);
|
const activityId = useRecoilValue(viewableActivityIdState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightDrawerPage>
|
<RightDrawerPage>
|
||||||
<RightDrawerTopBar />
|
<RightDrawerTopBar />
|
||||||
<RightDrawerBody>
|
<RightDrawerBody>
|
||||||
{commentThreadId && (
|
{activityId && (
|
||||||
<CommentThread
|
<Activity
|
||||||
commentThreadId={commentThreadId}
|
activityId={activityId}
|
||||||
showComment={false}
|
showComment={false}
|
||||||
autoFillTitle={true}
|
autoFillTitle={true}
|
||||||
/>
|
/>
|
||||||
@ -1,20 +1,20 @@
|
|||||||
import { useRecoilValue } from 'recoil';
|
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 { RightDrawerBody } from '@/ui/right-drawer/components/RightDrawerBody';
|
||||||
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
import { RightDrawerPage } from '@/ui/right-drawer/components/RightDrawerPage';
|
||||||
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
import { RightDrawerTopBar } from '@/ui/right-drawer/components/RightDrawerTopBar';
|
||||||
|
|
||||||
import { CommentThread } from '../CommentThread';
|
import { Activity } from '../Activity';
|
||||||
|
|
||||||
export function RightDrawerEditCommentThread() {
|
export function RightDrawerEditActivity() {
|
||||||
const commentThreadId = useRecoilValue(viewableCommentThreadIdState);
|
const activityId = useRecoilValue(viewableActivityIdState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RightDrawerPage>
|
<RightDrawerPage>
|
||||||
<RightDrawerTopBar />
|
<RightDrawerTopBar />
|
||||||
<RightDrawerBody>
|
<RightDrawerBody>
|
||||||
{commentThreadId && <CommentThread commentThreadId={commentThreadId} />}
|
{activityId && <Activity activityId={activityId} />}
|
||||||
</RightDrawerBody>
|
</RightDrawerBody>
|
||||||
</RightDrawerPage>
|
</RightDrawerPage>
|
||||||
);
|
);
|
||||||
@ -3,6 +3,6 @@ import { atom } from 'recoil';
|
|||||||
import { CommentableEntity } from '../types/CommentableEntity';
|
import { CommentableEntity } from '../types/CommentableEntity';
|
||||||
|
|
||||||
export const commentableEntityArrayState = atom<CommentableEntity[]>({
|
export const commentableEntityArrayState = atom<CommentableEntity[]>({
|
||||||
key: 'comments/commentable-entity-array',
|
key: 'activities/commentable-entity-array',
|
||||||
default: [],
|
default: [],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const viewableActivityIdState = atom<string | null>({
|
||||||
|
key: 'activities/viewable-activity-id',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import { atom } from 'recoil';
|
|
||||||
|
|
||||||
export const viewableCommentThreadIdState = atom<string | null>({
|
|
||||||
key: 'comments/viewable-comment-thread-id',
|
|
||||||
default: null,
|
|
||||||
});
|
|
||||||
@ -2,16 +2,16 @@ import React from 'react';
|
|||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { CommentThreadCreateButton } from '@/activities/components/CommentThreadCreateButton';
|
import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton';
|
||||||
import { useOpenCreateCommentThreadDrawer } from '@/activities/hooks/useOpenCreateCommentThreadDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
|
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
||||||
import { CommentableEntity } from '@/activities/types/CommentableEntity';
|
import { CommentableEntity } from '@/activities/types/CommentableEntity';
|
||||||
import { CommentThreadForDrawer } from '@/activities/types/CommentThreadForDrawer';
|
|
||||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||||
import { IconCircleDot } from '@/ui/icon';
|
import { IconCircleDot } from '@/ui/icon';
|
||||||
import {
|
import {
|
||||||
ActivityType,
|
ActivityType,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
useGetCommentThreadsByTargetsQuery,
|
useGetActivitiesByTargetsQuery,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
import { TimelineActivity } from './TimelineActivity';
|
import { TimelineActivity } from './TimelineActivity';
|
||||||
@ -96,9 +96,9 @@ const StyledStartIcon = styled.div`
|
|||||||
export function Timeline({ entity }: { entity: CommentableEntity }) {
|
export function Timeline({ entity }: { entity: CommentableEntity }) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { data: queryResult, loading } = useGetCommentThreadsByTargetsQuery({
|
const { data: queryResult, loading } = useGetActivitiesByTargetsQuery({
|
||||||
variables: {
|
variables: {
|
||||||
commentThreadTargetIds: [entity.id],
|
activityTargetIds: [entity.id],
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{
|
{
|
||||||
createdAt: SortOrder.Desc,
|
createdAt: SortOrder.Desc,
|
||||||
@ -107,21 +107,20 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const openCreateCommandThread = useOpenCreateCommentThreadDrawer();
|
const openCreateCommandThread = useOpenCreateActivityDrawer();
|
||||||
|
|
||||||
const commentThreads: CommentThreadForDrawer[] =
|
const activities: ActivityForDrawer[] = queryResult?.findManyActivities ?? [];
|
||||||
queryResult?.findManyCommentThreads ?? [];
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!commentThreads.length) {
|
if (!activities.length) {
|
||||||
return (
|
return (
|
||||||
<StyledTimelineEmptyContainer>
|
<StyledTimelineEmptyContainer>
|
||||||
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle>
|
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle>
|
||||||
<StyledEmptyTimelineSubTitle>Create one:</StyledEmptyTimelineSubTitle>
|
<StyledEmptyTimelineSubTitle>Create one:</StyledEmptyTimelineSubTitle>
|
||||||
<CommentThreadCreateButton
|
<ActivityCreateButton
|
||||||
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
|
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
|
||||||
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
|
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
|
||||||
/>
|
/>
|
||||||
@ -132,17 +131,14 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
|
|||||||
return (
|
return (
|
||||||
<StyledMainContainer>
|
<StyledMainContainer>
|
||||||
<StyledTopActionBar>
|
<StyledTopActionBar>
|
||||||
<CommentThreadCreateButton
|
<ActivityCreateButton
|
||||||
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
|
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
|
||||||
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
|
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
|
||||||
/>
|
/>
|
||||||
</StyledTopActionBar>
|
</StyledTopActionBar>
|
||||||
<StyledTimelineContainer>
|
<StyledTimelineContainer>
|
||||||
{commentThreads.map((commentThread) => (
|
{activities.map((activity) => (
|
||||||
<TimelineActivity
|
<TimelineActivity key={activity.id} activity={activity} />
|
||||||
key={commentThread.id}
|
|
||||||
commentThread={commentThread}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
<StyledStartIcon>
|
<StyledStartIcon>
|
||||||
<IconCircleDot size={theme.icon.size.lg} />
|
<IconCircleDot size={theme.icon.size.lg} />
|
||||||
|
|||||||
@ -3,14 +3,11 @@ import { Tooltip } from 'react-tooltip';
|
|||||||
import { getOperationName } from '@apollo/client/utilities';
|
import { getOperationName } from '@apollo/client/utilities';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { useOpenCommentThreadRightDrawer } from '@/activities/hooks/useOpenCommentThreadRightDrawer';
|
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries';
|
import { GET_ACTIVITIES_BY_TARGETS } from '@/activities/queries';
|
||||||
import { IconNotes } from '@/ui/icon';
|
import { IconNotes } from '@/ui/icon';
|
||||||
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
|
||||||
import {
|
import { Activity, useUpdateActivityMutation } from '~/generated/graphql';
|
||||||
CommentThread,
|
|
||||||
useUpdateCommentThreadMutation,
|
|
||||||
} from '~/generated/graphql';
|
|
||||||
import {
|
import {
|
||||||
beautifyExactDate,
|
beautifyExactDate,
|
||||||
beautifyPastDateRelativeToNow,
|
beautifyPastDateRelativeToNow,
|
||||||
@ -115,35 +112,31 @@ const StyledTimelineItemContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThread: Pick<
|
activity: Pick<
|
||||||
CommentThread,
|
Activity,
|
||||||
'id' | 'title' | 'body' | 'createdAt' | 'completedAt' | 'type'
|
'id' | 'title' | 'body' | 'createdAt' | 'completedAt' | 'type'
|
||||||
> & { author: Pick<CommentThread['author'], 'displayName'> };
|
> & { author: Pick<Activity['author'], 'displayName'> };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TimelineActivity({ commentThread }: OwnProps) {
|
export function TimelineActivity({ activity }: OwnProps) {
|
||||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(
|
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(activity.createdAt);
|
||||||
commentThread.createdAt,
|
const exactCreatedAt = beautifyExactDate(activity.createdAt);
|
||||||
);
|
const body = JSON.parse(activity.body ?? '{}')[0]?.content[0]?.text;
|
||||||
const exactCreatedAt = beautifyExactDate(commentThread.createdAt);
|
|
||||||
const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0]?.text;
|
|
||||||
|
|
||||||
const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer();
|
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
const [updateActivityMutation] = useUpdateActivityMutation();
|
||||||
|
|
||||||
const handleActivityCompletionChange = useCallback(
|
const handleActivityCompletionChange = useCallback(
|
||||||
(value: boolean) => {
|
(value: boolean) => {
|
||||||
updateCommentThreadMutation({
|
updateActivityMutation({
|
||||||
variables: {
|
variables: {
|
||||||
id: commentThread.id,
|
id: activity.id,
|
||||||
completedAt: value ? new Date().toISOString() : null,
|
completedAt: value ? new Date().toISOString() : null,
|
||||||
},
|
},
|
||||||
refetchQueries: [
|
refetchQueries: [getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? ''],
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[commentThread, updateCommentThreadMutation],
|
[activity, updateActivityMutation],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -153,14 +146,14 @@ export function TimelineActivity({ commentThread }: OwnProps) {
|
|||||||
<IconNotes />
|
<IconNotes />
|
||||||
</StyledIconContainer>
|
</StyledIconContainer>
|
||||||
<StyledItemTitleContainer>
|
<StyledItemTitleContainer>
|
||||||
<span>{commentThread.author.displayName}</span>
|
<span>{activity.author.displayName}</span>
|
||||||
created a {commentThread.type.toLowerCase()}
|
created a note created a {activity.type.toLowerCase()}
|
||||||
</StyledItemTitleContainer>
|
</StyledItemTitleContainer>
|
||||||
<StyledItemTitleDate id={`id-${commentThread.id}`}>
|
<StyledItemTitleDate id={`id-${activity.id}`}>
|
||||||
{beautifiedCreatedAt} ago
|
{beautifiedCreatedAt} ago
|
||||||
</StyledItemTitleDate>
|
</StyledItemTitleDate>
|
||||||
<StyledTooltip
|
<StyledTooltip
|
||||||
anchorSelect={`#id-${commentThread.id}`}
|
anchorSelect={`#id-${activity.id}`}
|
||||||
content={exactCreatedAt}
|
content={exactCreatedAt}
|
||||||
clickable
|
clickable
|
||||||
noArrow
|
noArrow
|
||||||
@ -171,13 +164,11 @@ export function TimelineActivity({ commentThread }: OwnProps) {
|
|||||||
<StyledVerticalLine></StyledVerticalLine>
|
<StyledVerticalLine></StyledVerticalLine>
|
||||||
</StyledVerticalLineContainer>
|
</StyledVerticalLineContainer>
|
||||||
<StyledCardContainer>
|
<StyledCardContainer>
|
||||||
<StyledCard
|
<StyledCard onClick={() => openActivityRightDrawer(activity.id)}>
|
||||||
onClick={() => openCommentThreadRightDrawer(commentThread.id)}
|
|
||||||
>
|
|
||||||
<TimelineActivityTitle
|
<TimelineActivityTitle
|
||||||
title={commentThread.title ?? ''}
|
title={activity.title ?? ''}
|
||||||
completed={!!commentThread.completedAt}
|
completed={!!activity.completedAt}
|
||||||
type={commentThread.type}
|
type={activity.type}
|
||||||
onCompletionChange={handleActivityCompletionChange}
|
onCompletionChange={handleActivityCompletionChange}
|
||||||
/>
|
/>
|
||||||
<StyledCardContent>
|
<StyledCardContent>
|
||||||
|
|||||||
4
front/src/modules/activities/types/ActivityForDrawer.ts
Normal file
4
front/src/modules/activities/types/ActivityForDrawer.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { GetActivitiesByTargetsQuery } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export type ActivityForDrawer =
|
||||||
|
GetActivitiesByTargetsQuery['findManyActivities'][0];
|
||||||
@ -1,5 +1,3 @@
|
|||||||
import { CommentThreadForDrawer } from './CommentThreadForDrawer';
|
import { ActivityForDrawer } from './ActivityForDrawer';
|
||||||
|
|
||||||
export type CommentForDrawer = NonNullable<
|
export type CommentForDrawer = NonNullable<ActivityForDrawer['comments']>[0];
|
||||||
CommentThreadForDrawer['comments']
|
|
||||||
>[0];
|
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
import { GetCommentThreadsByTargetsQuery } from '~/generated/graphql';
|
|
||||||
|
|
||||||
export type CommentThreadForDrawer =
|
|
||||||
GetCommentThreadsByTargetsQuery['findManyCommentThreads'][0];
|
|
||||||
@ -6,7 +6,7 @@ import { useRecoilState } from 'recoil';
|
|||||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { CommentThreadTarget } from '~/generated/graphql';
|
import { ActivityTarget } from '~/generated/graphql';
|
||||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||||
|
|
||||||
@ -25,12 +25,12 @@ export function useApolloFactory() {
|
|||||||
uri: `${process.env.REACT_APP_API_URL}`,
|
uri: `${process.env.REACT_APP_API_URL}`,
|
||||||
cache: new InMemoryCache({
|
cache: new InMemoryCache({
|
||||||
typePolicies: {
|
typePolicies: {
|
||||||
CommentThread: {
|
Activity: {
|
||||||
fields: {
|
fields: {
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
merge(
|
merge(
|
||||||
_existing: CommentThreadTarget[] = [],
|
_existing: ActivityTarget[] = [],
|
||||||
incoming: CommentThreadTarget[],
|
incoming: ActivityTarget[],
|
||||||
) {
|
) {
|
||||||
return [...incoming];
|
return [...incoming];
|
||||||
},
|
},
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { CompanyChip } from './CompanyChip';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
company: Pick<
|
company: Pick<
|
||||||
GetCompaniesQuery['companies'][0],
|
GetCompaniesQuery['companies'][0],
|
||||||
'id' | 'name' | 'domainName' | '_commentThreadCount'
|
'id' | 'name' | 'domainName' | '_activityCount'
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export const GET_COMPANIES = gql`
|
|||||||
address
|
address
|
||||||
linkedinUrl
|
linkedinUrl
|
||||||
employees
|
employees
|
||||||
_commentThreadCount
|
_activityCount
|
||||||
accountOwner {
|
accountOwner {
|
||||||
id
|
id
|
||||||
email
|
email
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export const GET_COMPANY = gql`
|
|||||||
address
|
address
|
||||||
linkedinUrl
|
linkedinUrl
|
||||||
employees
|
employees
|
||||||
_commentThreadCount
|
_activityCount
|
||||||
accountOwner {
|
accountOwner {
|
||||||
id
|
id
|
||||||
email
|
email
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export function EditableCompanyNameCell() {
|
|||||||
id: currentRowEntityId ?? '',
|
id: currentRowEntityId ?? '',
|
||||||
name: name ?? '',
|
name: name ?? '',
|
||||||
domainName: domainName ?? '',
|
domainName: domainName ?? '',
|
||||||
_commentThreadCount: commentCount ?? 0,
|
_activityCount: commentCount ?? 0,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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} />;
|
||||||
|
}
|
||||||
@ -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} />;
|
|
||||||
}
|
|
||||||
@ -10,7 +10,7 @@ type MockedCompany = Pick<
|
|||||||
| 'address'
|
| 'address'
|
||||||
| 'employees'
|
| 'employees'
|
||||||
| 'linkedinUrl'
|
| 'linkedinUrl'
|
||||||
| '_commentThreadCount'
|
| '_activityCount'
|
||||||
> & {
|
> & {
|
||||||
accountOwner: Pick<
|
accountOwner: Pick<
|
||||||
User,
|
User,
|
||||||
@ -33,7 +33,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
createdAt: '2023-04-26T10:08:54.724515+00:00',
|
createdAt: '2023-04-26T10:08:54.724515+00:00',
|
||||||
address: 'San Francisco, CA',
|
address: 'San Francisco, CA',
|
||||||
employees: 5000,
|
employees: 5000,
|
||||||
_commentThreadCount: 0,
|
_activityCount: 0,
|
||||||
accountOwner: {
|
accountOwner: {
|
||||||
email: 'charles@test.com',
|
email: 'charles@test.com',
|
||||||
displayName: 'Charles Test',
|
displayName: 'Charles Test',
|
||||||
@ -53,7 +53,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
address: 'Paris, France',
|
address: 'Paris, France',
|
||||||
employees: 800,
|
employees: 800,
|
||||||
_commentThreadCount: 0,
|
_activityCount: 0,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -65,7 +65,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
createdAt: '2023-04-26T10:10:32.530184+00:00',
|
createdAt: '2023-04-26T10:10:32.530184+00:00',
|
||||||
address: 'San Francisco, CA',
|
address: 'San Francisco, CA',
|
||||||
employees: 8000,
|
employees: 8000,
|
||||||
_commentThreadCount: 0,
|
_activityCount: 0,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -77,7 +77,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
createdAt: '2023-03-21T06:30:25.39474+00:00',
|
createdAt: '2023-03-21T06:30:25.39474+00:00',
|
||||||
address: 'San Francisco, CA',
|
address: 'San Francisco, CA',
|
||||||
employees: 800,
|
employees: 800,
|
||||||
_commentThreadCount: 0,
|
_activityCount: 0,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -89,7 +89,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
createdAt: '2023-04-26T10:13:29.712485+00:00',
|
createdAt: '2023-04-26T10:13:29.712485+00:00',
|
||||||
address: 'San Francisco, CA',
|
address: 'San Francisco, CA',
|
||||||
employees: 400,
|
employees: 400,
|
||||||
_commentThreadCount: 0,
|
_activityCount: 0,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -83,10 +83,10 @@ export function useSetCompanyEntityTable() {
|
|||||||
.getLoadable(companyCommentCountFamilyState(company.id))
|
.getLoadable(companyCommentCountFamilyState(company.id))
|
||||||
.valueOrThrow();
|
.valueOrThrow();
|
||||||
|
|
||||||
if (currentCommentCount !== company._commentThreadCount) {
|
if (currentCommentCount !== company._activityCount) {
|
||||||
set(
|
set(
|
||||||
companyCommentCountFamilyState(company.id),
|
companyCommentCountFamilyState(company.id),
|
||||||
company._commentThreadCount,
|
company._activityCount,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,11 +10,7 @@ type OwnProps = {
|
|||||||
| Partial<
|
| Partial<
|
||||||
Pick<
|
Pick<
|
||||||
Person,
|
Person,
|
||||||
| 'id'
|
'id' | 'firstName' | 'lastName' | 'displayName' | '_activityCount'
|
||||||
| 'firstName'
|
|
||||||
| 'lastName'
|
|
||||||
| 'displayName'
|
|
||||||
| '_commentThreadCount'
|
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
| null
|
| null
|
||||||
|
|||||||
@ -101,12 +101,12 @@ export function useSetPeopleEntityTable() {
|
|||||||
if (
|
if (
|
||||||
currentNameCell.firstName !== person.firstName ||
|
currentNameCell.firstName !== person.firstName ||
|
||||||
currentNameCell.lastName !== person.lastName ||
|
currentNameCell.lastName !== person.lastName ||
|
||||||
currentNameCell.commentCount !== person._commentThreadCount
|
currentNameCell.commentCount !== person._activityCount
|
||||||
) {
|
) {
|
||||||
set(peopleNameCellFamilyState(person.id), {
|
set(peopleNameCellFamilyState(person.id), {
|
||||||
firstName: person.firstName ?? null,
|
firstName: person.firstName ?? null,
|
||||||
lastName: person.lastName ?? null,
|
lastName: person.lastName ?? null,
|
||||||
commentCount: person._commentThreadCount,
|
commentCount: person._activityCount,
|
||||||
displayName: person.displayName ?? null,
|
displayName: person.displayName ?? null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const GET_PEOPLE = gql`
|
|||||||
jobTitle
|
jobTitle
|
||||||
linkedinUrl
|
linkedinUrl
|
||||||
createdAt
|
createdAt
|
||||||
_commentThreadCount
|
_activityCount
|
||||||
company {
|
company {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -107,7 +107,7 @@ export const GET_PERSON_NAMES_AND_COMMENT_COUNT = gql`
|
|||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
displayName
|
displayName
|
||||||
_commentThreadCount
|
_activityCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -129,7 +129,7 @@ export const GET_PERSON_COMMENT_COUNT = gql`
|
|||||||
query GetPersonCommentCountById($id: String!) {
|
query GetPersonCommentCountById($id: String!) {
|
||||||
person: findUniquePerson(id: $id) {
|
person: findUniquePerson(id: $id) {
|
||||||
id
|
id
|
||||||
_commentThreadCount
|
_activityCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export const GET_PERSON = gql`
|
|||||||
jobTitle
|
jobTitle
|
||||||
linkedinUrl
|
linkedinUrl
|
||||||
phone
|
phone
|
||||||
_commentThreadCount
|
_activityCount
|
||||||
company {
|
company {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function EditablePeopleFullNameCell() {
|
|||||||
<EditablePeopleFullName
|
<EditablePeopleFullName
|
||||||
person={{
|
person={{
|
||||||
id: currentRowEntityId ?? undefined,
|
id: currentRowEntityId ?? undefined,
|
||||||
_commentThreadCount: commentCount ?? undefined,
|
_activityCount: commentCount ?? undefined,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
displayName: displayName ?? undefined,
|
displayName: displayName ?? undefined,
|
||||||
|
|||||||
@ -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} />;
|
||||||
|
}
|
||||||
@ -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} />;
|
|
||||||
}
|
|
||||||
144
front/src/modules/ui/button/components/DropdownButton.tsx
Normal file
144
front/src/modules/ui/button/components/DropdownButton.tsx
Normal 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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { RightDrawerCreateCommentThread } from '@/activities/right-drawer/components/create/RightDrawerCreateCommentThread';
|
import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity';
|
||||||
import { RightDrawerEditCommentThread } from '@/activities/right-drawer/components/edit/RightDrawerEditCommentThread';
|
import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity';
|
||||||
import { RightDrawerTimeline } from '@/activities/right-drawer/components/RightDrawerTimeline';
|
import { RightDrawerTimeline } from '@/activities/right-drawer/components/RightDrawerTimeline';
|
||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
|
|
||||||
@ -18,10 +18,10 @@ export function RightDrawerRouter() {
|
|||||||
switch (rightDrawerPage) {
|
switch (rightDrawerPage) {
|
||||||
case RightDrawerPages.Timeline:
|
case RightDrawerPages.Timeline:
|
||||||
return <RightDrawerTimeline />;
|
return <RightDrawerTimeline />;
|
||||||
case RightDrawerPages.CreateCommentThread:
|
case RightDrawerPages.CreateActivity:
|
||||||
return <RightDrawerCreateCommentThread />;
|
return <RightDrawerCreateActivity />;
|
||||||
case RightDrawerPages.EditCommentThread:
|
case RightDrawerPages.EditActivity:
|
||||||
return <RightDrawerEditCommentThread />;
|
return <RightDrawerEditActivity />;
|
||||||
default:
|
default:
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export enum RightDrawerPages {
|
export enum RightDrawerPages {
|
||||||
Timeline = 'timeline',
|
Timeline = 'timeline',
|
||||||
CreateCommentThread = 'create-comment-thread',
|
CreateActivity = 'create-activity',
|
||||||
EditCommentThread = 'edit-comment-thread',
|
EditActivity = 'edit-activity',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export type EditableChipProps = {
|
|||||||
value: string;
|
value: string;
|
||||||
editModeHorizontalAlign?: 'left' | 'right';
|
editModeHorizontalAlign?: 'left' | 'right';
|
||||||
ChipComponent: React.ReactNode;
|
ChipComponent: React.ReactNode;
|
||||||
commentThreadCount?: number;
|
activityCount?: number;
|
||||||
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
onCommentClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
rightEndContents?: ReactNode[];
|
rightEndContents?: ReactNode[];
|
||||||
onSubmit?: (newValue: string) => void;
|
onSubmit?: (newValue: string) => void;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { GET_COMPANIES } from '@/companies/queries';
|
import { GET_COMPANIES } from '@/companies/queries';
|
||||||
import { CompanyTable } from '@/companies/table/components/CompanyTable';
|
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 { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies';
|
||||||
import { IconBuildingSkyscraper } from '@/ui/icon';
|
import { IconBuildingSkyscraper } from '@/ui/icon';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
||||||
@ -53,7 +53,7 @@ export function Companies() {
|
|||||||
<CompanyTable />
|
<CompanyTable />
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
<EntityTableActionBar>
|
<EntityTableActionBar>
|
||||||
<TableActionBarButtonCreateCommentThreadCompany />
|
<TableActionBarButtonCreateActivityCompany />
|
||||||
<TableActionBarButtonDeleteCompanies />
|
<TableActionBarButtonDeleteCompanies />
|
||||||
</EntityTableActionBar>
|
</EntityTableActionBar>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useTheme } from '@emotion/react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { CompanyTableMockMode } from '@/companies/table/components/CompanyTableMockMode';
|
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 { TableActionBarButtonDeleteCompanies } from '@/companies/table/components/TableActionBarButtonDeleteCompanies';
|
||||||
import { IconBuildingSkyscraper } from '@/ui/icon';
|
import { IconBuildingSkyscraper } from '@/ui/icon';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
||||||
@ -29,7 +29,7 @@ export function CompaniesMockMode() {
|
|||||||
<CompanyTableMockMode />
|
<CompanyTableMockMode />
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
<EntityTableActionBar>
|
<EntityTableActionBar>
|
||||||
<TableActionBarButtonCreateCommentThreadCompany />
|
<TableActionBarButtonCreateActivityCompany />
|
||||||
<TableActionBarButtonDeleteCompanies />
|
<TableActionBarButtonDeleteCompanies />
|
||||||
</EntityTableActionBar>
|
</EntityTableActionBar>
|
||||||
</RecoilScope>
|
</RecoilScope>
|
||||||
|
|||||||
@ -4,18 +4,15 @@ import type { Meta, StoryObj } from '@storybook/react';
|
|||||||
import { fireEvent, within } from '@storybook/testing-library';
|
import { fireEvent, within } from '@storybook/testing-library';
|
||||||
import { graphql } from 'msw';
|
import { graphql } from 'msw';
|
||||||
|
|
||||||
import {
|
import { GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITY } from '@/activities/queries';
|
||||||
GET_COMMENT_THREAD,
|
import { CREATE_ACTIVITY_WITH_COMMENT } from '@/activities/queries/create';
|
||||||
GET_COMMENT_THREADS_BY_TARGETS,
|
|
||||||
} from '@/activities/queries';
|
|
||||||
import { CREATE_COMMENT_THREAD_WITH_COMMENT } from '@/activities/queries/create';
|
|
||||||
import { GET_COMPANY } from '@/companies/queries';
|
import { GET_COMPANY } from '@/companies/queries';
|
||||||
import {
|
import {
|
||||||
PageDecorator,
|
PageDecorator,
|
||||||
type PageDecoratorArgs,
|
type PageDecoratorArgs,
|
||||||
} from '~/testing/decorators/PageDecorator';
|
} from '~/testing/decorators/PageDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
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 { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||||
|
|
||||||
import { CompanyShow } from '../CompanyShow';
|
import { CompanyShow } from '../CompanyShow';
|
||||||
@ -30,11 +27,11 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
msw: [
|
msw: [
|
||||||
...graphqlMocks,
|
...graphqlMocks,
|
||||||
graphql.query(
|
graphql.query(
|
||||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
getOperationName(GET_ACTIVITIES_BY_TARGETS) ?? '',
|
||||||
(req, res, ctx) => {
|
(req, res, ctx) => {
|
||||||
return res(
|
return res(
|
||||||
ctx.data({
|
ctx.data({
|
||||||
findManyCommentThreads: mockedCommentThreads,
|
findManyActivities: mockedActivities,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -82,25 +79,22 @@ export const EditNote: Story = {
|
|||||||
msw: [
|
msw: [
|
||||||
...meta.parameters?.msw,
|
...meta.parameters?.msw,
|
||||||
graphql.mutation(
|
graphql.mutation(
|
||||||
getOperationName(CREATE_COMMENT_THREAD_WITH_COMMENT) ?? '',
|
getOperationName(CREATE_ACTIVITY_WITH_COMMENT) ?? '',
|
||||||
(req, res, ctx) => {
|
(req, res, ctx) => {
|
||||||
return res(
|
return res(
|
||||||
ctx.data({
|
ctx.data({
|
||||||
createOneCommentThread: mockedCommentThreads[0],
|
createOneActivity: mockedActivities[0],
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
graphql.query(
|
|
||||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
|
||||||
(req, res, ctx) => {
|
|
||||||
return res(
|
|
||||||
ctx.data({
|
|
||||||
findManyCommentThreads: [mockedCommentThreads[0]],
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
graphql.query(getOperationName(GET_ACTIVITY) ?? '', (req, res, ctx) => {
|
||||||
|
return res(
|
||||||
|
ctx.data({
|
||||||
|
findManyActivitys: [mockedActivities[0]],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { GET_PEOPLE } from '@/people/queries';
|
import { GET_PEOPLE } from '@/people/queries';
|
||||||
import { PeopleTable } from '@/people/table/components/PeopleTable';
|
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 { TableActionBarButtonDeletePeople } from '@/people/table/components/TableActionBarButtonDeletePeople';
|
||||||
import { IconUser } from '@/ui/icon';
|
import { IconUser } from '@/ui/icon';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/components/WithTopBarContainer';
|
||||||
@ -46,7 +46,7 @@ export function People() {
|
|||||||
<PeopleTable />
|
<PeopleTable />
|
||||||
</StyledTableContainer>
|
</StyledTableContainer>
|
||||||
<EntityTableActionBar>
|
<EntityTableActionBar>
|
||||||
<TableActionBarButtonCreateCommentThreadPeople />
|
<TableActionBarButtonCreateActivityPeople />
|
||||||
<TableActionBarButtonDeletePeople />
|
<TableActionBarButtonDeletePeople />
|
||||||
</EntityTableActionBar>
|
</EntityTableActionBar>
|
||||||
</WithTopBarContainer>
|
</WithTopBarContainer>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
|
Activity,
|
||||||
|
ActivityTarget,
|
||||||
ActivityType,
|
ActivityType,
|
||||||
Comment,
|
Comment,
|
||||||
CommentableType,
|
CommentableType,
|
||||||
CommentThread,
|
|
||||||
CommentThreadTarget,
|
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
type MockedCommentThread = Pick<
|
type MockedActivity = Pick<
|
||||||
CommentThread,
|
Activity,
|
||||||
| 'id'
|
| 'id'
|
||||||
| 'createdAt'
|
| 'createdAt'
|
||||||
| 'updatedAt'
|
| 'updatedAt'
|
||||||
@ -25,21 +25,21 @@ type MockedCommentThread = Pick<
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
};
|
};
|
||||||
comments: Array<Pick<Comment, 'body'>>;
|
comments: Array<Pick<Comment, 'body'>>;
|
||||||
commentThreadTargets: Array<
|
activityTargets: Array<
|
||||||
Pick<
|
Pick<
|
||||||
CommentThreadTarget,
|
ActivityTarget,
|
||||||
| 'id'
|
| 'id'
|
||||||
| '__typename'
|
| '__typename'
|
||||||
| 'createdAt'
|
| 'createdAt'
|
||||||
| 'updatedAt'
|
| 'updatedAt'
|
||||||
| 'commentableType'
|
| 'activityId'
|
||||||
| 'commentableId'
|
| 'commentableId'
|
||||||
| 'commentThreadId'
|
| 'commentableType'
|
||||||
> & { commentThread: Pick<CommentThread, 'id' | 'createdAt' | 'updatedAt'> }
|
> & { activity: Pick<Activity, 'id' | 'createdAt' | 'updatedAt'> }
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockedCommentThreads: Array<MockedCommentThread> = [
|
export const mockedActivities: Array<MockedActivity> = [
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
|
||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
@ -55,20 +55,20 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
|||||||
},
|
},
|
||||||
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||||
comments: [],
|
comments: [],
|
||||||
commentThreadTargets: [
|
activityTargets: [
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
|
||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
commentableType: CommentableType.Company,
|
commentableType: CommentableType.Company,
|
||||||
commentableId: '89bb825c-171e-4bcc-9cf7-43448d6fb278', // airbnb
|
commentableId: '89bb825c-171e-4bcc-9cf7-43448d6fb278', // airbnb
|
||||||
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
|
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
|
||||||
commentThread: {
|
activity: {
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
|
||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
},
|
},
|
||||||
__typename: 'CommentThreadTarget',
|
__typename: 'ActivityTarget',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb301',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb301',
|
||||||
@ -76,16 +76,16 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
|||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
commentableType: CommentableType.Company,
|
commentableType: CommentableType.Company,
|
||||||
commentableId: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae', // aircall
|
commentableId: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae', // aircall
|
||||||
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
|
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
|
||||||
commentThread: {
|
activity: {
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
__typename: 'CommentThreadTarget',
|
__typename: 'ActivityTarget',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: 'CommentThread',
|
__typename: 'Activity',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
@ -102,20 +102,20 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
|||||||
},
|
},
|
||||||
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||||
comments: [],
|
comments: [],
|
||||||
commentThreadTargets: [
|
activityTargets: [
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
commentableType: CommentableType.Person,
|
commentableType: CommentableType.Person,
|
||||||
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', // Alexandre
|
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', // Alexandre
|
||||||
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
commentThread: {
|
activity: {
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||||
},
|
},
|
||||||
__typename: 'CommentThreadTarget',
|
__typename: 'ActivityTarget',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
@ -123,15 +123,15 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
|||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
commentableType: CommentableType.Person,
|
commentableType: CommentableType.Person,
|
||||||
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d', // Jean d'Eau
|
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d', // Jean d'Eau
|
||||||
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
activityId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
commentThread: {
|
activity: {
|
||||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
__typename: 'CommentThreadTarget',
|
__typename: 'ActivityTarget',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
__typename: 'CommentThread',
|
__typename: 'Activity',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -10,7 +10,7 @@ type MockedCompany = Pick<
|
|||||||
| 'address'
|
| 'address'
|
||||||
| 'employees'
|
| 'employees'
|
||||||
| 'linkedinUrl'
|
| 'linkedinUrl'
|
||||||
| '_commentThreadCount'
|
| '_activityCount'
|
||||||
> & {
|
> & {
|
||||||
accountOwner: Pick<
|
accountOwner: Pick<
|
||||||
User,
|
User,
|
||||||
@ -33,7 +33,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
address: '17 rue de clignancourt',
|
address: '17 rue de clignancourt',
|
||||||
employees: 12,
|
employees: 12,
|
||||||
linkedinUrl: 'https://www.linkedin.com/company/airbnb/',
|
linkedinUrl: 'https://www.linkedin.com/company/airbnb/',
|
||||||
_commentThreadCount: 1,
|
_activityCount: 1,
|
||||||
accountOwner: {
|
accountOwner: {
|
||||||
email: 'charles@test.com',
|
email: 'charles@test.com',
|
||||||
displayName: 'Charles Test',
|
displayName: 'Charles Test',
|
||||||
@ -53,7 +53,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
address: '',
|
address: '',
|
||||||
employees: 1,
|
employees: 1,
|
||||||
linkedinUrl: 'https://www.linkedin.com/company/aircall/',
|
linkedinUrl: 'https://www.linkedin.com/company/aircall/',
|
||||||
_commentThreadCount: 1,
|
_activityCount: 1,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -65,7 +65,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
address: '',
|
address: '',
|
||||||
employees: 1,
|
employees: 1,
|
||||||
linkedinUrl: 'https://www.linkedin.com/company/algolia/',
|
linkedinUrl: 'https://www.linkedin.com/company/algolia/',
|
||||||
_commentThreadCount: 1,
|
_activityCount: 1,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -77,7 +77,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
address: '',
|
address: '',
|
||||||
employees: 10,
|
employees: 10,
|
||||||
linkedinUrl: 'https://www.linkedin.com/company/apple/',
|
linkedinUrl: 'https://www.linkedin.com/company/apple/',
|
||||||
_commentThreadCount: 0,
|
_activityCount: 0,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -89,7 +89,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
address: '10 rue de la Paix',
|
address: '10 rue de la Paix',
|
||||||
employees: 1,
|
employees: 1,
|
||||||
linkedinUrl: 'https://www.linkedin.com/company/qonto/',
|
linkedinUrl: 'https://www.linkedin.com/company/qonto/',
|
||||||
_commentThreadCount: 2,
|
_activityCount: 2,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -101,7 +101,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
address: '',
|
address: '',
|
||||||
employees: 1,
|
employees: 1,
|
||||||
linkedinUrl: 'https://www.linkedin.com/company/facebook/',
|
linkedinUrl: 'https://www.linkedin.com/company/facebook/',
|
||||||
_commentThreadCount: 13,
|
_activityCount: 13,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
@ -113,7 +113,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
address: '',
|
address: '',
|
||||||
employees: 1,
|
employees: 1,
|
||||||
linkedinUrl: 'https://www.linkedin.com/company/sequoia/',
|
linkedinUrl: 'https://www.linkedin.com/company/sequoia/',
|
||||||
_commentThreadCount: 1,
|
_activityCount: 1,
|
||||||
accountOwner: null,
|
accountOwner: null,
|
||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,7 +17,7 @@ type MockedPerson = RequiredAndNotNull<
|
|||||||
| '__typename'
|
| '__typename'
|
||||||
| 'phone'
|
| 'phone'
|
||||||
| 'city'
|
| 'city'
|
||||||
| '_commentThreadCount'
|
| '_activityCount'
|
||||||
| 'createdAt'
|
| 'createdAt'
|
||||||
> & {
|
> & {
|
||||||
company: Pick<Company, 'id' | 'name' | 'domainName' | '__typename'>;
|
company: Pick<Company, 'id' | 'name' | 'domainName' | '__typename'>;
|
||||||
@ -41,7 +41,7 @@ export const mockedPeopleData: MockedPerson[] = [
|
|||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
phone: '06 12 34 56 78',
|
phone: '06 12 34 56 78',
|
||||||
_commentThreadCount: 1,
|
_activityCount: 1,
|
||||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||||
|
|
||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
@ -62,7 +62,7 @@ export const mockedPeopleData: MockedPerson[] = [
|
|||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
phone: '06 12 34 56 78',
|
phone: '06 12 34 56 78',
|
||||||
_commentThreadCount: 1,
|
_activityCount: 1,
|
||||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||||
|
|
||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
@ -83,7 +83,7 @@ export const mockedPeopleData: MockedPerson[] = [
|
|||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
phone: '06 12 34 56 78',
|
phone: '06 12 34 56 78',
|
||||||
_commentThreadCount: 1,
|
_activityCount: 1,
|
||||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||||
|
|
||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
@ -104,7 +104,7 @@ export const mockedPeopleData: MockedPerson[] = [
|
|||||||
__typename: 'Company',
|
__typename: 'Company',
|
||||||
},
|
},
|
||||||
phone: '06 12 34 56 78',
|
phone: '06 12 34 56 78',
|
||||||
_commentThreadCount: 2,
|
_activityCount: 2,
|
||||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||||
|
|
||||||
city: 'Paris',
|
city: 'Paris',
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { PureAbility, AbilityBuilder } from '@casl/ability';
|
import { PureAbility, AbilityBuilder } from '@casl/ability';
|
||||||
import { createPrismaAbility, PrismaQuery, Subjects } from '@casl/prisma';
|
import { createPrismaAbility, PrismaQuery, Subjects } from '@casl/prisma';
|
||||||
import {
|
import {
|
||||||
CommentThread,
|
Attachment,
|
||||||
|
Activity,
|
||||||
Company,
|
Company,
|
||||||
Comment,
|
Comment,
|
||||||
Person,
|
Person,
|
||||||
@ -11,11 +12,10 @@ import {
|
|||||||
User,
|
User,
|
||||||
Workspace,
|
Workspace,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
CommentThreadTarget,
|
ActivityTarget,
|
||||||
Pipeline,
|
Pipeline,
|
||||||
PipelineStage,
|
PipelineStage,
|
||||||
PipelineProgress,
|
PipelineProgress,
|
||||||
Attachment,
|
|
||||||
UserSettings,
|
UserSettings,
|
||||||
ViewField,
|
ViewField,
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
@ -29,9 +29,9 @@ type SubjectsAbility = Subjects<{
|
|||||||
Company: Company;
|
Company: Company;
|
||||||
Person: Person;
|
Person: Person;
|
||||||
RefreshToken: RefreshToken;
|
RefreshToken: RefreshToken;
|
||||||
CommentThread: CommentThread;
|
Activity: Activity;
|
||||||
Comment: Comment;
|
Comment: Comment;
|
||||||
CommentThreadTarget: CommentThreadTarget;
|
ActivityTarget: ActivityTarget;
|
||||||
Pipeline: Pipeline;
|
Pipeline: Pipeline;
|
||||||
PipelineStage: PipelineStage;
|
PipelineStage: PipelineStage;
|
||||||
PipelineProgress: PipelineProgress;
|
PipelineProgress: PipelineProgress;
|
||||||
@ -86,11 +86,11 @@ export class AbilityFactory {
|
|||||||
// RefreshToken
|
// RefreshToken
|
||||||
cannot(AbilityAction.Manage, 'RefreshToken');
|
cannot(AbilityAction.Manage, 'RefreshToken');
|
||||||
|
|
||||||
// CommentThread
|
// Activity
|
||||||
can(AbilityAction.Read, 'CommentThread', { workspaceId: workspace.id });
|
can(AbilityAction.Read, 'Activity', { workspaceId: workspace.id });
|
||||||
can(AbilityAction.Create, 'CommentThread');
|
can(AbilityAction.Create, 'Activity');
|
||||||
can(AbilityAction.Update, 'CommentThread', { workspaceId: workspace.id });
|
can(AbilityAction.Update, 'Activity', { workspaceId: workspace.id });
|
||||||
can(AbilityAction.Delete, 'CommentThread', { workspaceId: workspace.id });
|
can(AbilityAction.Delete, 'Activity', { workspaceId: workspace.id });
|
||||||
|
|
||||||
// Comment
|
// Comment
|
||||||
can(AbilityAction.Read, 'Comment', { workspaceId: workspace.id });
|
can(AbilityAction.Read, 'Comment', { workspaceId: workspace.id });
|
||||||
@ -104,9 +104,9 @@ export class AbilityFactory {
|
|||||||
authorId: user.id,
|
authorId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// CommentThreadTarget
|
// ActivityTarget
|
||||||
can(AbilityAction.Read, 'CommentThreadTarget');
|
can(AbilityAction.Read, 'ActivityTarget');
|
||||||
can(AbilityAction.Create, 'CommentThreadTarget');
|
can(AbilityAction.Create, 'ActivityTarget');
|
||||||
|
|
||||||
// Attachment
|
// Attachment
|
||||||
can(AbilityAction.Read, 'Attachment', { workspaceId: workspace.id });
|
can(AbilityAction.Read, 'Attachment', { workspaceId: workspace.id });
|
||||||
|
|||||||
@ -46,12 +46,12 @@ import {
|
|||||||
DeleteRefreshTokenAbilityHandler,
|
DeleteRefreshTokenAbilityHandler,
|
||||||
} from './handlers/refresh-token.ability-handler';
|
} from './handlers/refresh-token.ability-handler';
|
||||||
import {
|
import {
|
||||||
ManageCommentThreadAbilityHandler,
|
ManageActivityAbilityHandler,
|
||||||
ReadCommentThreadAbilityHandler,
|
ReadActivityAbilityHandler,
|
||||||
CreateCommentThreadAbilityHandler,
|
CreateActivityAbilityHandler,
|
||||||
UpdateCommentThreadAbilityHandler,
|
UpdateActivityAbilityHandler,
|
||||||
DeleteCommentThreadAbilityHandler,
|
DeleteActivityAbilityHandler,
|
||||||
} from './handlers/comment-thread.ability-handler';
|
} from './handlers/activity.ability-handler';
|
||||||
import {
|
import {
|
||||||
ManageCommentAbilityHandler,
|
ManageCommentAbilityHandler,
|
||||||
ReadCommentAbilityHandler,
|
ReadCommentAbilityHandler,
|
||||||
@ -60,12 +60,12 @@ import {
|
|||||||
DeleteCommentAbilityHandler,
|
DeleteCommentAbilityHandler,
|
||||||
} from './handlers/comment.ability-handler';
|
} from './handlers/comment.ability-handler';
|
||||||
import {
|
import {
|
||||||
ManageCommentThreadTargetAbilityHandler,
|
ManageActivityTargetAbilityHandler,
|
||||||
ReadCommentThreadTargetAbilityHandler,
|
ReadActivityTargetAbilityHandler,
|
||||||
CreateCommentThreadTargetAbilityHandler,
|
CreateActivityTargetAbilityHandler,
|
||||||
UpdateCommentThreadTargetAbilityHandler,
|
UpdateActivityTargetAbilityHandler,
|
||||||
DeleteCommentThreadTargetAbilityHandler,
|
DeleteActivityTargetAbilityHandler,
|
||||||
} from './handlers/comment-thread-target.ability-handler';
|
} from './handlers/activity-target.ability-handler';
|
||||||
import {
|
import {
|
||||||
ManagePipelineAbilityHandler,
|
ManagePipelineAbilityHandler,
|
||||||
ReadPipelineAbilityHandler,
|
ReadPipelineAbilityHandler,
|
||||||
@ -140,24 +140,24 @@ import {
|
|||||||
CreateRefreshTokenAbilityHandler,
|
CreateRefreshTokenAbilityHandler,
|
||||||
UpdateRefreshTokenAbilityHandler,
|
UpdateRefreshTokenAbilityHandler,
|
||||||
DeleteRefreshTokenAbilityHandler,
|
DeleteRefreshTokenAbilityHandler,
|
||||||
// CommentThread
|
// Activity
|
||||||
ManageCommentThreadAbilityHandler,
|
ManageActivityAbilityHandler,
|
||||||
ReadCommentThreadAbilityHandler,
|
ReadActivityAbilityHandler,
|
||||||
CreateCommentThreadAbilityHandler,
|
CreateActivityAbilityHandler,
|
||||||
UpdateCommentThreadAbilityHandler,
|
UpdateActivityAbilityHandler,
|
||||||
DeleteCommentThreadAbilityHandler,
|
DeleteActivityAbilityHandler,
|
||||||
// Comment
|
// Comment
|
||||||
ManageCommentAbilityHandler,
|
ManageCommentAbilityHandler,
|
||||||
ReadCommentAbilityHandler,
|
ReadCommentAbilityHandler,
|
||||||
CreateCommentAbilityHandler,
|
CreateCommentAbilityHandler,
|
||||||
UpdateCommentAbilityHandler,
|
UpdateCommentAbilityHandler,
|
||||||
DeleteCommentAbilityHandler,
|
DeleteCommentAbilityHandler,
|
||||||
// CommentThreadTarget
|
// ActivityTarget
|
||||||
ManageCommentThreadTargetAbilityHandler,
|
ManageActivityTargetAbilityHandler,
|
||||||
ReadCommentThreadTargetAbilityHandler,
|
ReadActivityTargetAbilityHandler,
|
||||||
CreateCommentThreadTargetAbilityHandler,
|
CreateActivityTargetAbilityHandler,
|
||||||
UpdateCommentThreadTargetAbilityHandler,
|
UpdateActivityTargetAbilityHandler,
|
||||||
DeleteCommentThreadTargetAbilityHandler,
|
DeleteActivityTargetAbilityHandler,
|
||||||
//Attachment
|
//Attachment
|
||||||
ManageAttachmentAbilityHandler,
|
ManageAttachmentAbilityHandler,
|
||||||
ReadAttachmentAbilityHandler,
|
ReadAttachmentAbilityHandler,
|
||||||
@ -224,24 +224,24 @@ import {
|
|||||||
CreateRefreshTokenAbilityHandler,
|
CreateRefreshTokenAbilityHandler,
|
||||||
UpdateRefreshTokenAbilityHandler,
|
UpdateRefreshTokenAbilityHandler,
|
||||||
DeleteRefreshTokenAbilityHandler,
|
DeleteRefreshTokenAbilityHandler,
|
||||||
// CommentThread
|
// Activity
|
||||||
ManageCommentThreadAbilityHandler,
|
ManageActivityAbilityHandler,
|
||||||
ReadCommentThreadAbilityHandler,
|
ReadActivityAbilityHandler,
|
||||||
CreateCommentThreadAbilityHandler,
|
CreateActivityAbilityHandler,
|
||||||
UpdateCommentThreadAbilityHandler,
|
UpdateActivityAbilityHandler,
|
||||||
DeleteCommentThreadAbilityHandler,
|
DeleteActivityAbilityHandler,
|
||||||
// Comment
|
// Comment
|
||||||
ManageCommentAbilityHandler,
|
ManageCommentAbilityHandler,
|
||||||
ReadCommentAbilityHandler,
|
ReadCommentAbilityHandler,
|
||||||
CreateCommentAbilityHandler,
|
CreateCommentAbilityHandler,
|
||||||
UpdateCommentAbilityHandler,
|
UpdateCommentAbilityHandler,
|
||||||
DeleteCommentAbilityHandler,
|
DeleteCommentAbilityHandler,
|
||||||
// CommentThreadTarget
|
// ActivityTarget
|
||||||
ManageCommentThreadTargetAbilityHandler,
|
ManageActivityTargetAbilityHandler,
|
||||||
ReadCommentThreadTargetAbilityHandler,
|
ReadActivityTargetAbilityHandler,
|
||||||
CreateCommentThreadTargetAbilityHandler,
|
CreateActivityTargetAbilityHandler,
|
||||||
UpdateCommentThreadTargetAbilityHandler,
|
UpdateActivityTargetAbilityHandler,
|
||||||
DeleteCommentThreadTargetAbilityHandler,
|
DeleteActivityTargetAbilityHandler,
|
||||||
//Attachment
|
//Attachment
|
||||||
ManageAttachmentAbilityHandler,
|
ManageAttachmentAbilityHandler,
|
||||||
ReadAttachmentAbilityHandler,
|
ReadAttachmentAbilityHandler,
|
||||||
|
|||||||
@ -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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
73
server/src/ability/handlers/activity.ability-handler.ts
Normal file
73
server/src/ability/handlers/activity.ability-handler.ts
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -42,16 +42,12 @@ export class CreateAttachmentAbilityHandler implements IAbilityHandler {
|
|||||||
const args = gqlContext.getArgs<AttachmentArgs>();
|
const args = gqlContext.getArgs<AttachmentArgs>();
|
||||||
assert(args.activityId, '', ForbiddenException);
|
assert(args.activityId, '', ForbiddenException);
|
||||||
|
|
||||||
const activity = await this.prismaService.client.commentThread.findUnique({
|
const activity = await this.prismaService.client.activity.findUnique({
|
||||||
where: { id: args.activityId },
|
where: { id: args.activityId },
|
||||||
include: { workspace: true },
|
|
||||||
});
|
});
|
||||||
assert(activity, '', NotFoundException);
|
assert(activity, '', NotFoundException);
|
||||||
|
|
||||||
return ability.can(
|
return ability.can(AbilityAction.Update, subject('Activity', activity));
|
||||||
AbilityAction.Update,
|
|
||||||
subject('Workspace', activity.workspace),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
server/src/core/activity/activity.module.ts
Normal file
11
server/src/core/activity/activity.module.ts
Normal 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 {}
|
||||||
@ -1,19 +1,19 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
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 { AbilityFactory } from 'src/ability/ability.factory';
|
||||||
|
|
||||||
import { CommentThreadResolver } from './comment-thread.resolver';
|
import { ActivityResolver } from './activity.resolver';
|
||||||
|
|
||||||
describe('CommentThreadResolver', () => {
|
describe('ActivityResolver', () => {
|
||||||
let resolver: CommentThreadResolver;
|
let resolver: ActivityResolver;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
CommentThreadResolver,
|
ActivityResolver,
|
||||||
{
|
{
|
||||||
provide: CommentThreadService,
|
provide: ActivityService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -23,7 +23,7 @@ describe('CommentThreadResolver', () => {
|
|||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
resolver = module.get<CommentThreadResolver>(CommentThreadResolver);
|
resolver = module.get<ActivityResolver>(ActivityResolver);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
156
server/src/core/activity/resolvers/activity.resolver.ts
Normal file
156
server/src/core/activity/resolvers/activity.resolver.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,15 +3,15 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
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', () => {
|
describe('ActivityTargetService', () => {
|
||||||
let service: CommentThreadTargetService;
|
let service: ActivityTargetService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
CommentThreadTargetService,
|
ActivityTargetService,
|
||||||
{
|
{
|
||||||
provide: PrismaService,
|
provide: PrismaService,
|
||||||
useValue: prismaMock,
|
useValue: prismaMock,
|
||||||
@ -19,9 +19,7 @@ describe('CommentThreadTargetService', () => {
|
|||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<CommentThreadTargetService>(
|
service = module.get<ActivityTargetService>(ActivityTargetService);
|
||||||
CommentThreadTargetService,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
40
server/src/core/activity/services/activity-target.service.ts
Normal file
40
server/src/core/activity/services/activity-target.service.ts
Normal 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;
|
||||||
|
}
|
||||||
@ -3,15 +3,15 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { PrismaService } from 'src/database/prisma.service';
|
import { PrismaService } from 'src/database/prisma.service';
|
||||||
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
import { prismaMock } from 'src/database/client-mock/jest-prisma-singleton';
|
||||||
|
|
||||||
import { CommentThreadService } from './comment-thread.service';
|
import { ActivityService } from './activity.service';
|
||||||
|
|
||||||
describe('CommentThreadService', () => {
|
describe('ActivityService', () => {
|
||||||
let service: CommentThreadService;
|
let service: ActivityService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
CommentThreadService,
|
ActivityService,
|
||||||
{
|
{
|
||||||
provide: PrismaService,
|
provide: PrismaService,
|
||||||
useValue: prismaMock,
|
useValue: prismaMock,
|
||||||
@ -19,7 +19,7 @@ describe('CommentThreadService', () => {
|
|||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
service = module.get<CommentThreadService>(CommentThreadService);
|
service = module.get<ActivityService>(ActivityService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
39
server/src/core/activity/services/activity.service.ts
Normal file
39
server/src/core/activity/services/activity.service.ts
Normal 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;
|
||||||
|
}
|
||||||
@ -1,19 +1,10 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { CommentService } from './services/comment.service';
|
import { CommentService } from './comment.service';
|
||||||
import { CommentResolver } from './resolvers/comment.resolver';
|
import { CommentResolver } from './comment.resolver';
|
||||||
import { CommentThreadTargetService } from './services/comment-thread-target.service';
|
|
||||||
import { CommentThreadResolver } from './resolvers/comment-thread.resolver';
|
|
||||||
import { CommentThreadService } from './services/comment-thread.service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [CommentService, CommentResolver],
|
||||||
CommentService,
|
exports: [CommentService],
|
||||||
CommentThreadService,
|
|
||||||
CommentThreadTargetService,
|
|
||||||
CommentResolver,
|
|
||||||
CommentThreadResolver,
|
|
||||||
],
|
|
||||||
exports: [CommentService, CommentThreadService, CommentThreadTargetService],
|
|
||||||
})
|
})
|
||||||
export class CommentModule {}
|
export class CommentModule {}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
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 { AbilityFactory } from 'src/ability/ability.factory';
|
||||||
|
|
||||||
import { CommentResolver } from './comment.resolver';
|
import { CommentResolver } from './comment.resolver';
|
||||||
@ -8,7 +8,7 @@ import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
|||||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||||
import { CreateOneCommentArgs } from 'src/core/@generated/comment/create-one-comment.args';
|
import { CreateOneCommentArgs } from 'src/core/@generated/comment/create-one-comment.args';
|
||||||
import { Comment } from 'src/core/@generated/comment/comment.model';
|
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 {
|
import {
|
||||||
PrismaSelector,
|
PrismaSelector,
|
||||||
PrismaSelect,
|
PrismaSelect,
|
||||||
@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
|
import { CommentService } from 'src/core/comment/comment.service';
|
||||||
import { CommentService } from 'src/core/comment/services/comment.service';
|
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||||
|
|
||||||
import { CompanyRelationsResolver } from './company-relations.resolver';
|
import { CompanyRelationsResolver } from './company-relations.resolver';
|
||||||
import { CompanyService } from './company.service';
|
import { CompanyService } from './company.service';
|
||||||
@ -18,7 +18,7 @@ describe('CompanyRelationsResolver', () => {
|
|||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CommentThreadService,
|
provide: ActivityService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,36 +1,36 @@
|
|||||||
import { Resolver, ResolveField, Root, Int } from '@nestjs/graphql';
|
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 { Comment } from 'src/core/@generated/comment/comment.model';
|
||||||
import { Company } from 'src/core/@generated/company/company.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/comment.service';
|
||||||
import { CommentService } from 'src/core/comment/services/comment.service';
|
|
||||||
import {
|
import {
|
||||||
PrismaSelect,
|
PrismaSelect,
|
||||||
PrismaSelector,
|
PrismaSelector,
|
||||||
} from 'src/decorators/prisma-select.decorator';
|
} 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)
|
@Resolver(() => Company)
|
||||||
export class CompanyRelationsResolver {
|
export class CompanyRelationsResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly commentThreadService: CommentThreadService,
|
private readonly activityService: ActivityService,
|
||||||
private readonly commentService: CommentService,
|
private readonly commentService: CommentService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ResolveField(() => [CommentThread], {
|
@ResolveField(() => [Activity], {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
async commentThreads(
|
async activities(
|
||||||
@Root() company: Company,
|
@Root() company: Company,
|
||||||
@PrismaSelector({ modelName: 'CommentThread' })
|
@PrismaSelector({ modelName: 'Activity' })
|
||||||
prismaSelect: PrismaSelect<'CommentThread'>,
|
prismaSelect: PrismaSelect<'Activity'>,
|
||||||
): Promise<Partial<CommentThread>[]> {
|
): Promise<Partial<Activity>[]> {
|
||||||
return this.commentThreadService.findMany({
|
return this.activityService.findMany({
|
||||||
where: {
|
where: {
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
some: {
|
some: {
|
||||||
commentableId: company.id,
|
|
||||||
commentableType: 'Company',
|
commentableType: 'Company',
|
||||||
|
commentableId: company.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -48,11 +48,11 @@ export class CompanyRelationsResolver {
|
|||||||
): Promise<Partial<Comment>[]> {
|
): Promise<Partial<Comment>[]> {
|
||||||
return this.commentService.findMany({
|
return this.commentService.findMany({
|
||||||
where: {
|
where: {
|
||||||
commentThread: {
|
activity: {
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
some: {
|
some: {
|
||||||
commentableId: company.id,
|
|
||||||
commentableType: 'Company',
|
commentableType: 'Company',
|
||||||
|
commentableId: company.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -64,13 +64,13 @@ export class CompanyRelationsResolver {
|
|||||||
@ResolveField(() => Int, {
|
@ResolveField(() => Int, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
async _commentThreadCount(@Root() company: Company): Promise<number> {
|
async _activityCount(@Root() company: Company): Promise<number> {
|
||||||
return this.commentThreadService.count({
|
return this.activityService.count({
|
||||||
where: {
|
where: {
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
some: {
|
some: {
|
||||||
commentableId: company.id,
|
|
||||||
commentableType: 'Company',
|
commentableType: 'Company',
|
||||||
|
commentableId: company.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { CommentModule } from 'src/core/comment/comment.module';
|
import { CommentModule } from 'src/core/comment/comment.module';
|
||||||
|
import { ActivityModule } from 'src/core/activity/activity.module';
|
||||||
|
|
||||||
import { CompanyService } from './company.service';
|
import { CompanyService } from './company.service';
|
||||||
import { CompanyResolver } from './company.resolver';
|
import { CompanyResolver } from './company.resolver';
|
||||||
import { CompanyRelationsResolver } from './company-relations.resolver';
|
import { CompanyRelationsResolver } from './company-relations.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [CommentModule],
|
imports: [CommentModule, ActivityModule],
|
||||||
providers: [CompanyService, CompanyResolver, CompanyRelationsResolver],
|
providers: [CompanyService, CompanyResolver, CompanyRelationsResolver],
|
||||||
exports: [CompanyService],
|
exports: [CompanyService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { AnalyticsModule } from './analytics/analytics.module';
|
|||||||
import { FileModule } from './file/file.module';
|
import { FileModule } from './file/file.module';
|
||||||
import { ClientConfigModule } from './client-config/client-config.module';
|
import { ClientConfigModule } from './client-config/client-config.module';
|
||||||
import { AttachmentModule } from './attachment/attachment.module';
|
import { AttachmentModule } from './attachment/attachment.module';
|
||||||
|
import { ActivityModule } from './activity/activity.module';
|
||||||
import { ViewModule } from './view/view.module';
|
import { ViewModule } from './view/view.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@ -26,6 +27,7 @@ import { ViewModule } from './view/view.module';
|
|||||||
FileModule,
|
FileModule,
|
||||||
ClientConfigModule,
|
ClientConfigModule,
|
||||||
AttachmentModule,
|
AttachmentModule,
|
||||||
|
ActivityModule,
|
||||||
ViewModule,
|
ViewModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
import { CommentThreadService } from 'src/core/comment/services/comment-thread.service';
|
import { CommentService } from 'src/core/comment/comment.service';
|
||||||
import { CommentService } from 'src/core/comment/services/comment.service';
|
import { ActivityService } from 'src/core/activity/services/activity.service';
|
||||||
|
|
||||||
import { PersonRelationsResolver } from './person-relations.resolver';
|
import { PersonRelationsResolver } from './person-relations.resolver';
|
||||||
import { PersonService } from './person.service';
|
import { PersonService } from './person.service';
|
||||||
@ -18,7 +18,7 @@ describe('PersonRelationsResolver', () => {
|
|||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: CommentThreadService,
|
provide: ActivityService,
|
||||||
useValue: {},
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,36 +1,36 @@
|
|||||||
import { Resolver, Root, ResolveField, Int } from '@nestjs/graphql';
|
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 { Comment } from 'src/core/@generated/comment/comment.model';
|
||||||
import { Person } from 'src/core/@generated/person/person.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/comment.service';
|
||||||
import { CommentService } from 'src/core/comment/services/comment.service';
|
|
||||||
import {
|
import {
|
||||||
PrismaSelect,
|
PrismaSelect,
|
||||||
PrismaSelector,
|
PrismaSelector,
|
||||||
} from 'src/decorators/prisma-select.decorator';
|
} 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)
|
@Resolver(() => Person)
|
||||||
export class PersonRelationsResolver {
|
export class PersonRelationsResolver {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly commentThreadService: CommentThreadService,
|
private readonly activityService: ActivityService,
|
||||||
private readonly commentService: CommentService,
|
private readonly commentService: CommentService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ResolveField(() => [CommentThread], {
|
@ResolveField(() => [Activity], {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
async commentThreads(
|
async activities(
|
||||||
@Root() person: Person,
|
@Root() person: Person,
|
||||||
@PrismaSelector({ modelName: 'CommentThread' })
|
@PrismaSelector({ modelName: 'Activity' })
|
||||||
prismaSelect: PrismaSelect<'CommentThread'>,
|
prismaSelect: PrismaSelect<'Activity'>,
|
||||||
): Promise<Partial<CommentThread>[]> {
|
): Promise<Partial<Activity>[]> {
|
||||||
return await this.commentThreadService.findMany({
|
return await this.activityService.findMany({
|
||||||
where: {
|
where: {
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
some: {
|
some: {
|
||||||
commentableId: person.id,
|
|
||||||
commentableType: 'Person',
|
commentableType: 'Person',
|
||||||
|
commentableId: person.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -48,11 +48,11 @@ export class PersonRelationsResolver {
|
|||||||
): Promise<Partial<Comment>[]> {
|
): Promise<Partial<Comment>[]> {
|
||||||
return this.commentService.findMany({
|
return this.commentService.findMany({
|
||||||
where: {
|
where: {
|
||||||
commentThread: {
|
activity: {
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
some: {
|
some: {
|
||||||
commentableId: person.id,
|
|
||||||
commentableType: 'Person',
|
commentableType: 'Person',
|
||||||
|
commentableId: person.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -64,13 +64,13 @@ export class PersonRelationsResolver {
|
|||||||
@ResolveField(() => Int, {
|
@ResolveField(() => Int, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
async _commentThreadCount(@Root() person: Person): Promise<number> {
|
async _activityCount(@Root() person: Person): Promise<number> {
|
||||||
return this.commentThreadService.count({
|
return this.activityService.count({
|
||||||
where: {
|
where: {
|
||||||
commentThreadTargets: {
|
activityTargets: {
|
||||||
some: {
|
some: {
|
||||||
commentableId: person.id,
|
|
||||||
commentableType: 'Person',
|
commentableType: 'Person',
|
||||||
|
commentableId: person.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { CommentModule } from 'src/core/comment/comment.module';
|
import { CommentModule } from 'src/core/comment/comment.module';
|
||||||
|
import { ActivityModule } from 'src/core/activity/activity.module';
|
||||||
|
|
||||||
import { PersonService } from './person.service';
|
import { PersonService } from './person.service';
|
||||||
import { PersonResolver } from './person.resolver';
|
import { PersonResolver } from './person.resolver';
|
||||||
import { PersonRelationsResolver } from './person-relations.resolver';
|
import { PersonRelationsResolver } from './person-relations.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [CommentModule, CommentModule],
|
imports: [CommentModule, ActivityModule],
|
||||||
providers: [PersonService, PersonResolver, PersonRelationsResolver],
|
providers: [PersonService, PersonResolver, PersonRelationsResolver],
|
||||||
exports: [PersonService],
|
exports: [PersonService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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 { CreateOnePersonArgs } from 'src/core/@generated/person/create-one-person.args';
|
||||||
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
import { AffectedRows } from 'src/core/@generated/prisma/affected-rows.output';
|
||||||
import { DeleteManyPersonArgs } from 'src/core/@generated/person/delete-many-person.args';
|
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 { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||||
import {
|
import {
|
||||||
PrismaSelect,
|
PrismaSelect,
|
||||||
@ -34,6 +33,7 @@ import {
|
|||||||
} from 'src/ability/handlers/person.ability-handler';
|
} from 'src/ability/handlers/person.ability-handler';
|
||||||
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
import { UserAbility } from 'src/decorators/user-ability.decorator';
|
||||||
import { AppAbility } from 'src/ability/ability.factory';
|
import { AppAbility } from 'src/ability/ability.factory';
|
||||||
|
import { Workspace } from 'src/core/@generated/workspace/workspace.model';
|
||||||
|
|
||||||
import { PersonService } from './person.service';
|
import { PersonService } from './person.service';
|
||||||
|
|
||||||
|
|||||||
@ -109,11 +109,11 @@ export class WorkspaceService {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
attachment,
|
attachment,
|
||||||
comment,
|
comment,
|
||||||
commentThreadTarget,
|
activityTarget,
|
||||||
commentThread,
|
activity,
|
||||||
} = this.prismaService.client;
|
} = this.prismaService.client;
|
||||||
|
|
||||||
const commentThreads = await commentThread.findMany({
|
const activitys = await activity.findMany({
|
||||||
where: { authorId: userId },
|
where: { authorId: userId },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -142,12 +142,12 @@ export class WorkspaceService {
|
|||||||
comment.deleteMany({
|
comment.deleteMany({
|
||||||
where,
|
where,
|
||||||
}),
|
}),
|
||||||
...commentThreads.map(({ id: commentThreadId }) =>
|
...activitys.map(({ id: activityId }) =>
|
||||||
commentThreadTarget.deleteMany({
|
activityTarget.deleteMany({
|
||||||
where: { commentThreadId },
|
where: { activityId },
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
commentThread.deleteMany({
|
activity.deleteMany({
|
||||||
where,
|
where,
|
||||||
}),
|
}),
|
||||||
refreshToken.deleteMany({
|
refreshToken.deleteMany({
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
-- Create the new tables first, without any foreign key constraints
|
|
||||||
-- Activities Table
|
-- Activities Table
|
||||||
CREATE TABLE "activities" (
|
CREATE TABLE "activities" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
|
|||||||
@ -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;
|
||||||
@ -20,20 +20,20 @@ generator nestgraphql {
|
|||||||
fields_Validator_from = "class-validator"
|
fields_Validator_from = "class-validator"
|
||||||
|
|
||||||
// All relations, only allow connect
|
// 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_field = "*(create|connectOrCreate|update|upsert|delete|createMany|updateMany|deleteMany)"
|
||||||
decorate_all_name = "HideField"
|
decorate_all_name = "HideField"
|
||||||
decorate_all_from = "@nestjs/graphql"
|
decorate_all_from = "@nestjs/graphql"
|
||||||
decorate_all_arguments = "[]"
|
decorate_all_arguments = "[]"
|
||||||
|
|
||||||
// CommentThread: Only Allow targets createOrConnect / createMany
|
// Activity: Only Allow targets createOrConnect / createMany
|
||||||
decorate_commentThreadTargets_type = "*CommentThreadTarget*Input"
|
decorate_activityTargets_type = "*ActivityTarget*Input"
|
||||||
decorate_commentThreadTargets_field = "*(update|upsert|updateMany)"
|
decorate_activityTargets_field = "*(update|upsert|updateMany)"
|
||||||
decorate_commentThreadTargets_name = "HideField"
|
decorate_activityTargets_name = "HideField"
|
||||||
decorate_commentThreadTargets_from = "@nestjs/graphql"
|
decorate_activityTargets_from = "@nestjs/graphql"
|
||||||
decorate_commentThreadTargets_arguments = "[]"
|
decorate_activityTargets_arguments = "[]"
|
||||||
|
|
||||||
// CommentThread: Only Allow targets createOrConnect / createMany
|
// User Settings: Only Allow targets createOrConnect / createMany
|
||||||
decorate_userSettings_type = "*UserSettingsUpdateOneRequiredWithoutUserNestedInput"
|
decorate_userSettings_type = "*UserSettingsUpdateOneRequiredWithoutUserNestedInput"
|
||||||
decorate_userSettings_field = "!(update)"
|
decorate_userSettings_field = "!(update)"
|
||||||
decorate_userSettings_name = "HideField"
|
decorate_userSettings_name = "HideField"
|
||||||
@ -106,10 +106,10 @@ model User {
|
|||||||
refreshTokens RefreshToken[]
|
refreshTokens RefreshToken[]
|
||||||
comments Comment[]
|
comments Comment[]
|
||||||
|
|
||||||
authoredCommentThreads CommentThread[] @relation(name: "authoredCommentThreads")
|
authoredActivities Activity[] @relation(name: "authoredActivities")
|
||||||
assignedCommentThreads CommentThread[] @relation(name: "assignedCommentThreads")
|
assignedActivities Activity[] @relation(name: "assignedActivities")
|
||||||
settings UserSettings @relation(fields: [settingsId], references: [id])
|
settings UserSettings @relation(fields: [settingsId], references: [id])
|
||||||
settingsId String @unique
|
settingsId String @unique
|
||||||
|
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
@ -164,20 +164,20 @@ model Workspace {
|
|||||||
workspaceMember WorkspaceMember[]
|
workspaceMember WorkspaceMember[]
|
||||||
companies Company[]
|
companies Company[]
|
||||||
people Person[]
|
people Person[]
|
||||||
commentThreads CommentThread[]
|
activities Activity[]
|
||||||
comments Comment[]
|
comments Comment[]
|
||||||
pipelines Pipeline[]
|
pipelines Pipeline[]
|
||||||
pipelineStages PipelineStage[]
|
pipelineStages PipelineStage[]
|
||||||
pipelineProgresses PipelineProgress[]
|
pipelineProgresses PipelineProgress[]
|
||||||
|
activityTargets ActivityTarget[]
|
||||||
viewFields ViewField[]
|
viewFields ViewField[]
|
||||||
|
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
Attachment Attachment[]
|
Attachment Attachment[]
|
||||||
CommentThreadTarget CommentThreadTarget[]
|
|
||||||
|
|
||||||
@@map("workspaces")
|
@@map("workspaces")
|
||||||
}
|
}
|
||||||
@ -234,8 +234,9 @@ model Company {
|
|||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
ActivityTarget ActivityTarget[]
|
||||||
|
|
||||||
@@map("companies")
|
@@map("companies")
|
||||||
}
|
}
|
||||||
@ -277,8 +278,9 @@ model Person {
|
|||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
ActivityTarget ActivityTarget[]
|
||||||
|
|
||||||
@@map("people")
|
@@map("people")
|
||||||
}
|
}
|
||||||
@ -311,30 +313,28 @@ enum ActivityType {
|
|||||||
Task
|
Task
|
||||||
}
|
}
|
||||||
|
|
||||||
model CommentThread {
|
model Activity {
|
||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
/// @Validator.IsOptional()
|
/// @Validator.IsOptional()
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
body String?
|
||||||
commentThreadTargets CommentThreadTarget[]
|
title String?
|
||||||
comments Comment[]
|
type ActivityType @default(Note)
|
||||||
/// @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)
|
|
||||||
|
|
||||||
reminderAt DateTime?
|
reminderAt DateTime?
|
||||||
dueAt DateTime?
|
dueAt DateTime?
|
||||||
completedAt 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)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
@ -342,9 +342,12 @@ model CommentThread {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
attachments Attachment[]
|
@@map("activities")
|
||||||
|
}
|
||||||
|
|
||||||
@@map("comment_threads")
|
enum CommentableType {
|
||||||
|
Person
|
||||||
|
Company
|
||||||
}
|
}
|
||||||
|
|
||||||
model Comment {
|
model Comment {
|
||||||
@ -354,12 +357,13 @@ model Comment {
|
|||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
body String
|
body String
|
||||||
|
|
||||||
author User @relation(fields: [authorId], references: [id])
|
author User @relation(fields: [authorId], references: [id])
|
||||||
authorId String
|
authorId String
|
||||||
commentThread CommentThread @relation(fields: [commentThreadId], references: [id], onDelete: Cascade)
|
activity Activity? @relation(fields: [activityId], references: [id], onDelete: Cascade)
|
||||||
|
activityId String?
|
||||||
commentThreadId String
|
commentThreadId String
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @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)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
workspaceId String
|
workspaceId String
|
||||||
|
|
||||||
@ -372,24 +376,25 @@ model Comment {
|
|||||||
@@map("comments")
|
@@map("comments")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CommentableType {
|
model ActivityTarget {
|
||||||
Person
|
|
||||||
Company
|
|
||||||
}
|
|
||||||
|
|
||||||
model CommentThreadTarget {
|
|
||||||
/// @Validator.IsString()
|
/// @Validator.IsString()
|
||||||
/// @Validator.IsOptional()
|
/// @Validator.IsOptional()
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
|
|
||||||
commentThread CommentThread @relation(fields: [commentThreadId], references: [id], onDelete: Cascade)
|
activity Activity @relation(fields: [activityId], references: [id], onDelete: Cascade)
|
||||||
commentThreadId String
|
activityId String
|
||||||
/// @TypeGraphQL.omit(input: true, output: false)
|
commentableType CommentableType?
|
||||||
workspace Workspace? @relation(fields: [workspaceId], references: [id])
|
commentableId String?
|
||||||
/// @TypeGraphQL.omit(input: true, output: true)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
workspaceId String?
|
workspace Workspace @relation(fields: [workspaceId], references: [id])
|
||||||
commentableType CommentableType
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
commentableId String
|
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)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
@ -397,7 +402,7 @@ model CommentThreadTarget {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
@@map("comment_thread_targets")
|
@@map("activity_targets")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Pipeline {
|
model Pipeline {
|
||||||
@ -515,17 +520,16 @@ model Attachment {
|
|||||||
type AttachmentType
|
type AttachmentType
|
||||||
name String
|
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)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
workspaceId String
|
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)
|
/// @TypeGraphQL.omit(input: true, output: true)
|
||||||
deletedAt DateTime?
|
deletedAt DateTime?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
export const seedComments = async (prisma: PrismaClient) => {
|
export const seedComments = async (prisma: PrismaClient) => {
|
||||||
await prisma.commentThread.upsert({
|
await prisma.activity.upsert({
|
||||||
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400' },
|
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400' },
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
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' },
|
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb600' },
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb600',
|
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb600',
|
||||||
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
|
||||||
commentableType: 'Company',
|
commentableType: 'Company',
|
||||||
commentableId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
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',
|
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb200',
|
||||||
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||||
body: 'Hi Félix ! How do you like your Twenty workspace?',
|
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',
|
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
|
||||||
authorId: 'twenty-ge256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
authorId: 'twenty-ge256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||||
},
|
},
|
||||||
@ -44,12 +45,13 @@ export const seedComments = async (prisma: PrismaClient) => {
|
|||||||
id: 'twenty-fe256b40-3ec3-4fe3-8997-b76aa0bfb200',
|
id: 'twenty-fe256b40-3ec3-4fe3-8997-b76aa0bfb200',
|
||||||
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||||
body: 'I love it!',
|
body: 'I love it!',
|
||||||
|
activityId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
|
||||||
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
|
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb400',
|
||||||
authorId: 'twenty-gk256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
authorId: 'twenty-gk256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.commentThread.upsert({
|
await prisma.activity.upsert({
|
||||||
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408' },
|
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408' },
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
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' },
|
where: { id: 'twenty-fe256b39-3ec3-4fe3-8997-a76aa0bfb600' },
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
id: 'twenty-fe256b39-3ec3-4fe3-8997-a76aa0bfb600',
|
id: 'twenty-fe256b39-3ec3-4fe3-8997-a76aa0bfb600',
|
||||||
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
|
||||||
commentableType: 'Person',
|
commentableType: 'Person',
|
||||||
commentableId: 'twenty-755035db-623d-41fe-92e7-dd45b7c568e1',
|
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',
|
id: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfb100',
|
||||||
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
workspaceId: 'twenty-7ed9d212-1c25-4d02-bf25-6aeccf7ea419',
|
||||||
body: 'I really like this comment thread feature!',
|
body: 'I really like this comment thread feature!',
|
||||||
|
activityId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
|
||||||
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
|
commentThreadId: 'twenty-fe256b39-3ec3-4fe3-8997-b76aa0bfc408',
|
||||||
authorId: 'twenty-ge256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
authorId: 'twenty-ge256b39-3ec3-4fe3-8997-b76aa0bfa408',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await prisma.commentThread.upsert({
|
await prisma.activity.upsert({
|
||||||
where: { id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408' },
|
where: { id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408' },
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
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' },
|
where: { id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-a76aa0bfba00' },
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-a76aa0bfba00',
|
id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-a76aa0bfba00',
|
||||||
workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
|
|
||||||
commentableType: 'Company',
|
commentableType: 'Company',
|
||||||
commentableId: 'twenty-dev-a674fa6c-1455-4c57-afaf-dd5dc086361e',
|
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',
|
id: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aa0bfb000',
|
||||||
workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
|
workspaceId: 'twenty-dev-7ed9d212-1c25-4d02-bf25-6aeccf7ea420',
|
||||||
body: 'I really like this comment thread feature!',
|
body: 'I really like this comment thread feature!',
|
||||||
|
activityId: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408',
|
||||||
commentThreadId: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408',
|
commentThreadId: 'twenty-dev-fe256b39-3ec3-4fe3-8997-b76aaabfb408',
|
||||||
authorId: 'twenty-dev-gk256b39-3ec3-4fe3-8997-b76aa0boa408',
|
authorId: 'twenty-dev-gk256b39-3ec3-4fe3-8997-b76aa0boa408',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -9,9 +9,9 @@ export type ModelSelectMap = {
|
|||||||
Company: Prisma.CompanySelect;
|
Company: Prisma.CompanySelect;
|
||||||
Person: Prisma.PersonSelect;
|
Person: Prisma.PersonSelect;
|
||||||
RefreshToken: Prisma.RefreshTokenSelect;
|
RefreshToken: Prisma.RefreshTokenSelect;
|
||||||
CommentThread: Prisma.CommentThreadSelect;
|
Activity: Prisma.ActivitySelect;
|
||||||
Comment: Prisma.CommentSelect;
|
Comment: Prisma.CommentSelect;
|
||||||
CommentThreadTarget: Prisma.CommentThreadTargetSelect;
|
ActivityTarget: Prisma.ActivityTargetSelect;
|
||||||
Pipeline: Prisma.PipelineSelect;
|
Pipeline: Prisma.PipelineSelect;
|
||||||
PipelineStage: Prisma.PipelineStageSelect;
|
PipelineStage: Prisma.PipelineStageSelect;
|
||||||
PipelineProgress: Prisma.PipelineProgressSelect;
|
PipelineProgress: Prisma.PipelineProgressSelect;
|
||||||
|
|||||||
Reference in New Issue
Block a user