From afa10d7195224a65de9411d3a0ffb9eabddfe135 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 25 Sep 2023 13:18:03 +0530 Subject: [PATCH 01/13] fix: sub issue state and member select build error (#2254) --- web/components/issues/sub-issues/properties.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/components/issues/sub-issues/properties.tsx b/web/components/issues/sub-issues/properties.tsx index 4f2dca43b..e2caefffa 100644 --- a/web/components/issues/sub-issues/properties.tsx +++ b/web/components/issues/sub-issues/properties.tsx @@ -161,6 +161,7 @@ export const IssueProperty: React.FC = ({
= ({
Date: Mon, 25 Sep 2023 13:38:49 +0530 Subject: [PATCH 02/13] rename view to layout (#2255) Co-authored-by: Your Name --- web/components/core/filters/issues-view-filter.tsx | 2 +- web/components/issues/my-issues/my-issues-view-options.tsx | 2 +- web/components/profile/profile-issues-view-options.tsx | 2 +- .../[workspaceSlug]/projects/[projectId]/modules/index.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/components/core/filters/issues-view-filter.tsx b/web/components/core/filters/issues-view-filter.tsx index afb7eb2b0..f5b0f477a 100644 --- a/web/components/core/filters/issues-view-filter.tsx +++ b/web/components/core/filters/issues-view-filter.tsx @@ -93,7 +93,7 @@ export const IssuesFilterView: React.FC = () => { {replaceUnderscoreIfSnakeCase(option.type)} View + {replaceUnderscoreIfSnakeCase(option.type)} Layout } position="bottom" > diff --git a/web/components/issues/my-issues/my-issues-view-options.tsx b/web/components/issues/my-issues/my-issues-view-options.tsx index 90d9b8971..549a00df2 100644 --- a/web/components/issues/my-issues/my-issues-view-options.tsx +++ b/web/components/issues/my-issues/my-issues-view-options.tsx @@ -49,7 +49,7 @@ export const MyIssuesViewOptions: React.FC = () => { {replaceUnderscoreIfSnakeCase(option.type)} View + {replaceUnderscoreIfSnakeCase(option.type)} Layout } position="bottom" > diff --git a/web/components/profile/profile-issues-view-options.tsx b/web/components/profile/profile-issues-view-options.tsx index 4f36faa04..30dcb8df4 100644 --- a/web/components/profile/profile-issues-view-options.tsx +++ b/web/components/profile/profile-issues-view-options.tsx @@ -81,7 +81,7 @@ export const ProfileIssuesViewOptions: React.FC = () => { {replaceUnderscoreIfSnakeCase(option.type)} View + {replaceUnderscoreIfSnakeCase(option.type)} Layout } position="bottom" > diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index 6e7e1bca4..028e95540 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -96,7 +96,7 @@ const ProjectModules: NextPage = () => { {replaceUnderscoreIfSnakeCase(option.type)} View + {replaceUnderscoreIfSnakeCase(option.type)} Layout } position="bottom" > From de7a672b799717dba522c6e9285bc8d95e8024a8 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:15:49 +0530 Subject: [PATCH 03/13] fix: bug fixes and ui improvement (#2250) --- web/components/cycles/single-cycle-list.tsx | 6 +- web/components/labels/single-label.tsx | 78 +++++++++++-------- .../[workspaceSlug]/me/profile/index.tsx | 2 + web/pages/[workspaceSlug]/settings/index.tsx | 10 +-- 4 files changed, 57 insertions(+), 39 deletions(-) diff --git a/web/components/cycles/single-cycle-list.tsx b/web/components/cycles/single-cycle-list.tsx index ec01da9e7..a4c21128a 100644 --- a/web/components/cycles/single-cycle-list.tsx +++ b/web/components/cycles/single-cycle-list.tsx @@ -149,6 +149,10 @@ export const SingleCycleList: React.FC = ({ color: group.color, })); + const completedIssues = cycle.completed_issues + cycle.cancelled_issues; + + const percentage = cycle.total_issues > 0 ? (completedIssues / cycle.total_issues) * 100 : 0; + return (
@@ -307,7 +311,7 @@ export const SingleCycleList: React.FC = ({ ) : cycleStatus === "completed" ? ( - {100} % + {Math.round(percentage)} % ) : ( diff --git a/web/components/labels/single-label.tsx b/web/components/labels/single-label.tsx index be981e510..c163a3735 100644 --- a/web/components/labels/single-label.tsx +++ b/web/components/labels/single-label.tsx @@ -1,11 +1,13 @@ -import React from "react"; +import React, { useRef, useState } from "react"; +//hook +import useOutsideClickDetector from "hooks/use-outside-click-detector"; // ui import { CustomMenu } from "components/ui"; // types import { IIssueLabels } from "types"; //icons -import { RectangleGroupIcon, PencilIcon } from "@heroicons/react/24/outline"; +import { PencilIcon } from "@heroicons/react/24/outline"; import { Component, X } from "lucide-react"; type Props = { @@ -20,9 +22,14 @@ export const SingleLabel: React.FC = ({ addLabelToGroup, editLabel, handleLabelDelete, -}) => ( -
-
+}) => { + const [isMenuActive, setIsMenuActive] = useState(false); + const actionSectionRef = useRef(null); + + useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); + + return ( +
= ({ />
{label.name}
-
-
- - -
- } +
+ setIsMenuActive(!isMenuActive)}> + +
+ } + > + addLabelToGroup(label)}> + + + Convert to group + + + editLabel(label)}> + + + Edit label + + + +
+
- -
-
-
-); + ); +}; diff --git a/web/pages/[workspaceSlug]/me/profile/index.tsx b/web/pages/[workspaceSlug]/me/profile/index.tsx index c536c384a..40ba8ecf3 100644 --- a/web/pages/[workspaceSlug]/me/profile/index.tsx +++ b/web/pages/[workspaceSlug]/me/profile/index.tsx @@ -253,6 +253,7 @@ const Profile: NextPage = () => { placeholder="Enter your first name" className="!px-3 !py-2 rounded-md font-medium" autoComplete="off" + maxLength={24} />
@@ -266,6 +267,7 @@ const Profile: NextPage = () => { placeholder="Enter your last name" autoComplete="off" className="!px-3 !py-2 rounded-md font-medium" + maxLength={24} />
diff --git a/web/pages/[workspaceSlug]/settings/index.tsx b/web/pages/[workspaceSlug]/settings/index.tsx index 4f576aa0a..7d0d8b1b6 100644 --- a/web/pages/[workspaceSlug]/settings/index.tsx +++ b/web/pages/[workspaceSlug]/settings/index.tsx @@ -337,10 +337,10 @@ const WorkspaceSettings: NextPage = () => {
- The danger zone of the project delete page is a critical area that - requires careful consideration and attention. When deleting a project, all - of the data and resources within that project will be permanently removed - and cannot be recovered. + The danger zone of the workspace delete page is a critical area that + requires careful consideration and attention. When deleting a workspace, + all of the data and resources within that workspace will be permanently + removed and cannot be recovered.
{ className="!text-sm" outline > - Delete my project + Delete my workspace
From 5e8d523ed40b7cff5d78dc37d33fee260a5370cb Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Mon, 25 Sep 2023 19:08:26 +0530 Subject: [PATCH 04/13] feat: quick-add placement in spreadsheet and gantt (#2259) * feat: sticking quick-add at the bottom of the screen fix: opening create issue modal instead of quick-add in draft-issues, my-issue and profile page * fix: build error due to dynamic import --- .../core/views/board-view/single-board.tsx | 10 ++- .../views/inline-issue-create-wrapper.tsx | 9 -- .../list-view/inline-create-issue-form.tsx | 19 ++-- .../core/views/list-view/single-list.tsx | 15 +++- .../spreadsheet-view/spreadsheet-view.tsx | 86 +++++++++---------- web/components/gantt-chart/sidebar.tsx | 45 +++++----- .../profile/profile-issues-view.tsx | 3 +- web/layouts/app-layout/app-sidebar.tsx | 13 ++- .../projects/[projectId]/cycles/[cycleId].tsx | 10 ++- .../[projectId]/modules/[moduleId].tsx | 6 +- 10 files changed, 121 insertions(+), 95 deletions(-) diff --git a/web/components/core/views/board-view/single-board.tsx b/web/components/core/views/board-view/single-board.tsx index 4226c3091..8f851527d 100644 --- a/web/components/core/views/board-view/single-board.tsx +++ b/web/components/core/views/board-view/single-board.tsx @@ -63,6 +63,10 @@ export const SingleBoard: React.FC = (props) => { const router = useRouter(); const { cycleId, moduleId } = router.query; + const isMyIssuesPage = router.pathname.split("/")[3] === "my-issues"; + const isProfileIssuesPage = router.pathname.split("/")[2] === "profile"; + const isDraftIssuesPage = router.pathname.split("/")[4] === "draft-issues"; + const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; // Check if it has at least 4 tickets since it is enough to accommodate the Calendar height @@ -214,7 +218,11 @@ export const SingleBoard: React.FC = (props) => {
{spreadsheetIssues ? ( -
+
{spreadsheetIssues.map((issue: IIssue, index) => ( = ({ /> ))} - setIsInlineCreateIssueFormOpen(false)} - prePopulatedData={{ - ...(cycleId && { cycle: cycleId.toString() }), - ...(moduleId && { module: moduleId.toString() }), - }} - /> +
+ setIsInlineCreateIssueFormOpen(false)} + prePopulatedData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + }} + /> -
- {!isInlineCreateIssueFormOpen && ( - <> - {type === "issue" ? ( + {type === "issue" + ? !disableUserActions && + !isInlineCreateIssueFormOpen && ( - ) : ( - !disableUserActions && ( - - - Add Issue - - } - position="left" - optionsClassName="left-5 !w-36" - noBorder - > - setIsInlineCreateIssueFormOpen(true)}> - Create new + ) + : !disableUserActions && + !isInlineCreateIssueFormOpen && ( + + + Add Issue + + } + position="left" + verticalPosition="top" + optionsClassName="left-5 !w-36" + noBorder + > + setIsInlineCreateIssueFormOpen(true)}> + Create new + + {openIssuesListModal && ( + + Add an existing issue - {openIssuesListModal && ( - - Add an existing issue - - )} - - ) + )} + )} - - )}
) : ( diff --git a/web/components/gantt-chart/sidebar.tsx b/web/components/gantt-chart/sidebar.tsx index 0d90ffdd0..35b253ef9 100644 --- a/web/components/gantt-chart/sidebar.tsx +++ b/web/components/gantt-chart/sidebar.tsx @@ -155,31 +155,32 @@ export const GanttSidebar: React.FC = (props) => { )} {droppableProvided.placeholder} - - setIsCreateIssueFormOpen(false)} - prePopulatedData={{ - start_date: new Date(Date.now()).toISOString().split("T")[0], - target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0], - ...(cycleId && { cycle: cycleId.toString() }), - ...(moduleId && { module: moduleId.toString() }), - }} - /> - - {!isCreateIssueFormOpen && ( - - )}
)} +
+ setIsCreateIssueFormOpen(false)} + prePopulatedData={{ + start_date: new Date(Date.now()).toISOString().split("T")[0], + target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0], + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + }} + /> + + {!isCreateIssueFormOpen && ( + + )} +
); }; diff --git a/web/components/profile/profile-issues-view.tsx b/web/components/profile/profile-issues-view.tsx index 035e8b990..9d6a53ffe 100644 --- a/web/components/profile/profile-issues-view.tsx +++ b/web/components/profile/profile-issues-view.tsx @@ -227,7 +227,8 @@ export const ProfileIssuesView = () => { router.pathname.includes("my-issues")) ?? false; - const disableAddIssueOption = isSubscribedIssuesRoute || isMySubscribedIssues; + const disableAddIssueOption = + isSubscribedIssuesRoute || isMySubscribedIssues || user?.id !== userId; return ( <> diff --git a/web/layouts/app-layout/app-sidebar.tsx b/web/layouts/app-layout/app-sidebar.tsx index 03ac72387..19878458a 100644 --- a/web/layouts/app-layout/app-sidebar.tsx +++ b/web/layouts/app-layout/app-sidebar.tsx @@ -1,3 +1,4 @@ +import dynamic from "next/dynamic"; // hooks import useTheme from "hooks/use-theme"; // components @@ -5,8 +6,18 @@ import { WorkspaceHelpSection, WorkspaceSidebarDropdown, WorkspaceSidebarMenu, - WorkspaceSidebarQuickAction, } from "components/workspace"; + +const WorkspaceSidebarQuickAction = dynamic<{}>( + () => + import("components/workspace/sidebar-quick-action").then( + (mod) => mod.WorkspaceSidebarQuickAction + ), + { + ssr: false, + } +); + import { ProjectSidebarList } from "components/project"; import { PublishProjectModal } from "components/project/publish-project/modal"; import { ConfirmProjectLeaveModal } from "components/project/confirm-project-leave-modal"; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index de6ad561e..f29629384 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -189,10 +189,12 @@ const SingleCycle: React.FC = () => { {cycleStatus === "completed" && ( setTransferIssuesModal(true)} /> )} - +
+ +
{ onClose={() => setAnalyticsModal(false)} />
From 7db78594dc4fa7ff205f08960ad81db59d43a0ef Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Mon, 25 Sep 2023 19:11:10 +0530 Subject: [PATCH 05/13] fix: draft issue delete not working (#2249) * fix: draft issue not deleting, project can't be changed in draft issue modal * fix: removed mutation for view where draft issues are not shown * fix: inline create issue for draft issue * fix: clearing data from localstorage on discard click --- .../core/views/inline-issue-create-wrapper.tsx | 16 ++++++++++++++-- .../issues/delete-draft-issue-modal.tsx | 9 ++++++--- web/components/issues/draft-issue-form.tsx | 4 +++- web/components/issues/draft-issue-modal.tsx | 18 +++++++----------- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/web/components/core/views/inline-issue-create-wrapper.tsx b/web/components/core/views/inline-issue-create-wrapper.tsx index 808cca7d8..3c01c50ce 100644 --- a/web/components/core/views/inline-issue-create-wrapper.tsx +++ b/web/components/core/views/inline-issue-create-wrapper.tsx @@ -39,6 +39,7 @@ import { CYCLE_DETAILS, MODULE_DETAILS, PROJECT_ISSUES_LIST_WITH_PARAMS, + PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS, } from "constants/fetch-keys"; // types @@ -119,6 +120,8 @@ export const InlineCreateIssueFormWrapper: React.FC = (props) => { const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; + const isDraftIssues = router.pathname?.split("/")?.[4] === "draft-issues"; + const { user } = useUser(); const { setToastAlert } = useToast(); @@ -184,8 +187,15 @@ export const InlineCreateIssueFormWrapper: React.FC = (props) => { reset({ ...defaultValues }); - await issuesService - .createIssues(workspaceSlug.toString(), projectId.toString(), formData, user) + await (!isDraftIssues + ? issuesService.createIssues(workspaceSlug.toString(), projectId.toString(), formData, user) + : issuesService.createDraftIssue( + workspaceSlug.toString(), + projectId.toString(), + formData, + user + ) + ) .then(async (res) => { mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params)); if (formData.cycle && formData.cycle !== "") @@ -207,6 +217,8 @@ export const InlineCreateIssueFormWrapper: React.FC = (props) => { params ); + if (isDraftIssues) + mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params)); if (displayFilters.layout === "calendar") mutate(calendarFetchKey); if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey); if (displayFilters.layout === "spreadsheet") mutate(spreadsheetFetchKey); diff --git a/web/components/issues/delete-draft-issue-modal.tsx b/web/components/issues/delete-draft-issue-modal.tsx index 6fc4f4218..8347f555b 100644 --- a/web/components/issues/delete-draft-issue-modal.tsx +++ b/web/components/issues/delete-draft-issue-modal.tsx @@ -4,6 +4,8 @@ import { useRouter } from "next/router"; import { mutate } from "swr"; +import useUser from "hooks/use-user"; + // headless ui import { Dialog, Transition } from "@headlessui/react"; // services @@ -16,7 +18,7 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // ui import { SecondaryButton, DangerButton } from "components/ui"; // types -import type { IIssue, ICurrentUserResponse } from "types"; +import type { IIssue } from "types"; // fetch-keys import { PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS } from "constants/fetch-keys"; @@ -24,12 +26,11 @@ type Props = { isOpen: boolean; handleClose: () => void; data: IIssue | null; - user?: ICurrentUserResponse; onSubmit?: () => Promise | void; }; export const DeleteDraftIssueModal: React.FC = (props) => { - const { isOpen, handleClose, data, user, onSubmit } = props; + const { isOpen, handleClose, data, onSubmit } = props; const [isDeleteLoading, setIsDeleteLoading] = useState(false); @@ -40,6 +41,8 @@ export const DeleteDraftIssueModal: React.FC = (props) => { const { setToastAlert } = useToast(); + const { user } = useUser(); + useEffect(() => { setIsDeleteLoading(false); }, [isOpen]); diff --git a/web/components/issues/draft-issue-form.tsx b/web/components/issues/draft-issue-form.tsx index aac5dede7..7433da82c 100644 --- a/web/components/issues/draft-issue-form.tsx +++ b/web/components/issues/draft-issue-form.tsx @@ -66,6 +66,7 @@ interface IssueFormProps { createMore: boolean; setCreateMore: React.Dispatch>; handleClose: () => void; + handleDiscard: () => void; status: boolean; user: ICurrentUserResponse | undefined; fieldsToShow: ( @@ -97,6 +98,7 @@ export const DraftIssueForm: FC = (props) => { status, user, fieldsToShow, + handleDiscard, } = props; const [stateModal, setStateModal] = useState(false); @@ -569,7 +571,7 @@ export const DraftIssueForm: FC = (props) => { {}} size="md" />
- Discard + Discard diff --git a/web/components/issues/draft-issue-modal.tsx b/web/components/issues/draft-issue-modal.tsx index c060c72f6..b6479d067 100644 --- a/web/components/issues/draft-issue-modal.tsx +++ b/web/components/issues/draft-issue-modal.tsx @@ -97,6 +97,11 @@ export const CreateUpdateDraftIssueModal: React.FC = (props) = setActiveProject(null); }; + const onDiscard = () => { + clearDraftIssueLocalStorage(); + onClose(); + }; + useEffect(() => { setPreloadedData(prePopulateDataProps ?? {}); @@ -141,7 +146,7 @@ export const CreateUpdateDraftIssueModal: React.FC = (props) = if (prePopulateData && prePopulateData.project && !activeProject) return setActiveProject(prePopulateData.project); - if (prePopulateData && prePopulateData.project) + if (prePopulateData && prePopulateData.project && !activeProject) return setActiveProject(prePopulateData.project); // if data is not present, set active project to the project @@ -180,16 +185,8 @@ export const CreateUpdateDraftIssueModal: React.FC = (props) = await issuesService .createDraftIssue(workspaceSlug as string, activeProject ?? "", payload, user) .then(async () => { - mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params)); mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params)); - if (displayFilters.layout === "calendar") mutate(calendarFetchKey); - if (displayFilters.layout === "gantt_chart") - mutate(ganttFetchKey, { - start_target_date: true, - order_by: "sort_order", - }); - if (displayFilters.layout === "spreadsheet") mutate(spreadsheetFetchKey); if (groupedIssues) mutateMyIssues(); setToastAlert({ @@ -200,8 +197,6 @@ export const CreateUpdateDraftIssueModal: React.FC = (props) = if (payload.assignees_list?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug as string)); - - if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent)); }) .catch(() => { setToastAlert({ @@ -396,6 +391,7 @@ export const CreateUpdateDraftIssueModal: React.FC = (props) = createMore={createMore} setCreateMore={setCreateMore} handleClose={onClose} + handleDiscard={onDiscard} projectId={activeProject ?? ""} setActiveProject={setActiveProject} status={data ? true : false} From 1ad99873a9458c672c807d4c7716623515a30461 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Tue, 26 Sep 2023 13:09:08 +0530 Subject: [PATCH 06/13] feat: Add peek overview in sub issues and updated UI for empty states. (#2263) --- web/components/issues/sub-issues/issue.tsx | 239 ++++++++++-------- .../issues/sub-issues/issues-list.tsx | 3 + web/components/issues/sub-issues/root.tsx | 57 ++++- 3 files changed, 184 insertions(+), 115 deletions(-) diff --git a/web/components/issues/sub-issues/issue.tsx b/web/components/issues/sub-issues/issue.tsx index 2adb7c6f9..d9c6fd303 100644 --- a/web/components/issues/sub-issues/issue.tsx +++ b/web/components/issues/sub-issues/issue.tsx @@ -1,6 +1,7 @@ import React from "react"; // next imports import Link from "next/link"; +import { useRouter } from "next/router"; // lucide icons import { ChevronDown, @@ -37,6 +38,7 @@ export interface ISubIssues { issueId: string, issue?: IIssue | null ) => void; + setPeekParentId: (id: string) => void; } export const SubIssues: React.FC = ({ @@ -52,38 +54,54 @@ export const SubIssues: React.FC = ({ handleIssuesLoader, copyText, handleIssueCrudOperation, -}) => ( -
- {issue && ( -
-
- {issue?.sub_issues_count > 0 && ( - <> - {issuesLoader.sub_issues.includes(issue?.id) ? ( -
- -
- ) : ( -
handleIssuesLoader({ key: "visibility", issueId: issue?.id })} - > - {issuesLoader && issuesLoader.visibility.includes(issue?.id) ? ( - - ) : ( - - )} -
- )} - - )} -
+ setPeekParentId, +}) => { + const router = useRouter(); - - + const openPeekOverview = (issue_id: string) => { + const { query } = router; + + setPeekParentId(parentIssue?.id); + router.push({ + pathname: router.pathname, + query: { ...query, peekIssue: issue_id }, + }); + }; + + return ( +
+ {issue && ( +
+
+ {issue?.sub_issues_count > 0 && ( + <> + {issuesLoader.sub_issues.includes(issue?.id) ? ( +
+ +
+ ) : ( +
handleIssuesLoader({ key: "visibility", issueId: issue?.id })} + > + {issuesLoader && issuesLoader.visibility.includes(issue?.id) ? ( + + ) : ( + + )} +
+ )} + + )} +
+ +
openPeekOverview(issue?.id)} + > -
- -
+
+ +
+ +
+ + {editable && ( + handleIssueCrudOperation("edit", parentIssue?.id, issue)} + > +
+ + Edit issue +
+
+ )} + + {editable && ( + handleIssueCrudOperation("delete", parentIssue?.id, issue)} + > +
+ + Delete issue +
+
+ )} -
- - {editable && ( handleIssueCrudOperation("edit", parentIssue?.id, issue)} + onClick={() => + copyText(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`) + } >
- - Edit issue + + Copy issue link
- )} +
+
- {editable && ( - handleIssueCrudOperation("delete", parentIssue?.id, issue)} - > -
- - Delete issue + {editable && ( + <> + {issuesLoader.delete.includes(issue?.id) ? ( +
+
- - )} - - - copyText(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`) - } - > -
- - Copy issue link -
-
- + ) : ( +
{ + handleIssuesLoader({ key: "delete", issueId: issue?.id }); + removeIssueFromSubIssues(parentIssue?.id, issue); + }} + > + +
+ )} + + )}
+ )} - {editable && ( - <> - {issuesLoader.delete.includes(issue?.id) ? ( -
- -
- ) : ( -
{ - handleIssuesLoader({ key: "delete", issueId: issue?.id }); - removeIssueFromSubIssues(parentIssue?.id, issue); - }} - > - -
- )} - - )} -
- )} - - {issuesLoader.visibility.includes(issue?.id) && issue?.sub_issues_count > 0 && ( - - )} -
-); + {issuesLoader.visibility.includes(issue?.id) && issue?.sub_issues_count > 0 && ( + + )} +
+ ); +}; diff --git a/web/components/issues/sub-issues/issues-list.tsx b/web/components/issues/sub-issues/issues-list.tsx index 9fc77992e..a713d6fb8 100644 --- a/web/components/issues/sub-issues/issues-list.tsx +++ b/web/components/issues/sub-issues/issues-list.tsx @@ -27,6 +27,7 @@ export interface ISubIssuesRootList { issueId: string, issue?: IIssue | null ) => void; + setPeekParentId: (id: string) => void; } export const SubIssuesRootList: React.FC = ({ @@ -41,6 +42,7 @@ export const SubIssuesRootList: React.FC = ({ handleIssuesLoader, copyText, handleIssueCrudOperation, + setPeekParentId, }) => { const { data: issues, isLoading } = useSWR( workspaceSlug && projectId && parentIssue && parentIssue?.id @@ -81,6 +83,7 @@ export const SubIssuesRootList: React.FC = ({ handleIssuesLoader={handleIssuesLoader} copyText={copyText} handleIssueCrudOperation={handleIssueCrudOperation} + setPeekParentId={setPeekParentId} /> ))} diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 305a9150b..352546eab 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -10,6 +10,7 @@ import { ExistingIssuesListModal } from "components/core"; import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues"; import { SubIssuesRootList } from "./issues-list"; import { ProgressBar } from "./progressbar"; +import { IssuePeekOverview } from "components/issues/peek-overview"; // ui import { CustomMenu } from "components/ui"; // hooks @@ -41,7 +42,11 @@ export interface ISubIssuesRootLoadersHandler { export const SubIssuesRoot: React.FC = ({ parentIssue, user }) => { const router = useRouter(); - const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; + const { workspaceSlug, projectId, peekIssue } = router.query as { + workspaceSlug: string; + projectId: string; + peekIssue: string; + }; const { memberRole } = useProjectMyMembership(); const { setToastAlert } = useToast(); @@ -55,6 +60,8 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = : null ); + const [peekParentId, setPeekParentId] = React.useState(""); + const [issuesLoader, setIssuesLoader] = React.useState({ visibility: [parentIssue?.id], delete: [], @@ -230,15 +237,48 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = handleIssuesLoader={handleIssuesLoader} copyText={copyText} handleIssueCrudOperation={handleIssueCrudOperation} + setPeekParentId={setPeekParentId} />
)} + +
+ + + Add sub-issue + + } + buttonClassName="whitespace-nowrap" + position="left" + noBorder + noChevron + > + { + mutateSubIssues(parentIssue?.id); + handleIssueCrudOperation("create", parentIssue?.id); + }} + > + Create new + + { + mutateSubIssues(parentIssue?.id); + handleIssueCrudOperation("existing", parentIssue?.id); + }} + > + Add an existing issue + + +
) : ( isEditable && ( -
-
No sub issues are available
- <> +
+
No Sub-Issues yet
+
@@ -268,7 +308,7 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = Add an existing issue - +
) )} @@ -323,6 +363,13 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = )} )} + + peekParentId && peekIssue && mutateSubIssues(peekParentId)} + projectId={projectId ?? ""} + workspaceSlug={workspaceSlug ?? ""} + readOnly={!isEditable} + />
); }; From 6d3bd780520e928c1e1838108ccbf1b538e4e01f Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:10:28 +0530 Subject: [PATCH 07/13] chore: add tooltip to show full time on activity logs (#2235) --- web/components/issues/activity.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/web/components/issues/activity.tsx b/web/components/issues/activity.tsx index fe322afe9..e6f54f512 100644 --- a/web/components/issues/activity.tsx +++ b/web/components/issues/activity.tsx @@ -7,9 +7,9 @@ import { useRouter } from "next/router"; import { ActivityIcon, ActivityMessage } from "components/core"; import { CommentCard } from "components/issues/comment"; // ui -import { Icon, Loader } from "components/ui"; +import { Icon, Loader, Tooltip } from "components/ui"; // helpers -import { timeAgo } from "helpers/date-time.helper"; +import { render24HourFormatTime, renderLongDateFormat, timeAgo } from "helpers/date-time.helper"; // types import { IIssueActivity, IIssueComment } from "types"; @@ -120,9 +120,15 @@ export const IssueActivitySection: React.FC = ({ )}{" "} {message}{" "} - - {timeAgo(activityItem.created_at)} - + + + {timeAgo(activityItem.created_at)} + +
From dae8ca60532156047ea922218585a7eee453bab4 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:11:00 +0530 Subject: [PATCH 08/13] fix: issue automation iterable error (#2208) --- apiserver/plane/bgtasks/issue_automation_task.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apiserver/plane/bgtasks/issue_automation_task.py b/apiserver/plane/bgtasks/issue_automation_task.py index a1b42073f..68c64403a 100644 --- a/apiserver/plane/bgtasks/issue_automation_task.py +++ b/apiserver/plane/bgtasks/issue_automation_task.py @@ -58,20 +58,23 @@ def archive_old_issues(): # Check if Issues if issues: + # Set the archive time to current time + archive_at = timezone.now() + issues_to_update = [] for issue in issues: - issue.archived_at = timezone.now() + issue.archived_at = archive_at issues_to_update.append(issue) # Bulk Update the issues and log the activity if issues_to_update: - updated_issues = Issue.objects.bulk_update( + Issue.objects.bulk_update( issues_to_update, ["archived_at"], batch_size=100 ) [ issue_activity.delay( type="issue.activity.updated", - requested_data=json.dumps({"archived_at": str(issue.archived_at)}), + requested_data=json.dumps({"archived_at": str(archive_at)}), actor_id=str(project.created_by_id), issue_id=issue.id, project_id=project_id, @@ -79,7 +82,7 @@ def archive_old_issues(): subscriber=False, epoch=int(timezone.now().timestamp()) ) - for issue in updated_issues + for issue in issues_to_update ] return except Exception as e: @@ -139,7 +142,7 @@ def close_old_issues(): # Bulk Update the issues and log the activity if issues_to_update: - updated_issues = Issue.objects.bulk_update(issues_to_update, ["state"], batch_size=100) + Issue.objects.bulk_update(issues_to_update, ["state"], batch_size=100) [ issue_activity.delay( type="issue.activity.updated", @@ -151,7 +154,7 @@ def close_old_issues(): subscriber=False, epoch=int(timezone.now().timestamp()) ) - for issue in updated_issues + for issue in issues_to_update ] return except Exception as e: From d38594376babd6df450b70b2d8180201d4f584e5 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:11:23 +0530 Subject: [PATCH 09/13] fix: n+1 queries for cycle list and project member endpoints (#2257) --- apiserver/plane/api/serializers/cycle.py | 22 +--------------------- apiserver/plane/api/views/project.py | 2 +- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/apiserver/plane/api/serializers/cycle.py b/apiserver/plane/api/serializers/cycle.py index 664368033..ad214c52a 100644 --- a/apiserver/plane/api/serializers/cycle.py +++ b/apiserver/plane/api/serializers/cycle.py @@ -34,7 +34,6 @@ class CycleSerializer(BaseSerializer): unstarted_issues = serializers.IntegerField(read_only=True) backlog_issues = serializers.IntegerField(read_only=True) assignees = serializers.SerializerMethodField(read_only=True) - labels = serializers.SerializerMethodField(read_only=True) total_estimates = serializers.IntegerField(read_only=True) completed_estimates = serializers.IntegerField(read_only=True) started_estimates = serializers.IntegerField(read_only=True) @@ -50,11 +49,10 @@ class CycleSerializer(BaseSerializer): members = [ { "avatar": assignee.avatar, - "first_name": assignee.first_name, "display_name": assignee.display_name, "id": assignee.id, } - for issue_cycle in obj.issue_cycle.all() + for issue_cycle in obj.issue_cycle.prefetch_related("issue__assignees").all() for assignee in issue_cycle.issue.assignees.all() ] # Use a set comprehension to return only the unique objects @@ -64,24 +62,6 @@ class CycleSerializer(BaseSerializer): unique_list = [dict(item) for item in unique_objects] return unique_list - - def get_labels(self, obj): - labels = [ - { - "name": label.name, - "color": label.color, - "id": label.id, - } - for issue_cycle in obj.issue_cycle.all() - for label in issue_cycle.issue.labels.all() - ] - # Use a set comprehension to return only the unique objects - unique_objects = {frozenset(item.items()) for item in labels} - - # Convert the set back to a list of dictionaries - unique_list = [dict(item) for item in unique_objects] - - return unique_list class Meta: model = Cycle diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 093c8ff78..c72b8d423 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -1094,7 +1094,7 @@ class ProjectMemberEndpoint(BaseAPIView): project_id=project_id, workspace__slug=slug, member__is_bot=False, - ).select_related("project", "member") + ).select_related("project", "member", "workspace") serializer = ProjectMemberSerializer(project_members, many=True) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as e: From 88a35efa06585d47c0653bc63a56dd30283280b7 Mon Sep 17 00:00:00 2001 From: Henit Chobisa Date: Tue, 26 Sep 2023 13:46:38 +0530 Subject: [PATCH 10/13] [fix] nginx continuously rewriting and reloading on index page of spaces app (#2236) * chore: shifted index page to /home route * chore: added rewrite logic, to rewrite index to /home * chore: routed home to login route as login page * chore: updated nginx config to route to login * chore: updated path for home --- nginx/nginx.conf.template | 8 +++++++- space/components/accounts/sign-in.tsx | 4 ++-- space/components/views/index.ts | 2 +- space/components/views/{home.tsx => login.tsx} | 2 +- space/pages/index.tsx | 8 -------- space/pages/login/index.tsx | 8 ++++++++ 6 files changed, 19 insertions(+), 13 deletions(-) rename space/components/views/{home.tsx => login.tsx} (88%) delete mode 100644 space/pages/index.tsx create mode 100644 space/pages/login/index.tsx diff --git a/nginx/nginx.conf.template b/nginx/nginx.conf.template index af80b04fa..4775dcbfa 100644 --- a/nginx/nginx.conf.template +++ b/nginx/nginx.conf.template @@ -11,6 +11,11 @@ http { client_max_body_size ${FILE_SIZE_LIMIT}; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Permissions-Policy "interest-cohort=()" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + location / { proxy_pass http://web:3000/; } @@ -20,6 +25,7 @@ http { } location /spaces/ { + rewrite ^/spaces/?$ /spaces/login break; proxy_pass http://space:3000/spaces/; } @@ -27,4 +33,4 @@ http { proxy_pass http://plane-minio:9000/uploads/; } } -} \ No newline at end of file +} diff --git a/space/components/accounts/sign-in.tsx b/space/components/accounts/sign-in.tsx index d3c29103d..c6a151d44 100644 --- a/space/components/accounts/sign-in.tsx +++ b/space/components/accounts/sign-in.tsx @@ -33,7 +33,7 @@ export const SignInView = observer(() => { const onSignInSuccess = (response: any) => { const isOnboarded = response?.user?.onboarding_step?.profile_complete || false; - const nextPath = router.asPath.includes("next_path") ? router.asPath.split("/?next_path=")[1] : "/"; + const nextPath = router.asPath.includes("next_path") ? router.asPath.split("/?next_path=")[1] : "/login"; userStore.setCurrentUser(response?.user); @@ -41,7 +41,7 @@ export const SignInView = observer(() => { router.push(`/onboarding?next_path=${nextPath}`); return; } - router.push((nextPath ?? "/").toString()); + router.push((nextPath ?? "/login").toString()); }; const handleGoogleSignIn = async ({ clientId, credential }: any) => { diff --git a/space/components/views/index.ts b/space/components/views/index.ts index 84d36cd29..f54d11bdd 100644 --- a/space/components/views/index.ts +++ b/space/components/views/index.ts @@ -1 +1 @@ -export * from "./home"; +export * from "./login"; diff --git a/space/components/views/home.tsx b/space/components/views/login.tsx similarity index 88% rename from space/components/views/home.tsx rename to space/components/views/login.tsx index 999fce073..d01a22681 100644 --- a/space/components/views/home.tsx +++ b/space/components/views/login.tsx @@ -4,7 +4,7 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { SignInView, UserLoggedIn } from "components/accounts"; -export const HomeView = observer(() => { +export const LoginView = observer(() => { const { user: userStore } = useMobxStore(); if (!userStore.currentUser) return ; diff --git a/space/pages/index.tsx b/space/pages/index.tsx deleted file mode 100644 index fe0b7d33a..000000000 --- a/space/pages/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; - -// components -import { HomeView } from "components/views"; - -const HomePage = () => ; - -export default HomePage; diff --git a/space/pages/login/index.tsx b/space/pages/login/index.tsx new file mode 100644 index 000000000..a80eff873 --- /dev/null +++ b/space/pages/login/index.tsx @@ -0,0 +1,8 @@ +import React from "react"; + +// components +import { LoginView } from "components/views"; + +const LoginPage = () => ; + +export default LoginPage; \ No newline at end of file From 52b57b1e37662de5722fc09e88f2789f0379f2a6 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Tue, 26 Sep 2023 14:18:06 +0530 Subject: [PATCH 11/13] dev: migration for 0.13 (#2266) * dev: updated migrations * dev: migration for 0.13 --- .../db/migrations/0044_auto_20230913_0709.py | 26 ++++---- .../db/migrations/0045_auto_20230915_0655.py | 62 ++++++++++++++++--- .../db/migrations/0046_auto_20230919_1421.py | 53 ---------------- .../db/migrations/0047_auto_20230921_0758.py | 27 -------- 4 files changed, 65 insertions(+), 103 deletions(-) delete mode 100644 apiserver/plane/db/migrations/0046_auto_20230919_1421.py delete mode 100644 apiserver/plane/db/migrations/0047_auto_20230921_0758.py diff --git a/apiserver/plane/db/migrations/0044_auto_20230913_0709.py b/apiserver/plane/db/migrations/0044_auto_20230913_0709.py index f30062371..19a1449af 100644 --- a/apiserver/plane/db/migrations/0044_auto_20230913_0709.py +++ b/apiserver/plane/db/migrations/0044_auto_20230913_0709.py @@ -26,19 +26,19 @@ def workspace_member_props(old_props): "calendar_date_range": old_props.get("calendarDateRange", ""), }, "display_properties": { - "assignee": old_props.get("properties", {}).get("assignee",None), - "attachment_count": old_props.get("properties", {}).get("attachment_count", None), - "created_on": old_props.get("properties", {}).get("created_on", None), - "due_date": old_props.get("properties", {}).get("due_date", None), - "estimate": old_props.get("properties", {}).get("estimate", None), - "key": old_props.get("properties", {}).get("key", None), - "labels": old_props.get("properties", {}).get("labels", None), - "link": old_props.get("properties", {}).get("link", None), - "priority": old_props.get("properties", {}).get("priority", None), - "start_date": old_props.get("properties", {}).get("start_date", None), - "state": old_props.get("properties", {}).get("state", None), - "sub_issue_count": old_props.get("properties", {}).get("sub_issue_count", None), - "updated_on": old_props.get("properties", {}).get("updated_on", None), + "assignee": old_props.get("properties", {}).get("assignee", True), + "attachment_count": old_props.get("properties", {}).get("attachment_count", True), + "created_on": old_props.get("properties", {}).get("created_on", True), + "due_date": old_props.get("properties", {}).get("due_date", True), + "estimate": old_props.get("properties", {}).get("estimate", True), + "key": old_props.get("properties", {}).get("key", True), + "labels": old_props.get("properties", {}).get("labels", True), + "link": old_props.get("properties", {}).get("link", True), + "priority": old_props.get("properties", {}).get("priority", True), + "start_date": old_props.get("properties", {}).get("start_date", True), + "state": old_props.get("properties", {}).get("state", True), + "sub_issue_count": old_props.get("properties", {}).get("sub_issue_count", True), + "updated_on": old_props.get("properties", {}).get("updated_on", True), }, } return new_props diff --git a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py index a8360c63d..8512594ba 100644 --- a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py +++ b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py @@ -1,24 +1,66 @@ # Generated by Django 4.2.3 on 2023-09-15 06:55 -from django.db import migrations +from django.db import migrations, models +from django.conf import settings +import django.db.models.deletion +import uuid def update_issue_activity(apps, schema_editor): - IssueActivityModel = apps.get_model("db", "IssueActivity") + IssueActivity = apps.get_model("db", "IssueActivity") updated_issue_activity = [] - for obj in IssueActivityModel.objects.all(): - if obj.field == "blocks": - obj.field = "blocked_by" - updated_issue_activity.append(obj) - IssueActivityModel.objects.bulk_update(updated_issue_activity, ["field"], batch_size=100) + for obj in IssueActivity.objects.all(): + obj.epoch = int(obj.created_at.timestamp()) + + # Set the old and new value to none if it is empty for Priority + if obj.field == "priority": + obj.new_value = obj.new_value or "none" + obj.old_value = obj.old_value or "none" + + # Change the field name from blocks to blocked_by + if obj.field == "blocks": + obj.field = "blocked_by" + + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["epoch", "field", "new_value", "old_value"], + batch_size=100, + ) class Migration(migrations.Migration): - dependencies = [ - ('db', '0044_auto_20230913_0709'), + ("db", "0044_auto_20230913_0709"), ] operations = [ - migrations.RunPython(update_issue_activity), + migrations.CreateModel( + name="GlobalView", + fields=[ + ("created_at", models.DateTimeField(auto_now_add=True, verbose_name="Created At"),), + ("updated_at", models.DateTimeField(auto_now=True, verbose_name="Last Modified At"),), + ("id", models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True,),), + ("name", models.CharField(max_length=255, verbose_name="View Name")), + ("description", models.TextField(blank=True, verbose_name="View Description"),), + ("query", models.JSONField(verbose_name="View Query")), + ("access", models.PositiveSmallIntegerField(choices=[(0, "Private"), (1, "Public")], default=1),), + ("query_data", models.JSONField(default=dict)), + ("created_by", models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="%(class)s_created_by", to=settings.AUTH_USER_MODEL, verbose_name="Created By",),), + ("updated_by", models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="%(class)s_updated_by", to=settings.AUTH_USER_MODEL, verbose_name="Last Modified By",),), + ("workspace", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name="global_views", to="db.workspace",),), + ], + options={ + "verbose_name": "Global View", + "verbose_name_plural": "Global Views", + "db_table": "global_views", + "ordering": ("-created_at",), + }, + ), + migrations.AddField( + model_name="issueactivity", + name="epoch", + field=models.FloatField(null=True), + ), + migrations.RunPython(update_issue_activity), ] diff --git a/apiserver/plane/db/migrations/0046_auto_20230919_1421.py b/apiserver/plane/db/migrations/0046_auto_20230919_1421.py deleted file mode 100644 index 4005a94d4..000000000 --- a/apiserver/plane/db/migrations/0046_auto_20230919_1421.py +++ /dev/null @@ -1,53 +0,0 @@ -# Generated by Django 4.2.3 on 2023-09-19 14:21 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -def update_epoch(apps, schema_editor): - IssueActivity = apps.get_model('db', 'IssueActivity') - updated_issue_activity = [] - for obj in IssueActivity.objects.all(): - obj.epoch = int(obj.created_at.timestamp()) - updated_issue_activity.append(obj) - IssueActivity.objects.bulk_update(updated_issue_activity, ["epoch"], batch_size=100) - - -class Migration(migrations.Migration): - - dependencies = [ - ('db', '0045_auto_20230915_0655'), - ] - - operations = [ - migrations.CreateModel( - name='GlobalView', - fields=[ - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), - ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), - ('name', models.CharField(max_length=255, verbose_name='View Name')), - ('description', models.TextField(blank=True, verbose_name='View Description')), - ('query', models.JSONField(verbose_name='View Query')), - ('access', models.PositiveSmallIntegerField(choices=[(0, 'Private'), (1, 'Public')], default=1)), - ('query_data', models.JSONField(default=dict)), - ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), - ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), - ('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='global_views', to='db.workspace')), - ], - options={ - 'verbose_name': 'Global View', - 'verbose_name_plural': 'Global Views', - 'db_table': 'global_views', - 'ordering': ('-created_at',), - }, - ), - migrations.AddField( - model_name='issueactivity', - name='epoch', - field=models.FloatField(null=True), - ), - migrations.RunPython(update_epoch), - ] diff --git a/apiserver/plane/db/migrations/0047_auto_20230921_0758.py b/apiserver/plane/db/migrations/0047_auto_20230921_0758.py deleted file mode 100644 index 4344963cd..000000000 --- a/apiserver/plane/db/migrations/0047_auto_20230921_0758.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 4.2.3 on 2023-09-21 07:58 - - -from django.db import migrations - - -def update_priority_history(apps, schema_editor): - IssueActivity = apps.get_model("db", "IssueActivity") - updated_issue_activity = [] - for obj in IssueActivity.objects.all(): - if obj.field == "priority": - obj.new_value = obj.new_value or "none" - obj.old_value = obj.old_value or "none" - updated_issue_activity.append(obj) - IssueActivity.objects.bulk_update( - updated_issue_activity, ["new_value", "old_value"], batch_size=100 - ) - - -class Migration(migrations.Migration): - dependencies = [ - ("db", "0046_auto_20230919_1421"), - ] - - operations = [ - migrations.RunPython(update_priority_history), - ] From 6e0999c35a133a0d8b803d047b1cf3fd8eb8ae27 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Tue, 26 Sep 2023 16:25:52 +0530 Subject: [PATCH 12/13] dev: re-split migrations into two different files (#2268) * dev: split issue activity migration separate files * dev: resplit migrations into two different files * dev: changed the batch size --- .../db/migrations/0045_auto_20230915_0655.py | 26 +---------- .../db/migrations/0046_auto_20230926_1015.py | 26 +++++++++++ .../db/migrations/0047_auto_20230926_1029.py | 44 +++++++++++++++++++ 3 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 apiserver/plane/db/migrations/0046_auto_20230926_1015.py create mode 100644 apiserver/plane/db/migrations/0047_auto_20230926_1029.py diff --git a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py index 8512594ba..cd9aa6902 100644 --- a/apiserver/plane/db/migrations/0045_auto_20230915_0655.py +++ b/apiserver/plane/db/migrations/0045_auto_20230915_0655.py @@ -6,29 +6,6 @@ import django.db.models.deletion import uuid -def update_issue_activity(apps, schema_editor): - IssueActivity = apps.get_model("db", "IssueActivity") - updated_issue_activity = [] - for obj in IssueActivity.objects.all(): - obj.epoch = int(obj.created_at.timestamp()) - - # Set the old and new value to none if it is empty for Priority - if obj.field == "priority": - obj.new_value = obj.new_value or "none" - obj.old_value = obj.old_value or "none" - - # Change the field name from blocks to blocked_by - if obj.field == "blocks": - obj.field = "blocked_by" - - updated_issue_activity.append(obj) - IssueActivity.objects.bulk_update( - updated_issue_activity, - ["epoch", "field", "new_value", "old_value"], - batch_size=100, - ) - - class Migration(migrations.Migration): dependencies = [ ("db", "0044_auto_20230913_0709"), @@ -61,6 +38,5 @@ class Migration(migrations.Migration): model_name="issueactivity", name="epoch", field=models.FloatField(null=True), - ), - migrations.RunPython(update_issue_activity), + ), ] diff --git a/apiserver/plane/db/migrations/0046_auto_20230926_1015.py b/apiserver/plane/db/migrations/0046_auto_20230926_1015.py new file mode 100644 index 000000000..8bce37d95 --- /dev/null +++ b/apiserver/plane/db/migrations/0046_auto_20230926_1015.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.5 on 2023-09-26 10:15 + +from django.db import migrations + + +def update_issue_activity(apps, schema_editor): + IssueActivity = apps.get_model("db", "IssueActivity") + updated_issue_activity = [] + for obj in IssueActivity.objects.all(): + obj.epoch = int(obj.created_at.timestamp()) + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["epoch"], + batch_size=5000, + ) + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0045_auto_20230915_0655'), + ] + + operations = [ + migrations.RunPython(update_issue_activity), + ] diff --git a/apiserver/plane/db/migrations/0047_auto_20230926_1029.py b/apiserver/plane/db/migrations/0047_auto_20230926_1029.py new file mode 100644 index 000000000..da64e11c8 --- /dev/null +++ b/apiserver/plane/db/migrations/0047_auto_20230926_1029.py @@ -0,0 +1,44 @@ +# Generated by Django 4.2.5 on 2023-09-26 10:29 + +from django.db import migrations + + +def update_issue_activity_priority(apps, schema_editor): + IssueActivity = apps.get_model("db", "IssueActivity") + updated_issue_activity = [] + for obj in IssueActivity.objects.filter(field="priority"): + # Set the old and new value to none if it is empty for Priority + obj.new_value = obj.new_value or "none" + obj.old_value = obj.old_value or "none" + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["new_value", "old_value"], + batch_size=1000, + ) + +def update_issue_activity_blocked(apps, schema_editor): + IssueActivity = apps.get_model("db", "IssueActivity") + updated_issue_activity = [] + for obj in IssueActivity.objects.filter(field="blocks"): + # Set the field to blocked_by + obj.field = "blocked_by" + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, + ["field"], + batch_size=1000, + ) + + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0046_auto_20230926_1015'), + ] + + operations = [ + migrations.RunPython(update_issue_activity_priority), + migrations.RunPython(update_issue_activity_blocked), + ] From b317a14983ac4d1d2a0b10d9a5df7a80d2ebc773 Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:35:51 +0530 Subject: [PATCH 13/13] fix: bugs in quick-add and draft issues (#2269) * fix: 'Last Drafted Issue' making sidebar look weird on collapsed * feat: scroll to the bottom when issue is created * fix: 'Add Issue' button overlapping issue card in spreadsheet view * fix: wrong placement of quick-add in calender layout * fix: spacing for issue card in spreadsheet view --- .../core/views/calendar-view/calendar.tsx | 5 +- .../inline-create-issue-form.tsx | 23 +++- .../core/views/calendar-view/single-date.tsx | 1 + .../views/inline-issue-create-wrapper.tsx | 10 +- .../spreadsheet-view/spreadsheet-view.tsx | 112 ++++++++++-------- web/components/gantt-chart/sidebar.tsx | 14 +++ .../workspace/sidebar-quick-action.tsx | 20 +++- .../projects/[projectId]/cycles/[cycleId].tsx | 2 +- 8 files changed, 118 insertions(+), 69 deletions(-) diff --git a/web/components/core/views/calendar-view/calendar.tsx b/web/components/core/views/calendar-view/calendar.tsx index 030f8b747..8fbe35305 100644 --- a/web/components/core/views/calendar-view/calendar.tsx +++ b/web/components/core/views/calendar-view/calendar.tsx @@ -183,7 +183,10 @@ export const CalendarView: React.FC = ({ {calendarIssues ? (
-
+
void; onSuccess?: (data: IIssue) => Promise | void; prePopulatedData?: Partial; + dependencies: any[]; }; -const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject) => { +const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject, deps: any[]) => { const [isThereSpaceOnRight, setIsThereSpaceOnRight] = useState(true); + const router = useRouter(); + const { moduleId, cycleId, viewId } = router.query; + + const container = document.getElementById(`calendar-view-${cycleId ?? moduleId ?? viewId}`); + useEffect(() => { if (!ref.current) return; const { right } = ref.current.getBoundingClientRect(); - const width = right + 250; + const width = right; - if (width > window.innerWidth) setIsThereSpaceOnRight(false); + const innerWidth = container?.getBoundingClientRect().width ?? window.innerWidth; + + if (width > innerWidth) setIsThereSpaceOnRight(false); else setIsThereSpaceOnRight(true); - }, [ref]); + }, [ref, deps, container]); return isThereSpaceOnRight; }; @@ -63,11 +74,11 @@ const InlineInput = () => { }; export const CalendarInlineCreateIssueForm: React.FC = (props) => { - const { isOpen } = props; + const { isOpen, dependencies } = props; const ref = useRef(null); - const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref); + const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref, dependencies); return ( <> diff --git a/web/components/core/views/calendar-view/single-date.tsx b/web/components/core/views/calendar-view/single-date.tsx index 178151204..02ea56678 100644 --- a/web/components/core/views/calendar-view/single-date.tsx +++ b/web/components/core/views/calendar-view/single-date.tsx @@ -83,6 +83,7 @@ export const SingleCalendarDate: React.FC = (props) => { setIsCreateIssueFormOpen(false)} prePopulatedData={{ target_date: date.date, diff --git a/web/components/core/views/inline-issue-create-wrapper.tsx b/web/components/core/views/inline-issue-create-wrapper.tsx index 3c01c50ce..ace407ae1 100644 --- a/web/components/core/views/inline-issue-create-wrapper.tsx +++ b/web/components/core/views/inline-issue-create-wrapper.tsx @@ -218,11 +218,11 @@ export const InlineCreateIssueFormWrapper: React.FC = (props) => { ); if (isDraftIssues) - mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params)); - if (displayFilters.layout === "calendar") mutate(calendarFetchKey); - if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey); - if (displayFilters.layout === "spreadsheet") mutate(spreadsheetFetchKey); - if (groupedIssues) mutateMyIssues(); + await mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params)); + if (displayFilters.layout === "calendar") await mutate(calendarFetchKey); + if (displayFilters.layout === "gantt_chart") await mutate(ganttFetchKey); + if (displayFilters.layout === "spreadsheet") await mutate(spreadsheetFetchKey); + if (groupedIssues) await mutateMyIssues(); setToastAlert({ type: "success", diff --git a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx index 2315c21c3..d11fe85d6 100644 --- a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx +++ b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx @@ -68,7 +68,11 @@ export const SpreadsheetView: React.FC = ({ workspaceSlug={workspaceSlug?.toString() ?? ""} readOnly={disableUserActions} /> -
+
@@ -89,62 +93,66 @@ export const SpreadsheetView: React.FC = ({ userAuth={userAuth} /> ))} - -
- setIsInlineCreateIssueFormOpen(false)} - prePopulatedData={{ - ...(cycleId && { cycle: cycleId.toString() }), - ...(moduleId && { module: moduleId.toString() }), - }} - /> - - {type === "issue" - ? !disableUserActions && - !isInlineCreateIssueFormOpen && ( - - ) - : !disableUserActions && - !isInlineCreateIssueFormOpen && ( - - - Add Issue - - } - position="left" - verticalPosition="top" - optionsClassName="left-5 !w-36" - noBorder - > - setIsInlineCreateIssueFormOpen(true)}> - Create new - - {openIssuesListModal && ( - - Add an existing issue - - )} - - )} -
) : ( )}
+ +
+ setIsInlineCreateIssueFormOpen(false)} + prePopulatedData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + }} + /> + + {type === "issue" + ? !disableUserActions && + !isInlineCreateIssueFormOpen && ( + + ) + : !disableUserActions && + !isInlineCreateIssueFormOpen && ( + + + Add Issue + + } + position="left" + verticalPosition="top" + optionsClassName="left-5 !w-36" + noBorder + > + setIsInlineCreateIssueFormOpen(true)}> + Create new + + {openIssuesListModal && ( + + Add an existing issue + + )} + + )} +
); }; diff --git a/web/components/gantt-chart/sidebar.tsx b/web/components/gantt-chart/sidebar.tsx index 35b253ef9..fc2cea1e0 100644 --- a/web/components/gantt-chart/sidebar.tsx +++ b/web/components/gantt-chart/sidebar.tsx @@ -92,6 +92,7 @@ export const GanttSidebar: React.FC = (props) => { {(droppableProvided) => (
= (props) => { setIsCreateIssueFormOpen(false)} + onSuccess={() => { + const ganttSidebar = document.getElementById(`gantt-sidebar-${cycleId}`); + + const timeoutId = setTimeout(() => { + if (ganttSidebar) + ganttSidebar.scrollBy({ + top: ganttSidebar.scrollHeight, + left: 0, + behavior: "smooth", + }); + clearTimeout(timeoutId); + }, 10); + }} prePopulatedData={{ start_date: new Date(Date.now()).toISOString().split("T")[0], target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0], diff --git a/web/components/workspace/sidebar-quick-action.tsx b/web/components/workspace/sidebar-quick-action.tsx index 8923abc14..1c614f694 100644 --- a/web/components/workspace/sidebar-quick-action.tsx +++ b/web/components/workspace/sidebar-quick-action.tsx @@ -44,7 +44,9 @@ export const WorkspaceSidebarQuickAction = () => { > -
+