feat: rename comment thread into activity (#939)

* feat: rename commentThread into activity server

* feat: rename commentThread into activity front

* feat: migration only create tables


feat: migration only create tables

* Update activities

* fix: rebase partial fix

* fix: all rebase problems and drop activity target alter

* fix: lint

* Update migration

* Update migration

* Fix conflicts

* Fix conflicts

---------

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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