From f8391507415facc23f3ed7afc12b1957caba13b6 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:08:53 +0530 Subject: [PATCH 01/57] fix: create new issue when grouped by label (#1308) --- .../components/core/board-view/all-boards.tsx | 3 +- .../core/board-view/board-header.tsx | 2 +- .../core/board-view/single-board.tsx | 72 ++++++++++--------- .../core/existing-issues-list-modal.tsx | 4 +- apps/app/components/core/issues-view.tsx | 10 ++- apps/app/components/issues/select/label.tsx | 7 +- 6 files changed, 55 insertions(+), 43 deletions(-) diff --git a/apps/app/components/core/board-view/all-boards.tsx b/apps/app/components/core/board-view/all-boards.tsx index 3e67e86b5..711fb7336 100644 --- a/apps/app/components/core/board-view/all-boards.tsx +++ b/apps/app/components/core/board-view/all-boards.tsx @@ -2,11 +2,12 @@ import useProjectIssuesView from "hooks/use-issues-view"; // components import { SingleBoard } from "components/core/board-view/single-board"; +// icons +import { getStateGroupIcon } from "components/icons"; // helpers import { addSpaceIfCamelCase } from "helpers/string.helper"; // types import { ICurrentUserResponse, IIssue, IState, UserAuth } from "types"; -import { getStateGroupIcon } from "components/icons"; type Props = { type: "issue" | "cycle" | "module"; diff --git a/apps/app/components/core/board-view/board-header.tsx b/apps/app/components/core/board-view/board-header.tsx index 6fa8f68f3..a5df7a426 100644 --- a/apps/app/components/core/board-view/board-header.tsx +++ b/apps/app/components/core/board-view/board-header.tsx @@ -166,7 +166,7 @@ export const BoardHeader: React.FC = ({ )} - {!isCompleted && ( + {!isCompleted && selectedGroup !== "created_by" && ( - ) : ( - !isCompleted && ( - - - Add Issue - - } - position="left" - noBorder + {selectedGroup !== "created_by" && ( +
+ {type === "issue" ? ( + + ) : ( + !isCompleted && ( + + + Add Issue + + } + position="left" + noBorder + > + + Create new - )} - - ) - )} -
+ {openIssuesListModal && ( + + Add an existing issue + + )} +
+ ) + )} + + )} )} diff --git a/apps/app/components/core/existing-issues-list-modal.tsx b/apps/app/components/core/existing-issues-list-modal.tsx index 8ef6132ae..fe6f1e1b4 100644 --- a/apps/app/components/core/existing-issues-list-modal.tsx +++ b/apps/app/components/core/existing-issues-list-modal.tsx @@ -72,9 +72,9 @@ export const ExistingIssuesListModal: React.FC = ({ const onSubmit: SubmitHandler = async (data) => { if (!data.issues || data.issues.length === 0) { setToastAlert({ - title: "Error", type: "error", - message: "Please select atleast one issue", + title: "Error!", + message: "Please select at least one issue.", }); return; diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index 2e9e14ae3..b90439b9d 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -283,9 +283,17 @@ export const IssuesView: React.FC = ({ const addIssueToState = useCallback( (groupTitle: string) => { setCreateIssueModal(true); + + let preloadedValue: string | string[] = groupTitle; + + if (selectedGroup === "labels") { + if (groupTitle === "None") preloadedValue = []; + else preloadedValue = [groupTitle]; + } + if (selectedGroup) setPreloadedData({ - [selectedGroup]: groupTitle, + [selectedGroup]: preloadedValue, actionType: "createIssue", }); else setPreloadedData({ actionType: "createIssue" }); diff --git a/apps/app/components/issues/select/label.tsx b/apps/app/components/issues/select/label.tsx index a7ada1133..e99eecc16 100644 --- a/apps/app/components/issues/select/label.tsx +++ b/apps/app/components/issues/select/label.tsx @@ -6,6 +6,10 @@ import useSWR from "swr"; // headless ui import { Combobox, Transition } from "@headlessui/react"; +// services +import issuesServices from "services/issues.service"; +// ui +import { IssueLabelsList } from "components/ui"; // icons import { CheckIcon, @@ -14,13 +18,10 @@ import { RectangleGroupIcon, TagIcon, } from "@heroicons/react/24/outline"; -// services -import issuesServices from "services/issues.service"; // types import type { IIssueLabels } from "types"; // fetch-keys import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; -import { IssueLabelsList } from "components/ui"; type Props = { setIsOpen: React.Dispatch>; From 9c85704be3c687ae02340ab688e1583d8c2e022f Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:09:03 +0530 Subject: [PATCH 02/57] style: profile settings, activity, preference page padding (#1335) --- apps/app/pages/[workspaceSlug]/me/profile/activity.tsx | 6 +++--- apps/app/pages/[workspaceSlug]/me/profile/index.tsx | 6 +++--- apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx b/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx index db9b1e55b..3fe99df92 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/activity.tsx @@ -4,6 +4,7 @@ import useSWR from "swr"; import userService from "services/user.service"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import SettingsNavbar from "layouts/settings-navbar"; // components import { Feeds } from "components/core"; // ui @@ -11,7 +12,6 @@ import { Loader } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // fetch-keys import { USER_ACTIVITY } from "constants/fetch-keys"; -import SettingsNavbar from "layouts/settings-navbar"; const ProfileActivity = () => { const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity()); @@ -24,8 +24,8 @@ const ProfileActivity = () => { } > -
-
+
+

Profile Settings

diff --git a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx index ce589c41d..247ef7e03 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/index.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/index.tsx @@ -10,6 +10,7 @@ import useUserAuth from "hooks/use-user-auth"; import useToast from "hooks/use-toast"; // layouts import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; +import SettingsNavbar from "layouts/settings-navbar"; // components import { ImageUploadModal } from "components/core"; // ui @@ -22,7 +23,6 @@ import type { NextPage } from "next"; import type { IUser } from "types"; // constants import { USER_ROLES } from "constants/workspace"; -import SettingsNavbar from "layouts/settings-navbar"; const defaultValues: Partial = { avatar: "", @@ -136,8 +136,8 @@ const Profile: NextPage = () => { userImage /> {myProfile ? ( -

-
+
+

Profile Settings

diff --git a/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx b/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx index f2fe98f80..abaaefcd8 100644 --- a/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx +++ b/apps/app/pages/[workspaceSlug]/me/profile/preferences.tsx @@ -25,7 +25,7 @@ const ProfilePreferences = () => { if (myProfile?.theme.palette) setPreLoadedData(myProfile.theme); if (!customThemeSelectorOptions) setCustomThemeSelectorOptions(true); } - }, [myProfile, theme]); + }, [myProfile, theme, customThemeSelectorOptions]); return ( { } > {myProfile ? ( -

-
+
+

Profile Settings

From d3c56c1765eead777e977d66768e0c076267ef08 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:09:34 +0530 Subject: [PATCH 03/57] fix: cycle stats empty state (#1338) * chore: active cycle percentage fix * fix: progress chart x-axis values --- .../core/sidebar/progress-chart.tsx | 25 ++- .../cycles/active-cycle-details.tsx | 173 +++++++++--------- .../components/cycles/active-cycle-stats.tsx | 153 ++++++++-------- .../components/cycles/single-cycle-list.tsx | 18 +- apps/app/helpers/date-time.helper.ts | 7 +- 5 files changed, 211 insertions(+), 165 deletions(-) diff --git a/apps/app/components/core/sidebar/progress-chart.tsx b/apps/app/components/core/sidebar/progress-chart.tsx index e6349bfe5..47af406cc 100644 --- a/apps/app/components/core/sidebar/progress-chart.tsx +++ b/apps/app/components/core/sidebar/progress-chart.tsx @@ -3,7 +3,7 @@ import React from "react"; // ui import { LineGraph } from "components/ui"; // helpers -import { renderShortNumericDateFormat } from "helpers/date-time.helper"; +import { getDatesInRange, renderShortNumericDateFormat } from "helpers/date-time.helper"; //types import { TCompletionChartDistribution } from "types"; @@ -46,6 +46,27 @@ const ProgressChart: React.FC = ({ distribution, startDate, endDate, tota pending: distribution[key], })); + const generateXAxisTickValues = () => { + const dates = getDatesInRange(startDate, endDate); + + const maxDates = 4; + const totalDates = dates.length; + + if (totalDates <= maxDates) return dates; + else { + const interval = Math.ceil(totalDates / maxDates); + const limitedDates = []; + + for (let i = 0; i < totalDates; i += interval) + limitedDates.push(renderShortNumericDateFormat(dates[i])); + + if (!limitedDates.includes(renderShortNumericDateFormat(dates[totalDates - 1]))) + limitedDates.push(renderShortNumericDateFormat(dates[totalDates - 1])); + + return limitedDates; + } + }; + return (

= ({ distribution, startDate, endDate, tota ]} layers={["grid", "markers", "areas", DashedLine, "slices", "points", "axes", "legends"]} axisBottom={{ - tickValues: chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : "")), + tickValues: generateXAxisTickValues(), }} enablePoints={false} enableArea diff --git a/apps/app/components/cycles/active-cycle-details.tsx b/apps/app/components/cycles/active-cycle-details.tsx index 5a0c1cb58..b2f27d037 100644 --- a/apps/app/components/cycles/active-cycle-details.tsx +++ b/apps/app/components/cycles/active-cycle-details.tsx @@ -395,82 +395,87 @@ export const ActiveCycleDetails: React.FC = () => {
High Priority Issues
{issues ? ( - issues.map((issue) => ( -
-
-
+ issues.length > 0 ? ( + issues.map((issue) => ( +
+
+
+ + + {issue.project_detail?.identifier}-{issue.sequence_id} + + +
- - {issue.project_detail?.identifier}-{issue.sequence_id} + + {truncateText(issue.name, 30)}
- - - {truncateText(issue.name, 30)} - - -
- -
-
- {getPriorityIcon(issue.priority, "text-sm")} -
- {issue.label_details.length > 0 ? ( -
- {issue.label_details.map((label) => ( - - - {label.name} - - ))} +
+
+ {getPriorityIcon(issue.priority, "text-sm")}
- ) : ( - "" - )} -
- {issue.assignees && - issue.assignees.length > 0 && - Array.isArray(issue.assignees) ? ( -
- + {issue.label_details.length > 0 ? ( +
+ {issue.label_details.map((label) => ( + + + {label.name} + + ))}
) : ( "" )} +
+ {issue.assignees && + issue.assignees.length > 0 && + Array.isArray(issue.assignees) ? ( +
+ +
+ ) : ( + "" + )} +
+ )) + ) : ( +
+ No issues present in the cycle.
- )) + ) ) : ( @@ -481,27 +486,29 @@ export const ActiveCycleDetails: React.FC = () => {
-
-
-
issue?.state_detail?.group === "completed") - ?.length / - issues.length) * - 100 ?? 0 - }%`, - }} - /> + {issues && issues.length > 0 && ( +
+
+
issue?.state_detail?.group === "completed") + ?.length / + issues.length) * + 100 ?? 0 + }%`, + }} + /> +
+
+ {issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of{" "} + {issues?.length} +
-
- {issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of{" "} - {issues?.length} -
-
+ )}
diff --git a/apps/app/components/cycles/active-cycle-stats.tsx b/apps/app/components/cycles/active-cycle-stats.tsx index a01293d43..30b69ffaf 100644 --- a/apps/app/components/cycles/active-cycle-stats.tsx +++ b/apps/app/components/cycles/active-cycle-stats.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { Fragment } from "react"; // headless ui import { Tab } from "@headlessui/react"; @@ -32,6 +32,7 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => { return ( { switch (i) { @@ -68,81 +69,87 @@ export const ActiveCycleProgressStats: React.FC = ({ cycle }) => { Labels - - - {cycle.distribution.assignees.map((assignee, index) => { - if (assignee.assignee_id) - return ( - - - {assignee.first_name} -
- } - completed={assignee.completed_issues} - total={assignee.total_issues} - /> - ); - else - return ( - -
- User 0 ? ( + + + {cycle.distribution.assignees.map((assignee, index) => { + if (assignee.assignee_id) + return ( + + + {assignee.first_name}
- No assignee -
- } - completed={assignee.completed_issues} - total={assignee.total_issues} - /> - ); - })} - - - {cycle.distribution.labels.map((label, index) => ( - - - {label.label_name ?? "No labels"} -
- } - completed={label.completed_issues} - total={label.total_issues} - /> - ))} - - + ); + else + return ( + +
+ User +
+ No assignee +
+ } + completed={assignee.completed_issues} + total={assignee.total_issues} + /> + ); + })} + + + {cycle.distribution.labels.map((label, index) => ( + + + {label.label_name ?? "No labels"} +
+ } + completed={label.completed_issues} + total={label.total_issues} + /> + ))} + + + ) : ( +
+ No issues present in the cycle. +
+ )} ); }; diff --git a/apps/app/components/cycles/single-cycle-list.tsx b/apps/app/components/cycles/single-cycle-list.tsx index f8a1fbf28..d957b5ab7 100644 --- a/apps/app/components/cycles/single-cycle-list.tsx +++ b/apps/app/components/cycles/single-cycle-list.tsx @@ -282,12 +282,18 @@ export const SingleCycleList: React.FC = ({ > {cycleStatus === "current" ? ( - - - {Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} % - + {cycle.total_issues > 0 ? ( + <> + + + {Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} % + + + ) : ( + No issues present + )} ) : cycleStatus === "upcoming" ? ( diff --git a/apps/app/helpers/date-time.helper.ts b/apps/app/helpers/date-time.helper.ts index 6333e7558..d462474a4 100644 --- a/apps/app/helpers/date-time.helper.ts +++ b/apps/app/helpers/date-time.helper.ts @@ -25,13 +25,18 @@ export const findHowManyDaysLeft = (date: string | Date) => { return Math.ceil(timeDiff / (1000 * 3600 * 24)); }; -export const getDatesInRange = (startDate: Date, endDate: Date) => { +export const getDatesInRange = (startDate: string | Date, endDate: string | Date) => { + startDate = new Date(startDate); + endDate = new Date(endDate); + const date = new Date(startDate.getTime()); const dates = []; + while (date <= endDate) { dates.push(new Date(date)); date.setDate(date.getDate() + 1); } + return dates; }; From 8982452500ac046a16f83ec7a559720485b56f3d Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:09:47 +0530 Subject: [PATCH 04/57] fix: issue title breaking in issue card (#1339) --- apps/app/components/core/board-view/single-issue.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 003be9e94..3571efa41 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -338,11 +338,8 @@ export const SingleBoardIssue: React.FC = ({ {issue.project_detail.identifier}-{issue.sequence_id}
)} -
- {truncateText(issue.name, 100)} +
+ {truncateText(issue.name, 120)}
From 048a01dbf35e1a8c4a22fba2c9aa35a24a0ce5df Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:09:57 +0530 Subject: [PATCH 05/57] fix: description not loading while editing an issue (#1349) --- apps/app/components/core/issues-view.tsx | 1 - apps/app/components/issues/form.tsx | 2 +- apps/app/components/issues/modal.tsx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index b90439b9d..bcc8e99ce 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -451,7 +451,6 @@ export const IssuesView: React.FC = ({ /> setEditIssueModal(false)} data={issueToEdit} /> diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index e59e9c2cb..5db688283 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -148,7 +148,7 @@ export const IssueForm: FC = ({ setValue, setFocus, } = useForm({ - defaultValues, + defaultValues: initialData ?? defaultValues, reValidateMode: "onChange", }); diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index 5ef39e13a..5142c6a32 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -328,7 +328,7 @@ export const CreateUpdateIssueModal: React.FC = ({ Date: Fri, 23 Jun 2023 11:10:09 +0530 Subject: [PATCH 06/57] fix: bulk delete issues mutation (#1351) --- .../core/bulk-delete-issues-modal.tsx | 134 ++++++++++++------ 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/apps/app/components/core/bulk-delete-issues-modal.tsx b/apps/app/components/core/bulk-delete-issues-modal.tsx index 603efe8e3..a2c6310e9 100644 --- a/apps/app/components/core/bulk-delete-issues-modal.tsx +++ b/apps/app/components/core/bulk-delete-issues-modal.tsx @@ -12,6 +12,8 @@ import { Combobox, Dialog, Transition } from "@headlessui/react"; import issuesServices from "services/issues.service"; // hooks import useToast from "hooks/use-toast"; +import useIssuesView from "hooks/use-issues-view"; +import useCalendarIssuesView from "hooks/use-calendar-issues-view"; // ui import { DangerButton, SecondaryButton } from "components/ui"; // icons @@ -20,7 +22,15 @@ import { LayerDiagonalIcon } from "components/icons"; // types import { ICurrentUserResponse, IIssue } from "types"; // fetch keys -import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; +import { + CYCLE_DETAILS, + CYCLE_ISSUES_WITH_PARAMS, + MODULE_DETAILS, + MODULE_ISSUES_WITH_PARAMS, + PROJECT_ISSUES_LIST, + PROJECT_ISSUES_LIST_WITH_PARAMS, + VIEW_ISSUES, +} from "constants/fetch-keys"; type FormInput = { delete_issue_ids: string[]; @@ -36,7 +46,7 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user const [query, setQuery] = useState(""); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { data: issues } = useSWR( workspaceSlug && projectId @@ -48,6 +58,9 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user ); const { setToastAlert } = useToast(); + const { issueView, params } = useIssuesView(); + const { params: calendarParams } = useCalendarIssuesView(); + const { order_by, group_by, ...viewGanttParams } = params; const { handleSubmit, @@ -61,6 +74,81 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user }, }); + const handleClose = () => { + setIsOpen(false); + setQuery(""); + reset(); + }; + + const handleDelete: SubmitHandler = async (data) => { + if (!workspaceSlug || !projectId) return; + + if (!data.delete_issue_ids || data.delete_issue_ids.length === 0) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Please select at least one issue.", + }); + return; + } + + if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids]; + + const calendarFetchKey = cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams) + : moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams) + : viewId + ? VIEW_ISSUES(viewId.toString(), calendarParams) + : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", calendarParams); + + const ganttFetchKey = cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString()) + : moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString()) + : viewId + ? VIEW_ISSUES(viewId.toString(), viewGanttParams) + : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? ""); + + await issuesServices + .bulkDeleteIssues( + workspaceSlug as string, + projectId as string, + { + issue_ids: data.delete_issue_ids, + }, + user + ) + .then(() => { + setToastAlert({ + type: "success", + title: "Success!", + message: "Issues deleted successfully!", + }); + + if (issueView === "calendar") mutate(calendarFetchKey); + else if (issueView === "gantt_chart") mutate(ganttFetchKey); + else { + if (cycleId) { + mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params)); + mutate(CYCLE_DETAILS(cycleId.toString())); + } else if (moduleId) { + mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); + mutate(MODULE_DETAILS(moduleId as string)); + } else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params)); + } + + handleClose(); + }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again.", + }) + ); + }; + const filteredIssues: IIssue[] = query === "" ? issues ?? [] @@ -72,48 +160,6 @@ export const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen, user .includes(query.toLowerCase()) ) ?? []; - const handleClose = () => { - setIsOpen(false); - setQuery(""); - reset(); - }; - - const handleDelete: SubmitHandler = async (data) => { - if (!data.delete_issue_ids || data.delete_issue_ids.length === 0) { - setToastAlert({ - title: "Error", - type: "error", - message: "Please select atleast one issue", - }); - return; - } - - if (!Array.isArray(data.delete_issue_ids)) data.delete_issue_ids = [data.delete_issue_ids]; - - if (workspaceSlug && projectId) { - await issuesServices - .bulkDeleteIssues( - workspaceSlug as string, - projectId as string, - { - issue_ids: data.delete_issue_ids, - }, - user - ) - .then((res) => { - setToastAlert({ - title: "Success", - type: "success", - message: res.message, - }); - handleClose(); - }) - .catch((e) => { - console.log(e); - }); - } - }; - return ( setQuery("")} appear> From 62392be5a3aa018454f10e7786bceee7de25a5c0 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:10:19 +0530 Subject: [PATCH 07/57] chore: info icon for activity graph (#1353) --- .../components/workspace/activity-graph.tsx | 153 +++++++++++++++--- .../app/components/workspace/issues-stats.tsx | 14 +- 2 files changed, 142 insertions(+), 25 deletions(-) diff --git a/apps/app/components/workspace/activity-graph.tsx b/apps/app/components/workspace/activity-graph.tsx index ec8e1dfd4..1f9db203d 100644 --- a/apps/app/components/workspace/activity-graph.tsx +++ b/apps/app/components/workspace/activity-graph.tsx @@ -1,34 +1,141 @@ +import { useEffect, useRef, useState } from "react"; + // ui -import { CalendarGraph } from "components/ui"; +import { Tooltip } from "components/ui"; // helpers -import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; +import { renderDateFormat, renderShortNumericDateFormat } from "helpers/date-time.helper"; // types import { IUserActivity } from "types"; +// constants +import { DAYS, MONTHS } from "constants/project"; type Props = { activities: IUserActivity[] | undefined; }; -export const ActivityGraph: React.FC = ({ activities }) => ( - ({ - day: activity.created_date, - value: activity.activity_count, - })) ?? [] +export const ActivityGraph: React.FC = ({ activities }) => { + const ref = useRef(null); + + const [width, setWidth] = useState(0); + + const today = new Date(); + const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1); + const twoMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 2, 1); + const threeMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 3, 1); + const fourMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 4, 1); + const fiveMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 5, 1); + + const recentMonths = [ + fiveMonthsAgo, + fourMonthsAgo, + threeMonthsAgo, + twoMonthsAgo, + lastMonth, + today, + ]; + + const getDatesOfMonth = (dateOfMonth: Date) => { + const month = dateOfMonth.getMonth(); + const year = dateOfMonth.getFullYear(); + + const dates = []; + const date = new Date(year, month, 1); + + while (date.getMonth() === month && date < new Date()) { + dates.push(renderDateFormat(new Date(date))); + date.setDate(date.getDate() + 1); } - from={activities?.length ? activities[0].created_date : new Date()} - to={activities?.length ? activities[activities.length - 1].created_date : new Date()} - height="200px" - margin={{ bottom: 0, left: 10, right: 10, top: 0 }} - tooltip={(datum) => ( -
- {renderShortDateWithYearFormat(datum.day)}:{" "} - {datum.value} + + return dates; + }; + + const recentDates = [ + ...getDatesOfMonth(recentMonths[0]), + ...getDatesOfMonth(recentMonths[1]), + ...getDatesOfMonth(recentMonths[2]), + ...getDatesOfMonth(recentMonths[3]), + ...getDatesOfMonth(recentMonths[4]), + ...getDatesOfMonth(recentMonths[5]), + ]; + + const activitiesIntensity = (activityCount: number) => { + if (activityCount <= 3) return "opacity-20"; + else if (activityCount > 3 && activityCount <= 6) return "opacity-40"; + else if (activityCount > 6 && activityCount <= 9) return "opacity-80"; + else return ""; + }; + + const addPaddingTiles = () => { + const firstDateDay = new Date(recentDates[0]).getDay(); + + for (let i = 0; i < firstDateDay; i++) recentDates.unshift(""); + }; + addPaddingTiles(); + + useEffect(() => { + if (!ref.current) return; + + setWidth(ref.current.offsetWidth); + }, [ref]); + + return ( +
+
+
+ {DAYS.map((day, index) => ( +
+ {index % 2 === 0 && day.substring(0, 3)} +
+ ))} +
+
+
+ {recentMonths.map((month, index) => ( +
+ {MONTHS[month.getMonth()].substring(0, 3)} +
+ ))} +
+
+ {recentDates.map((date, index) => { + const isActive = activities?.find((a) => a.created_date === date); + + return ( + +
+ + ); + })} +
+
+ Less + + + + + + More +
+
- )} - theme={{ - background: "rgb(var(--color-bg-base))", - }} - /> -); +
+ ); +}; diff --git a/apps/app/components/workspace/issues-stats.tsx b/apps/app/components/workspace/issues-stats.tsx index bc7f0364f..8e108a676 100644 --- a/apps/app/components/workspace/issues-stats.tsx +++ b/apps/app/components/workspace/issues-stats.tsx @@ -1,7 +1,9 @@ // components import { ActivityGraph } from "components/workspace"; // ui -import { Loader } from "components/ui"; +import { Loader, Tooltip } from "components/ui"; +// icons +import { InformationCircleIcon } from "@heroicons/react/24/outline"; // types import { IUserWorkspaceDashboard } from "types"; @@ -66,7 +68,15 @@ export const IssuesStats: React.FC = ({ data }) => (
-

Activity Graph

+

+ Activity Graph + + + +

From 41b7544cfce1fa3ceb7bf24d2579490a2cf0ea36 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:18:38 +0530 Subject: [PATCH 08/57] feat: search endpoint (#1317) * feat: search endpoint for parent issue selection * feat: blocker and blocked by search endpoint * refactor: blocker and blocked by components and types * refactor: blocker and blocked by components, feeat: cycle and module new search endpoints * chore: sub-issues param change * style: show selected issues list --- .../command-palette/command-pallette.tsx | 2 +- .../core/existing-issues-list-modal.tsx | 308 +++++++++------- .../issues/parent-issues-list-modal.tsx | 278 +++++++-------- apps/app/components/issues/select/parent.tsx | 1 - .../issues/sidebar-select/blocked.tsx | 330 +++++------------ .../issues/sidebar-select/blocker.tsx | 331 +++++------------- .../issues/sidebar-select/parent.tsx | 7 +- apps/app/components/issues/sidebar.tsx | 15 +- .../app/components/issues/sub-issues-list.tsx | 29 +- .../projects/[projectId]/cycles/[cycleId].tsx | 54 ++- .../projects/[projectId]/issues/[issueId].tsx | 16 +- .../[projectId]/modules/[moduleId].tsx | 37 +- apps/app/services/project.service.ts | 16 + apps/app/types/issues.d.ts | 18 +- apps/app/types/projects.d.ts | 22 ++ 15 files changed, 584 insertions(+), 880 deletions(-) diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index ffbe67ca5..cb2330b24 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -81,7 +81,7 @@ export const CommandPalette: React.FC = () => { const [deleteIssueModal, setDeleteIssueModal] = useState(false); const [isCreateUpdatePageModalOpen, setIsCreateUpdatePageModalOpen] = useState(false); - const [searchTerm, setSearchTerm] = React.useState(""); + const [searchTerm, setSearchTerm] = useState(""); const [results, setResults] = useState({ results: { workspace: [], diff --git a/apps/app/components/core/existing-issues-list-modal.tsx b/apps/app/components/core/existing-issues-list-modal.tsx index fe6f1e1b4..e9a004a5e 100644 --- a/apps/app/components/core/existing-issues-list-modal.tsx +++ b/apps/app/components/core/existing-issues-list-modal.tsx @@ -1,23 +1,24 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; import { mutate } from "swr"; -// react-hook-form -import { Controller, SubmitHandler, useForm } from "react-hook-form"; // headless ui import { Combobox, Dialog, Transition } from "@headlessui/react"; +// services +import projectService from "services/project.service"; // hooks import useToast from "hooks/use-toast"; import useIssuesView from "hooks/use-issues-view"; +import useDebounce from "hooks/use-debounce"; // ui -import { PrimaryButton, SecondaryButton } from "components/ui"; +import { Loader, PrimaryButton, SecondaryButton } from "components/ui"; // icons -import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { LayerDiagonalIcon } from "components/icons"; // types -import { IIssue } from "types"; +import { ISearchIssueResponse, TProjectIssuesSearchParams } from "types"; // fetch-keys import { CYCLE_DETAILS, @@ -26,27 +27,30 @@ import { MODULE_ISSUES_WITH_PARAMS, } from "constants/fetch-keys"; -type FormInput = { - issues: string[]; -}; - type Props = { isOpen: boolean; handleClose: () => void; - issues: IIssue[]; - handleOnSubmit: any; + searchParams: Partial; + handleOnSubmit: (data: ISearchIssueResponse[]) => Promise; }; export const ExistingIssuesListModal: React.FC = ({ isOpen, handleClose: onClose, - issues, + searchParams, handleOnSubmit, }) => { - const [query, setQuery] = useState(""); + const [searchTerm, setSearchTerm] = useState(""); + const [issues, setIssues] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isSearching, setIsSearching] = useState(false); + const [selectedIssues, setSelectedIssues] = useState([]); + const [isSubmitting, setIsSubmitting] = useState(false); + + const debouncedSearchTerm: string = useDebounce(searchTerm, 500); const router = useRouter(); - const { cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId } = router.query; const { setToastAlert } = useToast(); @@ -54,23 +58,12 @@ export const ExistingIssuesListModal: React.FC = ({ const handleClose = () => { onClose(); - setQuery(""); - reset(); + setSearchTerm(""); + setSelectedIssues([]); }; - const { - handleSubmit, - reset, - control, - formState: { isSubmitting }, - } = useForm({ - defaultValues: { - issues: [], - }, - }); - - const onSubmit: SubmitHandler = async (data) => { - if (!data.issues || data.issues.length === 0) { + const onSubmit = async () => { + if (selectedIssues.length === 0) { setToastAlert({ type: "error", title: "Error!", @@ -80,11 +73,15 @@ export const ExistingIssuesListModal: React.FC = ({ return; } - await handleOnSubmit(data); + setIsSubmitting(true); + + await handleOnSubmit(selectedIssues).finally(() => setIsSubmitting(false)); + if (cycleId) { mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); mutate(CYCLE_DETAILS(cycleId as string)); } + if (moduleId) { mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); mutate(MODULE_DETAILS(moduleId as string)); @@ -95,18 +92,45 @@ export const ExistingIssuesListModal: React.FC = ({ setToastAlert({ title: "Success", type: "success", - message: `Issue${data.issues.length > 1 ? "s" : ""} added successfully`, + message: `Issue${selectedIssues.length > 1 ? "s" : ""} added successfully`, }); }; - const filteredIssues: IIssue[] = - query === "" - ? issues ?? [] - : issues.filter((issue) => issue.name.toLowerCase().includes(query.toLowerCase())) ?? []; + useEffect(() => { + if (!workspaceSlug || !projectId) return; + + setIsLoading(true); + + if (debouncedSearchTerm) { + setIsSearching(true); + + projectService + .projectIssuesSearch(workspaceSlug as string, projectId as string, { + search: debouncedSearchTerm, + ...searchParams, + }) + .then((res) => { + setIssues(res); + }) + .finally(() => { + setIsLoading(false); + setIsSearching(false); + }); + } else { + setIssues([]); + setIsLoading(false); + setIsSearching(false); + } + }, [debouncedSearchTerm, workspaceSlug, projectId, searchParams]); return ( <> - setQuery("")} appear> + setSearchTerm("")} + appear + > = ({ leaveTo="opacity-0 scale-95" > -
- ( - -
-
+ { + if (selectedIssues.some((i) => i.id === val.id)) + setSelectedIssues((prevData) => prevData.filter((i) => i.id !== val.id)); + else setSelectedIssues((prevData) => [...prevData, val]); + }} + > +
+
- - {filteredIssues.length > 0 ? ( -
  • - {query === "" && ( -

    - Select issues to add -

    - )} -
      - {filteredIssues.map((issue) => ( - - `flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-brand-secondary ${ - active ? "bg-brand-surface-2 text-brand-base" : "" - } ${selected ? "text-brand-base" : ""}` - } - > - {({ selected }) => ( - <> - - - - {issue.project_detail.identifier}-{issue.sequence_id} - - {issue.name} - - )} - - ))} -
    -
  • - ) : ( -
    - -

    - No issues found. Create a new issue with{" "} -
    C
    - . -

    -
    - )} -
    -
    +
    + {selectedIssues.length > 0 ? ( +
    + {selectedIssues.map((issue) => ( +
    + {issue.project__identifier}-{issue.sequence_id} + +
    + ))} +
    + ) : ( +
    + No issues selected +
    )} - /> - {filteredIssues.length > 0 && ( -
    - Cancel - - {isSubmitting ? "Adding..." : "Add selected issues"} - -
    - )} - +
    + + + {debouncedSearchTerm !== "" && ( +
    + Search results for{" "} + + {'"'} + {debouncedSearchTerm} + {'"'} + {" "} + in project: +
    + )} + + {!isLoading && + issues.length === 0 && + searchTerm !== "" && + debouncedSearchTerm !== "" && ( +
    + +

    + No issues found. Create a new issue with{" "} +
    +                              C
    +                            
    + . +

    +
    + )} + + {isLoading || isSearching ? ( + + + + + + + ) : ( +
      0 ? "p-2" : ""}`}> + {issues.map((issue) => { + const selected = selectedIssues.some((i) => i.id === issue.id); + + return ( + + `flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-brand-secondary ${ + active ? "bg-brand-surface-2 text-brand-base" : "" + } ${selected ? "text-brand-base" : ""}` + } + > + + + + {issue.project__identifier}-{issue.sequence_id} + + {issue.name} + + ); + })} +
    + )} +
    +
    + {selectedIssues.length > 0 && ( +
    + Cancel + + {isSubmitting ? "Adding..." : "Add selected issues"} + +
    + )}
    diff --git a/apps/app/components/issues/parent-issues-list-modal.tsx b/apps/app/components/issues/parent-issues-list-modal.tsx index 7510d5e75..b93c07d3c 100644 --- a/apps/app/components/issues/parent-issues-list-modal.tsx +++ b/apps/app/components/issues/parent-issues-list-modal.tsx @@ -1,23 +1,28 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; + +import { useRouter } from "next/router"; // headless ui import { Combobox, Dialog, Transition } from "@headlessui/react"; -// icons -import { MagnifyingGlassIcon, RectangleStackIcon } from "@heroicons/react/24/outline"; -// ui -import { PrimaryButton, SecondaryButton } from "components/ui"; -// types -import { IIssue } from "types"; +// services +import projectService from "services/project.service"; +// hooks +import useDebounce from "hooks/use-debounce"; +// components import { LayerDiagonalIcon } from "components/icons"; +// ui +import { Loader } from "components/ui"; +// icons +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +// types +import { ISearchIssueResponse } from "types"; type Props = { isOpen: boolean; handleClose: () => void; value?: any; onChange: (...event: any[]) => void; - issues: IIssue[]; - title?: string; - multiple?: boolean; + issueId?: string; customDisplay?: JSX.Element; }; @@ -26,28 +31,60 @@ export const ParentIssuesListModal: React.FC = ({ handleClose: onClose, value, onChange, - issues, - title = "Issues", - multiple = false, + issueId, customDisplay, }) => { - const [query, setQuery] = useState(""); - const [values, setValues] = useState([]); + const [searchTerm, setSearchTerm] = useState(""); + const [issues, setIssues] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isSearching, setIsSearching] = useState(false); + + const debouncedSearchTerm: string = useDebounce(searchTerm, 500); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; const handleClose = () => { onClose(); - setQuery(""); - setValues([]); + setSearchTerm(""); }; - const filteredIssues: IIssue[] = - query === "" - ? issues ?? [] - : issues?.filter((issue) => issue.name.toLowerCase().includes(query.toLowerCase())) ?? []; + useEffect(() => { + if (!workspaceSlug || !projectId) return; + + setIsLoading(true); + + if (debouncedSearchTerm) { + setIsSearching(true); + + projectService + .projectIssuesSearch(workspaceSlug as string, projectId as string, { + search: debouncedSearchTerm, + parent: true, + issue_id: issueId, + }) + .then((res) => { + setIssues(res); + }) + .finally(() => { + setIsLoading(false); + setIsSearching(false); + }); + } else { + setIssues([]); + setIsLoading(false); + setIsSearching(false); + } + }, [debouncedSearchTerm, workspaceSlug, projectId, issueId]); return ( <> - setQuery("")} appear> + setSearchTerm("")} + appear + > = ({ leaveTo="opacity-0 scale-95" > - {multiple ? ( - <> - ({})} multiple> -
    -
    - {customDisplay &&
    {customDisplay}
    } - - {filteredIssues.length > 0 && ( -
  • - {query === "" && ( -

    {title}

    - )} -
      - {filteredIssues.map((issue) => ( - - `flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-brand-secondary ${ - active ? "bg-brand-surface-2 text-brand-base" : "" - } ${selected ? "text-brand-base" : ""}` - } - > - {({ selected }) => ( - <> - - - - {issue.project_detail?.identifier}-{issue.sequence_id} - {" "} - {issue.id} - - )} - - ))} -
    -
  • - )} -
    + +
    +
    + {customDisplay &&
    {customDisplay}
    } + + {debouncedSearchTerm !== "" && ( +
    + Search results for{" "} + + {'"'} + {debouncedSearchTerm} + {'"'} + {" "} + in project: +
    + )} - {query !== "" && filteredIssues.length === 0 && ( -
    -
    - )} -
    -
    - Cancel - onChange(values)}>Add issues -
    - - ) : ( - -
    -
    - {customDisplay &&
    {customDisplay}
    } - - {filteredIssues.length > 0 ? ( -
  • - {query === "" && ( -

    {title}

    - )} -
      - {filteredIssues.map((issue) => ( - - `flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-brand-secondary ${ - active ? "bg-brand-surface-2 text-brand-base" : "" - } ${selected ? "text-brand-base" : ""}` - } - onClick={handleClose} - > - <> - - - {issue.project_detail?.identifier}-{issue.sequence_id} - {" "} - {issue.name} - - - ))} -
    -
  • - ) : ( + {!isLoading && + issues.length === 0 && + searchTerm !== "" && + debouncedSearchTerm !== "" && (

    @@ -208,9 +152,45 @@ export const ParentIssuesListModal: React.FC = ({

    )} -
    -
    - )} + + {isLoading || isSearching ? ( + + + + + + + ) : ( +
      0 ? "p-2" : ""}`}> + {issues.map((issue) => ( + + `flex cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-brand-secondary ${ + active ? "bg-brand-surface-2 text-brand-base" : "" + } ${selected ? "text-brand-base" : ""}` + } + onClick={handleClose} + > + <> + + + {issue.project__identifier}-{issue.sequence_id} + {" "} + {issue.name} + + + ))} +
    + )} + +
    diff --git a/apps/app/components/issues/select/parent.tsx b/apps/app/components/issues/select/parent.tsx index c04e89b92..d73cd4e73 100644 --- a/apps/app/components/issues/select/parent.tsx +++ b/apps/app/components/issues/select/parent.tsx @@ -21,7 +21,6 @@ export const IssueParentSelect: React.FC = ({ control, isOpen, setIsOpen, isOpen={isOpen} handleClose={() => setIsOpen(false)} onChange={onChange} - issues={issues} /> )} /> diff --git a/apps/app/components/issues/sidebar-select/blocked.tsx b/apps/app/components/issues/sidebar-select/blocked.tsx index c07f80817..de8985792 100644 --- a/apps/app/components/issues/sidebar-select/blocked.tsx +++ b/apps/app/components/issues/sidebar-select/blocked.tsx @@ -3,299 +3,135 @@ import React, { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import useSWR from "swr"; - // react-hook-form -import { SubmitHandler, useForm, UseFormWatch } from "react-hook-form"; -// headless ui -import { Combobox, Dialog, Transition } from "@headlessui/react"; +import { UseFormWatch } from "react-hook-form"; // hooks import useToast from "hooks/use-toast"; -// services -import issuesService from "services/issues.service"; -// ui -import { PrimaryButton, SecondaryButton } from "components/ui"; +import useProjectDetails from "hooks/use-project-details"; +// components +import { ExistingIssuesListModal } from "components/core"; // icons -import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline"; -import { BlockedIcon, LayerDiagonalIcon } from "components/icons"; +import { XMarkIcon } from "@heroicons/react/24/outline"; +import { BlockedIcon } from "components/icons"; // types -import { IIssue, UserAuth } from "types"; -// fetch-keys -import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; - -type FormInput = { - blocked_issue_ids: string[]; -}; +import { BlockeIssue, IIssue, ISearchIssueResponse, UserAuth } from "types"; type Props = { + issueId?: string; submitChanges: (formData: Partial) => void; - issuesList: IIssue[]; watch: UseFormWatch; userAuth: UserAuth; }; export const SidebarBlockedSelect: React.FC = ({ + issueId, submitChanges, - issuesList, watch, userAuth, }) => { - const [query, setQuery] = useState(""); const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false); const { setToastAlert } = useToast(); + const { projectDetails } = useProjectDetails(); const router = useRouter(); const { workspaceSlug, projectId } = router.query; - const { data: issues } = useSWR( - workspaceSlug && projectId - ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) - : null, - workspaceSlug && projectId - ? () => issuesService.getIssues(workspaceSlug as string, projectId as string) - : null - ); - - const { - handleSubmit, - reset, - watch: watchBlocked, - setValue, - } = useForm({ - defaultValues: { - blocked_issue_ids: [], - }, - }); - const handleClose = () => { setIsBlockedModalOpen(false); - reset(); }; - const onSubmit: SubmitHandler = (data) => { - if (!data.blocked_issue_ids || data.blocked_issue_ids.length === 0) { + const onSubmit = async (data: ISearchIssueResponse[]) => { + if (data.length === 0) { setToastAlert({ title: "Error", type: "error", - message: "Please select atleast one issue", + message: "Please select at least one issue", }); + return; } - if (!Array.isArray(data.blocked_issue_ids)) data.blocked_issue_ids = [data.blocked_issue_ids]; + const selectedIssues: BlockeIssue[] = data.map((i) => ({ + blocked_issue_detail: { + id: i.id, + name: i.name, + sequence_id: i.sequence_id, + }, + })); - const newBlocked = [...watch("blocked_list"), ...data.blocked_issue_ids]; - submitChanges({ blocks_list: newBlocked }); + const newBlocked = [...watch("blocked_issues"), ...selectedIssues]; + + submitChanges({ + blocked_issues: newBlocked, + blocks_list: newBlocked.map((i) => i.blocked_issue_detail?.id ?? ""), + }); handleClose(); }; - const filteredIssues: IIssue[] = - query === "" - ? issuesList - : issuesList.filter( - (issue) => - issue.name.toLowerCase().includes(query.toLowerCase()) || - `${issue.project_detail.identifier}-${issue.sequence_id}` - .toLowerCase() - .includes(query.toLowerCase()) - ); - const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( -
    -
    - -

    Blocked by

    -
    -
    -
    - {watch("blocked_list") && watch("blocked_list").length > 0 - ? watch("blocked_list").map((issue) => ( -
    - i.id === issue)?.id - }`} - > - - - {`${issues?.find((i) => i.id === issue)?.project_detail?.identifier}-${ - issues?.find((i) => i.id === issue)?.sequence_id - }`} - - - -
    - )) - : null} + <> + setIsBlockedModalOpen(false)} + searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + handleOnSubmit={onSubmit} + /> +
    +
    + +

    Blocked by

    - setQuery("")} - appear - > - - -
    - +
    +
    + {watch("blocked_issues") && watch("blocked_issues").length > 0 + ? watch("blocked_issues").map((issue) => ( +
    + + + + {`${projectDetails?.identifier}-${issue.blocked_issue_detail?.sequence_id}`} + + +
    -
    - + + +
    + )) + : null} +
    + +
    -
    + ); }; diff --git a/apps/app/components/issues/sidebar-select/blocker.tsx b/apps/app/components/issues/sidebar-select/blocker.tsx index aeede09bb..40f1eb10f 100644 --- a/apps/app/components/issues/sidebar-select/blocker.tsx +++ b/apps/app/components/issues/sidebar-select/blocker.tsx @@ -3,296 +3,137 @@ import React, { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; -import useSWR from "swr"; - // react-hook-form -import { SubmitHandler, useForm, UseFormWatch } from "react-hook-form"; -// headless ui -import { Combobox, Dialog, Transition } from "@headlessui/react"; +import { UseFormWatch } from "react-hook-form"; // hooks import useToast from "hooks/use-toast"; -// services -import issuesServices from "services/issues.service"; -// ui -import { PrimaryButton, SecondaryButton } from "components/ui"; +import useProjectDetails from "hooks/use-project-details"; +// components +import { ExistingIssuesListModal } from "components/core"; // icons -import { MagnifyingGlassIcon, XMarkIcon } from "@heroicons/react/24/outline"; -import { BlockerIcon, LayerDiagonalIcon } from "components/icons"; +import { XMarkIcon } from "@heroicons/react/24/outline"; +import { BlockerIcon } from "components/icons"; // types -import { IIssue, UserAuth } from "types"; -// fetch-keys -import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; - -type FormInput = { - blocker_issue_ids: string[]; -}; +import { BlockeIssue, IIssue, ISearchIssueResponse, UserAuth } from "types"; type Props = { + issueId?: string; submitChanges: (formData: Partial) => void; - issuesList: IIssue[]; watch: UseFormWatch; userAuth: UserAuth; }; export const SidebarBlockerSelect: React.FC = ({ + issueId, submitChanges, - issuesList, watch, userAuth, }) => { - const [query, setQuery] = useState(""); const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false); const { setToastAlert } = useToast(); + const { projectDetails } = useProjectDetails(); const router = useRouter(); const { workspaceSlug, projectId } = router.query; - const { data: issues } = useSWR( - workspaceSlug && projectId - ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) - : null, - workspaceSlug && projectId - ? () => issuesServices.getIssues(workspaceSlug as string, projectId as string) - : null - ); - - const { - handleSubmit, - reset, - watch: watchBlocker, - setValue, - } = useForm({ - defaultValues: { - blocker_issue_ids: [], - }, - }); - const handleClose = () => { setIsBlockerModalOpen(false); - reset(); }; - const onSubmit: SubmitHandler = (data) => { - if (!data.blocker_issue_ids || data.blocker_issue_ids.length === 0) { + const onSubmit = async (data: ISearchIssueResponse[]) => { + if (data.length === 0) { setToastAlert({ - title: "Error", type: "error", - message: "Please select atleast one issue", + title: "Error!", + message: "Please select at least one issue.", }); + return; } - if (!Array.isArray(data.blocker_issue_ids)) data.blocker_issue_ids = [data.blocker_issue_ids]; + const selectedIssues: BlockeIssue[] = data.map((i) => ({ + blocker_issue_detail: { + id: i.id, + name: i.name, + sequence_id: i.sequence_id, + }, + })); - const newBlockers = [...watch("blockers_list"), ...data.blocker_issue_ids]; - submitChanges({ blockers_list: newBlockers }); + const newBlockers = [...watch("blocker_issues"), ...selectedIssues]; + + submitChanges({ + blocker_issues: newBlockers, + blockers_list: newBlockers.map((i) => i.blocker_issue_detail?.id ?? ""), + }); handleClose(); }; - const filteredIssues: IIssue[] = - query === "" - ? issuesList - : issuesList.filter( - (issue) => - issue.name.toLowerCase().includes(query.toLowerCase()) || - `${issue.project_detail.identifier}-${issue.sequence_id}` - .toLowerCase() - .includes(query.toLowerCase()) - ); - const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( -
    -
    - -

    Blocking

    -
    -
    -
    - {watch("blockers_list") && watch("blockers_list").length > 0 - ? watch("blockers_list").map((issue) => ( -
    - i.id === issue)?.id - }`} - > - - - {`${issues?.find((i) => i.id === issue)?.project_detail?.identifier}-${ - issues?.find((i) => i.id === issue)?.sequence_id - }`} - - - -
    - )) - : null} + <> + setIsBlockerModalOpen(false)} + searchParams={{ blocker_blocked_by: true, issue_id: issueId }} + handleOnSubmit={onSubmit} + /> +
    +
    + +

    Blocking

    - setQuery("")} - appear - > - - -
    - - -
    - - - { - const selectedIssues = watchBlocker("blocker_issue_ids"); - if (selectedIssues.includes(val)) - setValue( - "blocker_issue_ids", - selectedIssues.filter((i) => i !== val) - ); - else setValue("blocker_issue_ids", [...selectedIssues, val]); - }} +
    +
    + {watch("blocker_issues") && watch("blocker_issues").length > 0 + ? watch("blocker_issues").map((issue) => ( +
    -
    -
    - - - {filteredIssues.length > 0 ? ( -
  • - {query === "" && ( -

    - Select blocker issues -

    - )} -
      - {filteredIssues.map((issue) => { - if ( - !watch("blockers_list").includes(issue.id) && - !watch("blocked_list").includes(issue.id) - ) - return ( - - `flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-brand-secondary ${ - active ? "bg-brand-surface-2 text-brand-base" : "" - } ` - } - > -
      - - - - { - issues?.find((i) => i.id === issue.id)?.project_detail - ?.identifier - } - -{issue.sequence_id} - - {issue.name} -
      -
      - ); - })} -
    -
  • - ) : ( -
    - -

    - No issues found. Create a new issue with{" "} -
    C
    . -

    -
    - )} -
    - + + + {`${projectDetails?.identifier}-${issue.blocker_issue_detail?.sequence_id}`} + + +
    -
    -
    - + submitChanges({ + blocker_issues: updatedBlockers, + blockers_list: updatedBlockers.map( + (i) => i.blocker_issue_detail?.id ?? "" + ), + }); + }} + > + + +
    + )) + : null} +
    + +
    -
    + ); }; diff --git a/apps/app/components/issues/sidebar-select/parent.tsx b/apps/app/components/issues/sidebar-select/parent.tsx index 92a51269f..9d183d262 100644 --- a/apps/app/components/issues/sidebar-select/parent.tsx +++ b/apps/app/components/issues/sidebar-select/parent.tsx @@ -20,7 +20,6 @@ import { PROJECT_ISSUES_LIST } from "constants/fetch-keys"; type Props = { control: Control; submitChanges: (formData: Partial) => void; - issuesList: IIssue[]; customDisplay: JSX.Element; watch: UseFormWatch; userAuth: UserAuth; @@ -29,7 +28,6 @@ type Props = { export const SidebarParentSelect: React.FC = ({ control, submitChanges, - issuesList, customDisplay, watch, userAuth, @@ -37,7 +35,7 @@ export const SidebarParentSelect: React.FC = ({ const [isParentModalOpen, setIsParentModalOpen] = useState(false); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId, issueId } = router.query; const { data: issues } = useSWR( workspaceSlug && projectId @@ -68,8 +66,7 @@ export const SidebarParentSelect: React.FC = ({ submitChanges({ parent: val }); onChange(val); }} - issues={issuesList} - title="Select Parent" + issueId={issueId as string} value={value} customDisplay={customDisplay} /> diff --git a/apps/app/components/issues/sidebar.tsx b/apps/app/components/issues/sidebar.tsx index cdc815d3c..6f231871b 100644 --- a/apps/app/components/issues/sidebar.tsx +++ b/apps/app/components/issues/sidebar.tsx @@ -370,14 +370,6 @@ export const IssueDetailsSidebar: React.FC = ({ - i.id !== issueDetail?.id && - i.id !== issueDetail?.parent && - i.parent !== issueDetail?.id - ) ?? [] - } customDisplay={ issueDetail?.parent_detail ? ( ) : ( -
    +
    No parent selected
    ) @@ -400,16 +393,16 @@ export const IssueDetailsSidebar: React.FC = ({ )} {(fieldsToShow.includes("all") || fieldsToShow.includes("blocker")) && ( i.id !== issueDetail?.id) ?? []} watch={watchIssue} userAuth={memberRole} /> )} {(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && ( i.id !== issueDetail?.id) ?? []} watch={watchIssue} userAuth={memberRole} /> diff --git a/apps/app/components/issues/sub-issues-list.tsx b/apps/app/components/issues/sub-issues-list.tsx index 76424767e..ac550348e 100644 --- a/apps/app/components/issues/sub-issues-list.tsx +++ b/apps/app/components/issues/sub-issues-list.tsx @@ -21,7 +21,7 @@ import { ChevronRightIcon, PlusIcon, XMarkIcon } from "@heroicons/react/24/outli // helpers import { orderArrayBy } from "helpers/array.helper"; // types -import { ICurrentUserResponse, IIssue, ISubIssueResponse } from "types"; +import { ICurrentUserResponse, IIssue, ISearchIssueResponse, ISubIssueResponse } from "types"; // fetch-keys import { PROJECT_ISSUES_LIST, SUB_ISSUES } from "constants/fetch-keys"; @@ -58,14 +58,16 @@ export const SubIssuesList: FC = ({ parentIssue, user }) => { : null ); - const addAsSubIssue = async (data: { issues: string[] }) => { + const addAsSubIssue = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; + const payload = { + sub_issue_ids: data.map((i) => i.id), + }; + await issuesService - .addSubIssues(workspaceSlug as string, projectId as string, parentIssue?.id ?? "", { - sub_issue_ids: data.issues, - }) - .then((res) => { + .addSubIssues(workspaceSlug as string, projectId as string, parentIssue?.id ?? "", payload) + .then(() => { mutate( SUB_ISSUES(parentIssue?.id ?? ""), (prevData) => { @@ -74,10 +76,12 @@ export const SubIssuesList: FC = ({ parentIssue, user }) => { const stateDistribution = { ...prevData.state_distribution }; - data.issues.forEach((issueId: string) => { + payload.sub_issue_ids.forEach((issueId: string) => { const issue = issues?.find((i) => i.id === issueId); + if (issue) { newSubIssues.push(issue); + const issueGroup = issue.state_detail.group; stateDistribution[issueGroup] = stateDistribution[issueGroup] + 1; } @@ -96,7 +100,7 @@ export const SubIssuesList: FC = ({ parentIssue, user }) => { PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string), (prevData) => (prevData ?? []).map((p) => { - if (data.issues.includes(p.id)) + if (payload.sub_issue_ids.includes(p.id)) return { ...p, parent: parentIssue.id, @@ -188,14 +192,7 @@ export const SubIssuesList: FC = ({ parentIssue, user }) => { setSubIssuesListModal(false)} - issues={ - issues?.filter( - (i) => - (i.parent === "" || i.parent === null) && - i.id !== parentIssue?.id && - i.id !== parentIssue?.parent - ) ?? [] - } + searchParams={{ sub_issue: true, issue_id: parentIssue?.id }} handleOnSubmit={addAsSubIssue} /> {subIssuesResponse && diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index af9f073a8..78af8e9e1 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; // icons import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { CyclesIcon } from "components/icons"; @@ -16,7 +16,6 @@ import { CycleDetailsSidebar } from "components/cycles"; // services import issuesService from "services/issues.service"; import cycleServices from "services/cycles.service"; -import projectService from "services/project.service"; // hooks import useToast from "hooks/use-toast"; import useUserAuth from "hooks/use-user-auth"; @@ -28,14 +27,10 @@ import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // helpers import { truncateText } from "helpers/string.helper"; import { getDateRangeStatus } from "helpers/date-time.helper"; +// types +import { ISearchIssueResponse } from "types"; // fetch-keys -import { - CYCLE_ISSUES, - CYCLES_LIST, - PROJECT_DETAILS, - CYCLE_DETAILS, - PROJECT_ISSUES_LIST, -} from "constants/fetch-keys"; +import { CYCLES_LIST, CYCLE_DETAILS } from "constants/fetch-keys"; const SingleCycle: React.FC = () => { const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false); @@ -49,13 +44,6 @@ const SingleCycle: React.FC = () => { const { setToastAlert } = useToast(); - const { data: activeProject } = useSWR( - workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, - workspaceSlug && projectId - ? () => projectService.getProject(workspaceSlug as string, projectId as string) - : null - ); - const { data: cycles } = useSWR( workspaceSlug && projectId ? CYCLES_LIST(projectId as string) : null, workspaceSlug && projectId @@ -75,15 +63,6 @@ const SingleCycle: React.FC = () => { : null ); - const { data: issues } = useSWR( - workspaceSlug && projectId - ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) - : null, - workspaceSlug && projectId - ? () => issuesService.getIssues(workspaceSlug as string, projectId as string) - : null - ); - const cycleStatus = cycleDetails?.start_date && cycleDetails?.end_date ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) @@ -93,14 +72,21 @@ const SingleCycle: React.FC = () => { setCycleIssuesListModal(true); }; - const handleAddIssuesToCycle = async (data: { issues: string[] }) => { + const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; + const payload = { + issues: data.map((i) => i.id), + }; + await issuesService - .addIssueToCycle(workspaceSlug as string, projectId as string, cycleId as string, data, user) - .then(() => { - mutate(CYCLE_ISSUES(cycleId as string)); - }) + .addIssueToCycle( + workspaceSlug as string, + projectId as string, + cycleId as string, + payload, + user + ) .catch(() => { setToastAlert({ type: "error", @@ -115,15 +101,15 @@ const SingleCycle: React.FC = () => { setCycleIssuesListModal(false)} - issues={issues?.filter((i) => !i.cycle_id) ?? []} + searchParams={{ cycle: true }} handleOnSubmit={handleAddIssuesToCycle} /> } @@ -142,7 +128,7 @@ const SingleCycle: React.FC = () => { {truncateText(cycle.name, 40)} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 516a2471b..93bfecfe0 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -31,8 +31,6 @@ const defaultValues = { state: "", assignees_list: [], priority: "low", - blockers_list: [], - blocked_list: [], target_date: new Date().toString(), issue_cycle: null, issue_module: null, @@ -65,6 +63,7 @@ const IssueDetailsPage: NextPage = () => { ISSUE_DETAILS(issueId as string), (prevData) => { if (!prevData) return prevData; + return { ...prevData, ...formData, @@ -73,10 +72,13 @@ const IssueDetailsPage: NextPage = () => { false ); - const payload = { ...formData }; + const payload: Partial = { + ...formData, + }; + await issuesService .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user) - .then((res) => { + .then(() => { mutateIssueDetails(); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); }) @@ -93,12 +95,6 @@ const IssueDetailsPage: NextPage = () => { mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); reset({ ...issueDetails, - blockers_list: - issueDetails.blockers_list ?? - issueDetails.blocker_issues?.map((issue) => issue.blocker_issue_detail?.id), - blocked_list: - issueDetails.blocks_list ?? - issueDetails.blocked_issues?.map((issue) => issue.blocked_issue_detail?.id), assignees_list: issueDetails.assignees_list ?? issueDetails.assignee_details?.map((user) => user.id), labels_list: issueDetails.labels_list ?? issueDetails.labels, diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx index d63af5865..51b6b7a5b 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/[moduleId].tsx @@ -2,13 +2,12 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; // icons import { ArrowLeftIcon, RectangleGroupIcon } from "@heroicons/react/24/outline"; // services import modulesService from "services/modules.service"; -import issuesService from "services/issues.service"; // hooks import useToast from "hooks/use-toast"; import useUserAuth from "hooks/use-user-auth"; @@ -21,20 +20,14 @@ import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "component import { ModuleDetailsSidebar } from "components/modules"; import { AnalyticsProjectModal } from "components/analytics"; // ui -import { CustomMenu, EmptySpace, EmptySpaceItem, SecondaryButton, Spinner } from "components/ui"; +import { CustomMenu, SecondaryButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // helpers import { truncateText } from "helpers/string.helper"; // types -import { IModule } from "types"; - +import { ISearchIssueResponse } from "types"; // fetch-keys -import { - MODULE_DETAILS, - MODULE_ISSUES, - MODULE_LIST, - PROJECT_ISSUES_LIST, -} from "constants/fetch-keys"; +import { MODULE_DETAILS, MODULE_ISSUES, MODULE_LIST } from "constants/fetch-keys"; const SingleModule: React.FC = () => { const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false); @@ -48,15 +41,6 @@ const SingleModule: React.FC = () => { const { setToastAlert } = useToast(); - const { data: issues } = useSWR( - workspaceSlug && projectId - ? PROJECT_ISSUES_LIST(workspaceSlug as string, projectId as string) - : null, - workspaceSlug && projectId - ? () => issuesService.getIssues(workspaceSlug as string, projectId as string) - : null - ); - const { data: modules } = useSWR( workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null, workspaceSlug && projectId @@ -76,7 +60,7 @@ const SingleModule: React.FC = () => { : null ); - const { data: moduleDetails } = useSWR( + const { data: moduleDetails } = useSWR( moduleId ? MODULE_DETAILS(moduleId as string) : null, workspaceSlug && projectId ? () => @@ -88,18 +72,21 @@ const SingleModule: React.FC = () => { : null ); - const handleAddIssuesToModule = async (data: { issues: string[] }) => { + const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => { if (!workspaceSlug || !projectId) return; + const payload = { + issues: data.map((i) => i.id), + }; + await modulesService .addIssuesToModule( workspaceSlug as string, projectId as string, moduleId as string, - data, + payload, user ) - .then(() => mutate(MODULE_ISSUES(moduleId as string))) .catch(() => setToastAlert({ type: "error", @@ -118,7 +105,7 @@ const SingleModule: React.FC = () => { setModuleIssuesListModal(false)} - issues={issues?.filter((i) => !i.module_id) ?? []} + searchParams={{ module: true }} handleOnSubmit={handleAddIssuesToModule} /> { + return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/search-issues/`, { + params, + }) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } export default new ProjectServices(); diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index 0ac0207be..a8881924b 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -69,12 +69,8 @@ export interface IIssue { assignees_list: string[]; attachment_count: number; attachments: any[]; - blocked_by_issue_details: any[]; - blocked_issue_details: any[]; blocked_issues: BlockeIssue[]; - blocked_list: string[]; blocker_issues: BlockeIssue[]; - blockers: any[]; blockers_list: string[]; blocks_list: string[]; bridge_id?: string | null; @@ -141,26 +137,14 @@ export interface ISubIssueResponse { } export interface BlockeIssue { - id: string; blocked_issue_detail?: BlockeIssueDetail; - created_at: Date; - updated_at: Date; - created_by: string; - updated_by: string; - project: string; - workspace: string; - block: string; - blocked_by: string; blocker_issue_detail?: BlockeIssueDetail; } export interface BlockeIssueDetail { id: string; name: string; - description: string; - priority: null; - start_date: null; - target_date: null; + sequence_id: number; } export interface IIssueComment { diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index 51cc4ba9a..c9972bc62 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -124,3 +124,25 @@ export interface GithubRepositoriesResponse { repositories: IGithubRepository[]; total_count: number; } + +export type TProjectIssuesSearchParams = { + search: string; + parent?: boolean; + blocker_blocked_by?: boolean; + cycle?: boolean; + module?: boolean; + sub_issue?: boolean; + issue_id?: string; +}; + +export interface ISearchIssueResponse { + id: string; + name: string; + project_id: string; + project__identifier: string; + sequence_id: number; + state__color: string; + state__group: string; + state__name: string; + workspace__slug: string; +} From 71b2884b570c260118ab8a8f16656d011f819bf8 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:19:26 +0530 Subject: [PATCH 09/57] chore: route to issue after creating it (#1359) * chore: navigate to newly created inbox issue * refactor: inbox * fix: hide ai modal after issue creation * chore: hide action buttons after acting upon them * chore: add icon to inbox status * chore: update inbox status colors --- .../app/components/inbox/filters-dropdown.tsx | 126 +++--- .../components/inbox/inbox-action-headers.tsx | 415 ++++++++++-------- .../app/components/inbox/inbox-issue-card.tsx | 179 ++++---- .../components/inbox/inbox-main-content.tsx | 114 +++-- apps/app/components/issues/form.tsx | 2 + apps/app/components/issues/modal.tsx | 4 + apps/app/constants/inbox.ts | 15 + apps/app/pages/[workspaceSlug]/index.tsx | 2 +- .../projects/[projectId]/inbox/[inboxId].tsx | 222 +--------- apps/app/services/inbox.service.ts | 2 +- 10 files changed, 517 insertions(+), 564 deletions(-) diff --git a/apps/app/components/inbox/filters-dropdown.tsx b/apps/app/components/inbox/filters-dropdown.tsx index b567baed0..1b6af608c 100644 --- a/apps/app/components/inbox/filters-dropdown.tsx +++ b/apps/app/components/inbox/filters-dropdown.tsx @@ -1,63 +1,81 @@ +// hooks +import useInboxView from "hooks/use-inbox-view"; // ui import { MultiLevelDropdown } from "components/ui"; // icons import { getPriorityIcon } from "components/icons"; -// types -import { IInboxFilterOptions } from "types"; // constants import { PRIORITIES } from "constants/project"; import { INBOX_STATUS } from "constants/inbox"; -type Props = { - filters: Partial; - onSelect: (option: any) => void; - direction?: "left" | "right"; - height?: "sm" | "md" | "rg" | "lg"; -}; +export const FiltersDropdown: React.FC = () => { + const { filters, setFilters, filtersLength } = useInboxView(); -export const FiltersDropdown: React.FC = ({ filters, onSelect, direction, height }) => ( - ({ - id: priority ?? "none", - label: ( -
    - {getPriorityIcon(priority)} {priority ?? "None"} -
    - ), - value: { - key: "priority", - value: priority, - }, - selected: filters?.priority?.includes(priority ?? "none"), - })), - ], - }, - { - id: "inbox_status", - label: "Status", - value: INBOX_STATUS.map((status) => status.value), - children: [ - ...INBOX_STATUS.map((status) => ({ - id: status.key, - label: status.label, - value: { - key: "inbox_status", - value: status.value, - }, - selected: filters?.inbox_status?.includes(status.value), - })), - ], - }, - ]} - /> -); + return ( +
    + { + const key = option.key as keyof typeof filters; + + const valueExists = (filters[key] as any[])?.includes(option.value); + + if (valueExists) { + setFilters({ + [option.key]: ((filters[key] ?? []) as any[])?.filter((val) => val !== option.value), + }); + } else { + setFilters({ + [option.key]: [...((filters[key] ?? []) as any[]), option.value], + }); + } + }} + direction="right" + height="rg" + options={[ + { + id: "priority", + label: "Priority", + value: PRIORITIES, + children: [ + ...PRIORITIES.map((priority) => ({ + id: priority ?? "none", + label: ( +
    + {getPriorityIcon(priority)} {priority ?? "None"} +
    + ), + value: { + key: "priority", + value: priority, + }, + selected: filters?.priority?.includes(priority ?? "none"), + })), + ], + }, + { + id: "inbox_status", + label: "Status", + value: INBOX_STATUS.map((status) => status.value), + children: [ + ...INBOX_STATUS.map((status) => ({ + id: status.key, + label: status.label, + value: { + key: "inbox_status", + value: status.value, + }, + selected: filters?.inbox_status?.includes(status.value), + })), + ], + }, + ]} + /> + {filtersLength > 0 && ( +
    + {filtersLength} +
    + )} +
    + ); +}; diff --git a/apps/app/components/inbox/inbox-action-headers.tsx b/apps/app/components/inbox/inbox-action-headers.tsx index 5ceaa8f2c..5702d560b 100644 --- a/apps/app/components/inbox/inbox-action-headers.tsx +++ b/apps/app/components/inbox/inbox-action-headers.tsx @@ -2,17 +2,27 @@ import { useEffect, useState } from "react"; import { useRouter } from "next/router"; +import { mutate } from "swr"; + // react-datepicker import DatePicker from "react-datepicker"; // headless ui import { Popover } from "@headlessui/react"; // contexts import { useProjectMyMembership } from "contexts/project-member.context"; +// services +import inboxServices from "services/inbox.service"; // hooks import useInboxView from "hooks/use-inbox-view"; import useUserAuth from "hooks/use-user-auth"; +import useToast from "hooks/use-toast"; // components -import { FiltersDropdown } from "components/inbox"; +import { + DeclineIssueModal, + DeleteIssueModal, + FiltersDropdown, + SelectDuplicateInboxIssueModal, +} from "components/inbox"; // ui import { PrimaryButton, SecondaryButton } from "components/ui"; // icons @@ -26,47 +36,84 @@ import { TrashIcon, } from "@heroicons/react/24/outline"; // types -import type { IInboxIssue } from "types"; - -type Props = { - issueCount: number; - currentIssueIndex: number; - issue?: IInboxIssue; - onAccept: () => Promise; - onDecline: () => void; - onMarkAsDuplicate: () => void; - onSnooze: (date: Date | string) => void; - onDelete: () => void; -}; - -export const InboxActionHeader: React.FC = (props) => { - const { - issueCount, - currentIssueIndex, - onAccept, - onDecline, - onMarkAsDuplicate, - onSnooze, - onDelete, - issue, - } = props; +import type { IInboxIssueDetail, TInboxStatus } from "types"; +// fetch-keys +import { INBOX_ISSUE_DETAILS } from "constants/fetch-keys"; +export const InboxActionHeader = () => { const [isAccepting, setIsAccepting] = useState(false); const [date, setDate] = useState(new Date()); + const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); + const [declineIssueModal, setDeclineIssueModal] = useState(false); + const [deleteIssueModal, setDeleteIssueModal] = useState(false); const router = useRouter(); - const { inboxIssueId } = router.query; + const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query; - const { memberRole } = useProjectMyMembership(); - const { filters, setFilters, filtersLength } = useInboxView(); const { user } = useUserAuth(); + const { memberRole } = useProjectMyMembership(); + const { issues: inboxIssues, mutate: mutateInboxIssues } = useInboxView(); + const { setToastAlert } = useToast(); + + const markInboxStatus = async (data: TInboxStatus) => { + if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId) return; + + mutate( + INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string), + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + issue_inbox: [{ ...prevData.issue_inbox[0], ...data }], + }; + }, + false + ); + mutateInboxIssues( + (prevData) => + (prevData ?? []).map((i) => + i.bridge_id === inboxIssueId + ? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] } + : i + ), + false + ); + + await inboxServices + .markInboxStatus( + workspaceSlug.toString(), + projectId.toString(), + inboxId.toString(), + inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.bridge_id!, + data, + user + ) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong while updating inbox status. Please try again.", + }) + ) + .finally(() => { + mutate(INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string)); + mutateInboxIssues(); + }); + }; const handleAcceptIssue = () => { setIsAccepting(true); - onAccept().finally(() => setIsAccepting(false)); + markInboxStatus({ + status: 1, + }).finally(() => setIsAccepting(false)); }; + const issue = inboxIssues?.find((issue) => issue.bridge_id === inboxIssueId); + const currentIssueIndex = + inboxIssues?.findIndex((issue) => issue.bridge_id === inboxIssueId) ?? 0; + useEffect(() => { if (!issue?.issue_inbox[0].snoozed_till) return; @@ -82,163 +129,165 @@ export const InboxActionHeader: React.FC = (props) => { tomorrow.setDate(today.getDate() + 1); return ( -
    -
    -
    - -

    Inbox

    -
    -
    - { - const key = option.key as keyof typeof filters; - - const valueExists = (filters[key] as any[])?.includes(option.value); - - if (valueExists) { - setFilters({ - [option.key]: ((filters[key] ?? []) as any[])?.filter( - (val) => val !== option.value - ), - }); - } else { - setFilters({ - [option.key]: [...((filters[key] ?? []) as any[]), option.value], - }); - } - }} - direction="right" - height="rg" - /> - {filtersLength > 0 && ( -
    - {filtersLength} -
    - )} -
    -
    - {inboxIssueId && ( -
    -
    - - -
    - {currentIssueIndex + 1}/{issueCount} -
    + <> + setSelectDuplicateIssue(false)} + value={ + inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.issue_inbox[0] + .duplicate_to + } + onSubmit={(dupIssueId: string) => { + markInboxStatus({ + status: 2, + duplicate_to: dupIssueId, + }).finally(() => setSelectDuplicateIssue(false)); + }} + /> + setDeclineIssueModal(false)} + data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} + onSubmit={async () => { + await markInboxStatus({ + status: -1, + }).finally(() => setDeclineIssueModal(false)); + }} + /> + setDeleteIssueModal(false)} + data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} + /> +
    +
    +
    + +

    Inbox

    -
    - {isAllowed && ( -
    +
    + {inboxIssueId && ( +
    +
    + + +
    + {currentIssueIndex + 1}/{inboxIssues?.length ?? 0} +
    +
    +
    + {isAllowed && (issueStatus === 0 || issueStatus === -2) && ( +
    + + + + + Snooze + + + + {({ close }) => ( +
    + { + if (!val) return; + setDate(val); + }} + dateFormat="dd-MM-yyyy" + minDate={tomorrow} + inline + /> + { + close(); + markInboxStatus({ + status: 0, + snoozed_till: new Date(date), + }); + }} + > + Snooze + +
    + )} +
    +
    +
    + )} + {isAllowed && issueStatus === -2 && ( +
    + setSelectDuplicateIssue(true)} > - - - Snooze - - - - {({ close }) => ( -
    - { - if (!val) return; - setDate(val); - }} - dateFormat="dd-MM-yyyy" - minDate={tomorrow} - inline - /> - { - close(); - onSnooze(date); - }} - > - Snooze - -
    - )} -
    - -
    - )} - {isAllowed && ( -
    - - - Mark as duplicate - - - - {isAccepting ? "Accepting..." : "Accept"} - - - - Decline - -
    - )} - {(isAllowed || user?.id === issue?.created_by) && ( -
    - - - Delete - -
    - )} + + Mark as duplicate + +
    + )} + {isAllowed && (issueStatus === 0 || issueStatus === -2) && ( +
    + + + {isAccepting ? "Accepting..." : "Accept"} + +
    + )} + {isAllowed && issueStatus === -2 && ( +
    + setDeclineIssueModal(true)} + > + + Decline + +
    + )} + {(isAllowed || user?.id === issue?.created_by) && ( +
    + setDeleteIssueModal(true)} + > + + Delete + +
    + )} +
    -
    - )} -
    + )} +
    + ); }; diff --git a/apps/app/components/inbox/inbox-issue-card.tsx b/apps/app/components/inbox/inbox-issue-card.tsx index 814f221a5..072647ae3 100644 --- a/apps/app/components/inbox/inbox-issue-card.tsx +++ b/apps/app/components/inbox/inbox-issue-card.tsx @@ -4,13 +4,21 @@ import Link from "next/link"; // ui import { Tooltip } from "components/ui"; // icons -import { getPriorityIcon, getStateGroupIcon } from "components/icons"; -import { CalendarDaysIcon, ClockIcon } from "@heroicons/react/24/outline"; +import { getPriorityIcon } from "components/icons"; +import { + CalendarDaysIcon, + CheckCircleIcon, + ClockIcon, + DocumentDuplicateIcon, + ExclamationTriangleIcon, + XCircleIcon, +} from "@heroicons/react/24/outline"; // helpers import { renderShortNumericDateFormat } from "helpers/date-time.helper"; -import { addSpaceIfCamelCase } from "helpers/string.helper"; // types import type { IInboxIssue } from "types"; +// constants +import { INBOX_STATUS } from "constants/inbox"; type Props = { issue: IInboxIssue; @@ -30,93 +38,88 @@ export const InboxIssueCard: React.FC = (props) => { href={`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${issue.bridge_id}`} > - -
    -
    -

    - {issue.project_detail?.identifier}-{issue.sequence_id} -

    -
    {issue.name}
    -
    -
    - -
    - {getStateGroupIcon( - issue.state_detail?.group ?? "backlog", - "14", - "14", - issue.state_detail?.color - )} - {issue.state_detail?.name ?? "Triage"} -
    -
    - -
    - {getPriorityIcon( - issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", - "text-sm" - )} -
    -
    - -
    - - {renderShortNumericDateFormat(issue.created_at ?? "")} -
    -
    - {issue.issue_inbox[0].snoozed_till && ( -
    - - - Snoozed till {renderShortNumericDateFormat(issue.issue_inbox[0].snoozed_till)} - -
    - )} -
    +
    +

    + {issue.project_detail?.identifier}-{issue.sequence_id} +

    +
    {issue.name}
    - +
    + +
    + {getPriorityIcon( + issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", + "text-sm" + )} +
    +
    + +
    + + {renderShortNumericDateFormat(issue.created_at ?? "")} +
    +
    +
    +
    s.value === issueStatus)?.textColor + }`} + > + {issueStatus === -2 ? ( + <> + + Pending + + ) : issueStatus === -1 ? ( + <> + + Declined + + ) : issueStatus === 0 ? ( + <> + + + {new Date(issue.issue_inbox[0].snoozed_till ?? "") < new Date() + ? "Snoozed date passed" + : "Snoozed"} + + + ) : issueStatus === 1 ? ( + <> + + Accepted + + ) : ( + <> + + Duplicate + + )} +
    +
    ); diff --git a/apps/app/components/inbox/inbox-main-content.tsx b/apps/app/components/inbox/inbox-main-content.tsx index 83948495c..d03368944 100644 --- a/apps/app/components/inbox/inbox-main-content.tsx +++ b/apps/app/components/inbox/inbox-main-content.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect } from "react"; -import { useRouter } from "next/router"; +import Router, { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; @@ -29,6 +29,7 @@ import { ClockIcon, DocumentDuplicateIcon, ExclamationTriangleIcon, + InboxIcon, XCircleIcon, } from "@heroicons/react/24/outline"; // helpers @@ -37,6 +38,8 @@ import { renderShortNumericDateFormat } from "helpers/date-time.helper"; import type { IInboxIssue, IIssue } from "types"; // fetch-keys import { INBOX_ISSUES, INBOX_ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; +// constants +import { INBOX_STATUS } from "constants/inbox"; const defaultValues = { name: "", @@ -55,7 +58,7 @@ export const InboxMainContent: React.FC = () => { const { user } = useUserAuth(); const { memberRole } = useProjectMyMembership(); - const { params } = useInboxView(); + const { params, issues: inboxIssues } = useInboxView(); const { reset, control, watch } = useForm({ defaultValues, @@ -76,17 +79,6 @@ export const InboxMainContent: React.FC = () => { : null ); - useEffect(() => { - if (!issueDetails || !inboxIssueId) return; - - reset({ - ...issueDetails, - assignees_list: - issueDetails.assignees_list ?? (issueDetails.assignee_details ?? []).map((user) => user.id), - labels_list: issueDetails.labels_list ?? issueDetails.labels, - }); - }, [issueDetails, reset, inboxIssueId]); - const submitChanges = useCallback( async (formData: Partial) => { if (!workspaceSlug || !projectId || !inboxIssueId || !inboxId || !issueDetails) return; @@ -144,7 +136,86 @@ export const InboxMainContent: React.FC = () => { ] ); + const onKeyDown = useCallback( + (e: KeyboardEvent) => { + if (!inboxIssues || !inboxIssueId) return; + + const currentIssueIndex = inboxIssues.findIndex((issue) => issue.bridge_id === inboxIssueId); + + switch (e.key) { + case "ArrowUp": + Router.push({ + pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, + query: { + inboxIssueId: + currentIssueIndex === 0 + ? inboxIssues[inboxIssues.length - 1].bridge_id + : inboxIssues[currentIssueIndex - 1].bridge_id, + }, + }); + break; + case "ArrowDown": + Router.push({ + pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, + query: { + inboxIssueId: + currentIssueIndex === inboxIssues.length - 1 + ? inboxIssues[0].bridge_id + : inboxIssues[currentIssueIndex + 1].bridge_id, + }, + }); + break; + default: + break; + } + }, + [workspaceSlug, projectId, inboxIssueId, inboxId, inboxIssues] + ); + + useEffect(() => { + document.addEventListener("keydown", onKeyDown); + + return () => { + document.removeEventListener("keydown", onKeyDown); + }; + }, [onKeyDown]); + + useEffect(() => { + if (!issueDetails || !inboxIssueId) return; + + reset({ + ...issueDetails, + assignees_list: + issueDetails.assignees_list ?? (issueDetails.assignee_details ?? []).map((user) => user.id), + labels_list: issueDetails.labels_list ?? issueDetails.labels, + }); + }, [issueDetails, reset, inboxIssueId]); + const issueStatus = issueDetails?.issue_inbox[0].status; + const inboxStatusDetails = INBOX_STATUS.find((s) => s.value === issueStatus); + + if (!inboxIssueId) + return ( +
    +
    +
    + + {inboxIssues && inboxIssues.length > 0 ? ( + + {inboxIssues?.length} issues found. Select an issue from the sidebar to view its + details. + + ) : ( + + No issues found. Use{" "} +
    C
    shortcut to + create a new issue +
    + )} +
    +
    +
    + ); return ( <> @@ -153,19 +224,10 @@ export const InboxMainContent: React.FC = () => {
    {issueStatus === -2 ? ( @@ -266,6 +328,4 @@ export const InboxMainContent: React.FC = () => { )} ); - - return null; }; diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 5db688283..cc5cae3f1 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -163,6 +163,8 @@ export const IssueForm: FC = ({ const handleCreateUpdateIssue = async (formData: Partial) => { await handleFormSubmit(formData); + setGptAssistantModal(false); + reset({ ...defaultValues, project: projectId, diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index 5142c6a32..22a275fee 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -188,6 +188,10 @@ export const CreateUpdateIssueModal: React.FC = ({ message: "Issue created successfully.", }); + router.push( + `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}` + ); + mutate(INBOX_ISSUES(inboxId.toString(), inboxParams)); }) .catch(() => { diff --git a/apps/app/constants/inbox.ts b/apps/app/constants/inbox.ts index 9bbe8230a..3d7e8a054 100644 --- a/apps/app/constants/inbox.ts +++ b/apps/app/constants/inbox.ts @@ -3,26 +3,41 @@ export const INBOX_STATUS = [ key: "pending", label: "Pending", value: -2, + textColor: "text-yellow-500", + bgColor: "bg-yellow-500/10", + borderColor: "border-yellow-500", }, { key: "declined", label: "Declined", value: -1, + textColor: "text-red-500", + bgColor: "bg-red-500/10", + borderColor: "border-red-500", }, { key: "snoozed", label: "Snoozed", value: 0, + textColor: "text-brand-secondary", + bgColor: "bg-gray-500/10", + borderColor: "border-gray-500", }, { key: "accepted", label: "Accepted", value: 1, + textColor: "text-green-500", + bgColor: "bg-green-500/10", + borderColor: "border-green-500", }, { key: "duplicate", label: "Duplicate", value: 2, + textColor: "text-brand-secondary", + bgColor: "bg-gray-500/10", + borderColor: "border-gray-500", }, ]; diff --git a/apps/app/pages/[workspaceSlug]/index.tsx b/apps/app/pages/[workspaceSlug]/index.tsx index 233fd9b8a..3c75890f6 100644 --- a/apps/app/pages/[workspaceSlug]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/index.tsx @@ -49,7 +49,7 @@ const WorkspacePage: NextPage = () => { )}
    -
    +

    Plane is open source, support us by starring us on GitHub.

    diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx index 140e704ed..dc301e0d8 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/inbox/[inboxId].tsx @@ -1,29 +1,13 @@ -import { useState, useEffect, useCallback } from "react"; +import { useRouter } from "next/router"; -import Router, { useRouter } from "next/router"; - -import useSWR, { mutate } from "swr"; - -// services -import inboxServices from "services/inbox.service"; -import projectService from "services/project.service"; // hooks -import useInboxView from "hooks/use-inbox-view"; -import useUserAuth from "hooks/use-user-auth"; -import useToast from "hooks/use-toast"; +import useProjectDetails from "hooks/use-project-details"; // layouts import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; // contexts import { InboxViewContextProvider } from "contexts/inbox-view-context"; // components -import { - InboxActionHeader, - InboxMainContent, - SelectDuplicateInboxIssueModal, - DeclineIssueModal, - DeleteIssueModal, - IssuesListSidebar, -} from "components/inbox"; +import { InboxActionHeader, InboxMainContent, IssuesListSidebar } from "components/inbox"; // helper import { truncateText } from "helpers/string.helper"; // ui @@ -31,123 +15,14 @@ import { PrimaryButton } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; // icons import { PlusIcon } from "@heroicons/react/24/outline"; -import { InboxIcon } from "components/icons"; // types -import { IInboxIssueDetail, TInboxStatus } from "types"; import type { NextPage } from "next"; -// fetch-keys -import { INBOX_ISSUE_DETAILS, PROJECT_DETAILS } from "constants/fetch-keys"; const ProjectInbox: NextPage = () => { - const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); - const [declineIssueModal, setDeclineIssueModal] = useState(false); - const [deleteIssueModal, setDeleteIssueModal] = useState(false); - const router = useRouter(); - const { workspaceSlug, projectId, inboxId, inboxIssueId } = router.query; + const { workspaceSlug } = router.query; - const { user } = useUserAuth(); - const { issues: inboxIssues, mutate: mutateInboxIssues } = useInboxView(); - const { setToastAlert } = useToast(); - - const { data: projectDetails } = useSWR( - workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, - workspaceSlug && projectId - ? () => projectService.getProject(workspaceSlug as string, projectId as string) - : null - ); - - const onKeyDown = useCallback( - (e: KeyboardEvent) => { - if (!inboxIssues || !inboxIssueId) return; - - const currentIssueIndex = inboxIssues.findIndex((issue) => issue.bridge_id === inboxIssueId); - - switch (e.key) { - case "ArrowUp": - Router.push({ - pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, - query: { - inboxIssueId: - currentIssueIndex === 0 - ? inboxIssues[inboxIssues.length - 1].bridge_id - : inboxIssues[currentIssueIndex - 1].bridge_id, - }, - }); - break; - case "ArrowDown": - Router.push({ - pathname: `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}`, - query: { - inboxIssueId: - currentIssueIndex === inboxIssues.length - 1 - ? inboxIssues[0].bridge_id - : inboxIssues[currentIssueIndex + 1].bridge_id, - }, - }); - - break; - default: - break; - } - }, - [workspaceSlug, projectId, inboxIssueId, inboxId, inboxIssues] - ); - - useEffect(() => { - document.addEventListener("keydown", onKeyDown); - - return () => { - document.removeEventListener("keydown", onKeyDown); - }; - }, [onKeyDown]); - - const markInboxStatus = async (data: TInboxStatus) => { - if (!workspaceSlug || !projectId || !inboxId || !inboxIssueId) return; - - mutate( - INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string), - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - issue_inbox: [{ ...prevData.issue_inbox[0], ...data }], - }; - }, - false - ); - mutateInboxIssues( - (prevData) => - (prevData ?? []).map((i) => - i.bridge_id === inboxIssueId - ? { ...i, issue_inbox: [{ ...i.issue_inbox[0], ...data }] } - : i - ), - false - ); - - await inboxServices - .markInboxStatus( - workspaceSlug.toString(), - projectId.toString(), - inboxId.toString(), - inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId)?.bridge_id!, - data, - user - ) - .catch(() => - setToastAlert({ - type: "error", - title: "Error!", - message: "Something went wrong while updating inbox status. Please try again.", - }) - ) - .finally(() => { - mutate(INBOX_ISSUE_DETAILS(inboxId as string, inboxIssueId as string)); - mutateInboxIssues(); - }); - }; + const { projectDetails } = useProjectDetails(); return ( @@ -175,88 +50,15 @@ const ProjectInbox: NextPage = () => {
    } > - <> - setSelectDuplicateIssue(false)} - value={ - inboxIssues?.find((inboxIssue) => inboxIssue.bridge_id === inboxIssueId) - ?.issue_inbox[0].duplicate_to - } - onSubmit={(dupIssueId: string) => { - markInboxStatus({ - status: 2, - duplicate_to: dupIssueId, - }).finally(() => setSelectDuplicateIssue(false)); - }} - /> - setDeclineIssueModal(false)} - data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} - onSubmit={async () => { - await markInboxStatus({ - status: -1, - }).finally(() => setDeclineIssueModal(false)); - }} - /> - setDeleteIssueModal(false)} - data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} - /> -
    - issue.bridge_id === inboxIssueId)} - currentIssueIndex={ - inboxIssues?.findIndex((issue) => issue.bridge_id === inboxIssueId) ?? 0 - } - issueCount={inboxIssues?.length ?? 0} - onAccept={() => - markInboxStatus({ - status: 1, - }) - } - onDecline={() => setDeclineIssueModal(true)} - onMarkAsDuplicate={() => setSelectDuplicateIssue(true)} - onSnooze={(date) => { - markInboxStatus({ - status: 0, - snoozed_till: new Date(date), - }); - }} - onDelete={() => setDeleteIssueModal(true)} - /> -
    - -
    - {inboxIssueId ? ( - - ) : ( -
    -
    -
    - - {inboxIssues && inboxIssues.length > 0 ? ( - - {inboxIssues?.length} issues found. Select an issue from the sidebar to - view its details. - - ) : ( - - No issues found. Use{" "} -
    C
    {" "} - shortcut to create a new issue -
    - )} -
    -
    -
    - )} -
    +
    + +
    + +
    +
    - +
    ); diff --git a/apps/app/services/inbox.service.ts b/apps/app/services/inbox.service.ts index 9ee20923c..61949c877 100644 --- a/apps/app/services/inbox.service.ts +++ b/apps/app/services/inbox.service.ts @@ -162,7 +162,7 @@ class InboxServices extends APIService { inboxId: string, data: any, user: ICurrentUserResponse | undefined - ): Promise { + ): Promise { return this.post( `/api/workspaces/${workspaceSlug}/projects/${projectId}/inboxes/${inboxId}/inbox-issues/`, data From 37303e6cb86a71e5ac7b94ed28ae7268f6967b29 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:30:52 +0530 Subject: [PATCH 10/57] refactor: inbox issues (#1370) * refactor: inbox issue endpoints * dev: update inbox issues endpooint --- apiserver/plane/api/views/inbox.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index 0e4c1603e..f794db058 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -68,13 +68,12 @@ class InboxViewSet(BaseViewSet): inbox = Inbox.objects.get( workspace__slug=slug, project_id=project_id, pk=pk ) - + # Handle default inbox delete if inbox.is_default: return Response( {"error": "You cannot delete the default inbox"}, status=status.HTTP_400_BAD_REQUEST, ) - inbox.delete() return Response(status=status.HTTP_204_NO_CONTENT) except Exception as e: @@ -120,23 +119,17 @@ class InboxIssueViewSet(BaseViewSet): workspace__slug=slug, project_id=project_id, ) + .filter(**filters) + .order_by(order_by) + .annotate(bridge_id=F("issue_inbox__id")) + .select_related("workspace", "project", "state", "parent") + .prefetch_related("assignees", "labels") .annotate( sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") ) - .annotate(bridge_id=F("issue_inbox__id")) - .filter(project_id=project_id) - .filter(workspace__slug=slug) - .select_related("project") - .select_related("workspace") - .select_related("state") - .select_related("parent") - .prefetch_related("assignees") - .prefetch_related("labels") - .order_by(order_by) - .filter(**filters) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) .order_by() @@ -180,7 +173,8 @@ class InboxIssueViewSet(BaseViewSet): {"error": "Name is required"}, status=status.HTTP_400_BAD_REQUEST ) - if not request.data.get("issue", {}).get("priority", "low") in [ + # Check for valid priority + if not request.data.get("issue", {}).get("priority", None) in [ "low", "medium", "high", @@ -213,7 +207,6 @@ class InboxIssueViewSet(BaseViewSet): ) # Create an Issue Activity - # Track the issue issue_activity.delay( type="issue.activity.created", requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), @@ -231,9 +224,7 @@ class InboxIssueViewSet(BaseViewSet): ) serializer = IssueStateInboxSerializer(issue) - return Response(serializer.data, status=status.HTTP_200_OK) - except Exception as e: capture_exception(e) return Response( From 0cb856b92fef7f5a5db68008b2a53c432e33b95f Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 23 Jun 2023 13:33:01 +0530 Subject: [PATCH 11/57] chore: inbox issue ordering (#1367) --- apiserver/plane/api/views/inbox.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index f794db058..24fff54ae 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -111,7 +111,6 @@ class InboxIssueViewSet(BaseViewSet): def list(self, request, slug, project_id, inbox_id): try: - order_by = request.GET.get("order_by", "created_at") filters = issue_filters(request.query_params, "GET") issues = ( Issue.objects.filter( @@ -120,10 +119,10 @@ class InboxIssueViewSet(BaseViewSet): project_id=project_id, ) .filter(**filters) - .order_by(order_by) .annotate(bridge_id=F("issue_inbox__id")) .select_related("workspace", "project", "state", "parent") .prefetch_related("assignees", "labels") + .order_by("issue_inbox__snoozed_till", "issue_inbox__status") .annotate( sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) .order_by() From e08fc59114f9b634ca59a588cb653794cd969208 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:20:05 +0530 Subject: [PATCH 12/57] feat: spreadsheet view (#1369) * feat: spreadsheet view * fix: fix scroll and overflow issues, feat: updated issue properties component, style: ui improvements * feat: sub-issue toggle and sub-issue hook added, chore: code refactor * fix: only render parent issue * feat: sub issue fetching hook updated and nested sub issue added, chore: code refactor * style: title sticky to left on scroll and column styling * fix: tooltip , filter and view z-index fix * feat: spreadsheet view column sorting, fix: sticky scroll issue fix * feat: updated issue view filter for spreadsheet view * style: spreadsheet view column * feat: double click to edit title * fix: estimate sorting fix * style: spreadsheet view columns * fix: spreadsheet view mutation, feat: edit , copy and delete option added * fix: edit sub issue fix --- apps/app/components/core/index.ts | 1 + .../components/core/issues-view-filter.tsx | 24 +- apps/app/components/core/issues-view.tsx | 16 +- .../components/core/spreadsheet-view/index.ts | 4 + .../core/spreadsheet-view/single-issue.tsx | 266 ++++++++++++++++++ .../spreadsheet-view/spreadsheet-columns.tsx | 241 ++++++++++++++++ .../spreadsheet-view/spreadsheet-issues.tsx | 90 ++++++ .../spreadsheet-view/spreadsheet-view.tsx | 94 +++++++ .../components/issues/delete-issue-modal.tsx | 16 ++ apps/app/components/issues/modal.tsx | 12 + .../issues/view-select/assignee.tsx | 66 +++-- .../issues/view-select/due-date.tsx | 3 + .../issues/view-select/estimate.tsx | 20 +- .../issues/view-select/priority.tsx | 21 +- .../components/issues/view-select/state.tsx | 28 +- apps/app/components/ui/custom-menu.tsx | 8 +- apps/app/components/ui/datepicker.tsx | 6 +- .../components/ui/multi-level-dropdown.tsx | 2 +- apps/app/components/ui/tooltip.tsx | 2 +- apps/app/constants/fetch-keys.ts | 5 +- apps/app/constants/spreadsheet.ts | 60 ++++ .../app/hooks/use-spreadsheet-issues-view.tsx | 125 ++++++++ apps/app/hooks/use-sub-issue.tsx | 34 +++ apps/app/types/issues.d.ts | 18 +- 24 files changed, 1093 insertions(+), 69 deletions(-) create mode 100644 apps/app/components/core/spreadsheet-view/index.ts create mode 100644 apps/app/components/core/spreadsheet-view/single-issue.tsx create mode 100644 apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx create mode 100644 apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx create mode 100644 apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx create mode 100644 apps/app/constants/spreadsheet.ts create mode 100644 apps/app/hooks/use-spreadsheet-issues-view.tsx create mode 100644 apps/app/hooks/use-sub-issue.tsx diff --git a/apps/app/components/core/index.ts b/apps/app/components/core/index.ts index e3e187d60..c50ce7251 100644 --- a/apps/app/components/core/index.ts +++ b/apps/app/components/core/index.ts @@ -2,6 +2,7 @@ export * from "./board-view"; export * from "./calendar-view"; export * from "./gantt-chart-view"; export * from "./list-view"; +export * from "./spreadsheet-view"; export * from "./sidebar"; export * from "./bulk-delete-issues-modal"; export * from "./existing-issues-list-modal"; diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 6856e8f8b..679f6adc3 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -10,7 +10,7 @@ import { Popover, Transition } from "@headlessui/react"; // components import { SelectFilters } from "components/views"; // ui -import { CustomMenu, ToggleSwitch } from "components/ui"; +import { CustomMenu, Icon, ToggleSwitch } from "components/ui"; // icons import { ChevronDownIcon, @@ -83,6 +83,15 @@ export const IssuesFilterView: React.FC = () => { > + + )} +
    + + + + {issue.name} + + +
    + {properties.state && ( +
    + +
    + )} + {properties.priority && ( +
    + +
    + )} + {properties.assignee && ( +
    + +
    + )} + {properties.labels ? ( + issue.label_details.length > 0 ? ( +
    + {issue.label_details.slice(0, 4).map((label, index) => ( +
    + +
    + ))} + {issue.label_details.length > 4 ? +{issue.label_details.length - 4} : null} +
    + ) : ( +
    + No Labels +
    + ) + ) : ( + "" + )} + {properties.due_date && ( +
    + +
    + )} + {properties.estimate && ( +
    + +
    + )} +
    + {!isNotAllowed && ( + + +
    + + Edit issue +
    +
    + handleDeleteIssue(issue)}> +
    + + Delete issue +
    +
    + +
    + + Copy issue link +
    +
    +
    + )} +
    +
    + ); +}; diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx new file mode 100644 index 000000000..9f615f165 --- /dev/null +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx @@ -0,0 +1,241 @@ +import React from "react"; +// hooks +import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; +import useLocalStorage from "hooks/use-local-storage"; +// component +import { CustomMenu, Icon } from "components/ui"; +// icon +import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; +// types +import { TIssueOrderByOptions } from "types"; + +type Props = { + columnData: any; + gridTemplateColumns: string; +}; + +export const SpreadsheetColumns: React.FC = ({ columnData, gridTemplateColumns }) => { + const { storedValue: selectedMenuItem, setValue: setSelectedMenuItem } = useLocalStorage( + "spreadsheetViewSorting", + "" + ); + + const { orderBy, setOrderBy } = useSpreadsheetIssuesView(); + + const handleOrderBy = (order: TIssueOrderByOptions, itemKey: string) => { + setOrderBy(order); + setSelectedMenuItem(`${order}_${itemKey}`); + }; + return ( +
    + {columnData.map((col: any) => { + if (col.isActive) { + return ( +
    + {col.propertyName === "title" || col.propertyName === "priority" ? ( +
    + {col.icon ? ( +
    + ) : ( + + {col.icon ? ( +
    + } + menuItemsWhiteBg + width="xl" + > + { + handleOrderBy(col.ascendingOrder, col.propertyName); + }} + > +
    +
    + {col.propertyName === "assignee" || col.propertyName === "labels" ? ( + <> + A-Z + Ascending + + ) : col.propertyName === "due_date" ? ( + <> + 1-9 + Ascending + + ) : col.propertyName === "estimate" ? ( + <> + 0 + + 10 + + ) : ( + <> + First + + Last + + )} +
    + + +
    +
    + { + handleOrderBy(col.descendingOrder, col.propertyName); + }} + > +
    +
    + {col.propertyName === "assignee" || col.propertyName === "labels" ? ( + <> + Z-A + Descending + + ) : col.propertyName === "due_date" ? ( + <> + 9-1 + Descending + + ) : col.propertyName === "estimate" ? ( + <> + 10 + + 0 + + ) : ( + <> + Last + + First + + )} +
    + + +
    +
    + { + handleOrderBy("-created_at", col.propertyName); + }} + > +
    +
    + + None +
    + + +
    +
    + + )} +
    + ); + } + })} +
    + ); +}; diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx new file mode 100644 index 000000000..8652b3a7e --- /dev/null +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx @@ -0,0 +1,90 @@ +import React, { useState } from "react"; + +// components +import { SingleSpreadsheetIssue } from "components/core"; +// hooks +import useSubIssue from "hooks/use-sub-issue"; +// types +import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types"; + +type Props = { + key: string; + issue: IIssue; + expandedIssues: string[]; + setExpandedIssues: React.Dispatch>; + properties: Properties; + handleEditIssue: (issue: IIssue) => void; + handleDeleteIssue: (issue: IIssue) => void; + gridTemplateColumns: string; + user: ICurrentUserResponse | undefined; + userAuth: UserAuth; + nestingLevel?: number; +}; + +export const SpreadsheetIssues: React.FC = ({ + key, + issue, + expandedIssues, + setExpandedIssues, + gridTemplateColumns, + properties, + handleEditIssue, + handleDeleteIssue, + user, + userAuth, + nestingLevel = 0, +}) => { + const handleToggleExpand = (issueId: string) => { + setExpandedIssues((prevState) => { + const newArray = [...prevState]; + const index = newArray.indexOf(issueId); + if (index > -1) { + newArray.splice(index, 1); + } else { + newArray.push(issueId); + } + return newArray; + }); + }; + + const isExpanded = expandedIssues.indexOf(issue.id) > -1; + + const { subIssues, isLoading } = useSubIssue(issue.id, isExpanded); + + return ( +
    + handleEditIssue(issue)} + handleDeleteIssue={handleDeleteIssue} + user={user} + userAuth={userAuth} + nestingLevel={nestingLevel} + /> + + {isExpanded && + !isLoading && + subIssues && + subIssues.length > 0 && + subIssues.map((subIssue: IIssue, subIndex: number) => ( + handleEditIssue(subIssue)} + handleDeleteIssue={handleDeleteIssue} + user={user} + userAuth={userAuth} + nestingLevel={nestingLevel + 1} + /> + ))} +
    + ); +}; diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx new file mode 100644 index 000000000..6f36b2dbb --- /dev/null +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx @@ -0,0 +1,94 @@ +import React, { useState } from "react"; + +// next +import { useRouter } from "next/router"; + +// components +import { SpreadsheetColumns, SpreadsheetIssues } from "components/core"; +import { Icon, Spinner } from "components/ui"; +// hooks +import useIssuesProperties from "hooks/use-issue-properties"; +import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; +// types +import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types"; +// constants +import { SPREADSHEET_COLUMN } from "constants/spreadsheet"; +// icon +import { PlusIcon } from "@heroicons/react/24/outline"; + +type Props = { + handleEditIssue: (issue: IIssue) => void; + handleDeleteIssue: (issue: IIssue) => void; + user: ICurrentUserResponse | undefined; + userAuth: UserAuth; +}; + +export const SpreadsheetView: React.FC = ({ + handleEditIssue, + handleDeleteIssue, + user, + userAuth, +}) => { + const [expandedIssues, setExpandedIssues] = useState([]); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { spreadsheetIssues } = useSpreadsheetIssuesView(); + + const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); + + const columnData = SPREADSHEET_COLUMN.map((column) => ({ + ...column, + isActive: properties + ? column.propertyName === "labels" + ? properties[column.propertyName as keyof Properties] + : column.propertyName === "title" + ? true + : properties[column.propertyName as keyof Properties] + : false, + })); + + const gridTemplateColumns = columnData + .filter((column) => column.isActive) + .map((column) => column.colSize) + .join(" "); + + return ( +
    +
    + +
    + {spreadsheetIssues ? ( +
    + {spreadsheetIssues.map((issue: IIssue, index) => ( + + ))} + +
    + ) : ( + + )} +
    + ); +}; diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index d0ef4b6e9..ffdebb314 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -12,6 +12,7 @@ import issueServices from "services/issues.service"; import useIssuesView from "hooks/use-issues-view"; import useCalendarIssuesView from "hooks/use-calendar-issues-view"; import useToast from "hooks/use-toast"; +import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; // icons import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // ui @@ -41,6 +42,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data, u const { issueView, params } = useIssuesView(); const { params: calendarParams } = useCalendarIssuesView(); + const { params: spreadsheetParams } = useSpreadsheetIssuesView(); const { setToastAlert } = useToast(); @@ -74,6 +76,20 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data, u (prevData) => (prevData ?? []).filter((p) => p.id !== data.id), false ); + } else if (issueView === "spreadsheet") { + const spreadsheetFetchKey = cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams) + : moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), spreadsheetParams) + : viewId + ? VIEW_ISSUES(viewId.toString(), spreadsheetParams) + : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", spreadsheetParams); + + mutate( + spreadsheetFetchKey, + (prevData) => (prevData ?? []).filter((p) => p.id !== data.id), + false + ); } else { if (cycleId) mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); else if (moduleId) mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index 22a275fee..d88ad674d 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -17,6 +17,7 @@ import useIssuesView from "hooks/use-issues-view"; import useCalendarIssuesView from "hooks/use-calendar-issues-view"; import useToast from "hooks/use-toast"; import useInboxView from "hooks/use-inbox-view"; +import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; // components import { IssueForm } from "components/issues"; // types @@ -79,6 +80,7 @@ export const CreateUpdateIssueModal: React.FC = ({ const { params: calendarParams } = useCalendarIssuesView(); const { order_by, group_by, ...viewGanttParams } = params; const { params: inboxParams } = useInboxView(); + const { params: spreadsheetParams } = useSpreadsheetIssuesView(); if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string }; if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string }; @@ -211,6 +213,14 @@ export const CreateUpdateIssueModal: React.FC = ({ ? VIEW_ISSUES(viewId.toString(), calendarParams) : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", calendarParams); + const spreadsheetFetchKey = cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams) + : moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), spreadsheetParams) + : viewId + ? VIEW_ISSUES(viewId.toString(), spreadsheetParams) + : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", spreadsheetParams); + const ganttFetchKey = cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString()) : moduleId @@ -234,6 +244,7 @@ export const CreateUpdateIssueModal: React.FC = ({ if (issueView === "calendar") mutate(calendarFetchKey); if (issueView === "gantt_chart") mutate(ganttFetchKey); + if (issueView === "spreadsheet") mutate(spreadsheetFetchKey); setToastAlert({ type: "success", @@ -264,6 +275,7 @@ export const CreateUpdateIssueModal: React.FC = ({ mutate(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false); } else { if (issueView === "calendar") mutate(calendarFetchKey); + if (issueView === "spreadsheet") mutate(spreadsheetFetchKey); mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params)); } diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index 27d4901f6..1dbfbabba 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -22,6 +22,7 @@ type Props = { position?: "left" | "right"; selfPositioned?: boolean; tooltipPosition?: "left" | "right"; + customButton?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; }; @@ -34,6 +35,7 @@ export const ViewAssigneeSelect: React.FC = ({ tooltipPosition = "right", user, isNotAllowed, + customButton = false, }) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -65,6 +67,38 @@ export const ViewAssigneeSelect: React.FC = ({ ), })); + const assigneeLabel = ( + 0 + ? issue.assignee_details + .map((assignee) => + assignee?.first_name !== "" ? assignee?.first_name : assignee?.email + ) + .join(", ") + : "No Assignee" + } + > +
    + {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? ( +
    + +
    + ) : ( +
    + +
    + )} +
    +
    + ); + return ( = ({ ); }} options={options} - label={ - 0 - ? issue.assignee_details - .map((assignee) => - assignee?.first_name !== "" ? assignee?.first_name : assignee?.email - ) - .join(", ") - : "No Assignee" - } - > -
    - {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? ( -
    - -
    - ) : ( -
    - -
    - )} -
    -
    - } + {...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })} multiple noChevron position={position} diff --git a/apps/app/components/issues/view-select/due-date.tsx b/apps/app/components/issues/view-select/due-date.tsx index bea5ff045..f74b62689 100644 --- a/apps/app/components/issues/view-select/due-date.tsx +++ b/apps/app/components/issues/view-select/due-date.tsx @@ -12,6 +12,7 @@ import { ICurrentUserResponse, IIssue } from "types"; type Props = { issue: IIssue; partialUpdateIssue: (formData: Partial, issueId: string) => void; + noBorder?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; }; @@ -19,6 +20,7 @@ type Props = { export const ViewDueDateSelect: React.FC = ({ issue, partialUpdateIssue, + noBorder = false, user, isNotAllowed, }) => { @@ -62,6 +64,7 @@ export const ViewDueDateSelect: React.FC = ({ ); }} className={issue?.target_date ? "w-[6.5rem]" : "w-[5rem] text-center"} + noBorder={noBorder} disabled={isNotAllowed} />
    diff --git a/apps/app/components/issues/view-select/estimate.tsx b/apps/app/components/issues/view-select/estimate.tsx index 914a5286e..02a3e0710 100644 --- a/apps/app/components/issues/view-select/estimate.tsx +++ b/apps/app/components/issues/view-select/estimate.tsx @@ -18,6 +18,7 @@ type Props = { partialUpdateIssue: (formData: Partial, issueId: string) => void; position?: "left" | "right"; selfPositioned?: boolean; + customButton?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; }; @@ -27,6 +28,7 @@ export const ViewEstimateSelect: React.FC = ({ partialUpdateIssue, position = "left", selfPositioned = false, + customButton = false, user, isNotAllowed, }) => { @@ -37,6 +39,15 @@ export const ViewEstimateSelect: React.FC = ({ const estimateValue = estimatePoints?.find((e) => e.key === issue.estimate_point)?.value; + const estimateLabels = ( + +
    + + {estimateValue ?? "None"} +
    +
    + ); + if (!isEstimateActive) return null; return ( @@ -57,14 +68,7 @@ export const ViewEstimateSelect: React.FC = ({ user ); }} - label={ - -
    - - {estimateValue ?? "Estimate"} -
    -
    - } + {...(customButton ? { customButton: estimateLabels } : { label: estimateLabels })} maxHeight="md" noChevron disabled={isNotAllowed} diff --git a/apps/app/components/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index a0c5cd47c..499546931 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -12,12 +12,15 @@ import { ICurrentUserResponse, IIssue } from "types"; import { PRIORITIES } from "constants/project"; // services import trackEventServices from "services/track-event.service"; +// helper +import { capitalizeFirstLetter } from "helpers/string.helper"; type Props = { issue: IIssue; partialUpdateIssue: (formData: Partial, issueId: string) => void; position?: "left" | "right"; selfPositioned?: boolean; + noBorder?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; }; @@ -27,6 +30,7 @@ export const ViewPrioritySelect: React.FC = ({ partialUpdateIssue, position = "left", selfPositioned = false, + noBorder = false, user, isNotAllowed, }) => { @@ -55,10 +59,12 @@ export const ViewPrioritySelect: React.FC = ({ customButton={ diff --git a/apps/app/components/issues/view-select/state.tsx b/apps/app/components/issues/view-select/state.tsx index 2b904eb1e..c097c7326 100644 --- a/apps/app/components/issues/view-select/state.tsx +++ b/apps/app/components/issues/view-select/state.tsx @@ -22,6 +22,7 @@ type Props = { partialUpdateIssue: (formData: Partial, issueId: string) => void; position?: "left" | "right"; selfPositioned?: boolean; + customButton?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; }; @@ -31,6 +32,7 @@ export const ViewStateSelect: React.FC = ({ partialUpdateIssue, position = "left", selfPositioned = false, + customButton = false, user, isNotAllowed, }) => { @@ -58,6 +60,19 @@ export const ViewStateSelect: React.FC = ({ const selectedOption = states?.find((s) => s.id === issue.state); + const stateLabel = ( + +
    + {selectedOption && + getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)} + {selectedOption?.name ?? "State"} +
    +
    + ); + return ( = ({ } }} options={options} - label={ - -
    - {selectedOption && - getStateGroupIcon(selectedOption.group, "16", "16", selectedOption.color)} - {selectedOption?.name ?? "State"} -
    -
    - } + {...(customButton ? { customButton: stateLabel } : { label: stateLabel })} position={position} disabled={isNotAllowed} noChevron diff --git a/apps/app/components/ui/custom-menu.tsx b/apps/app/components/ui/custom-menu.tsx index dac7927b1..006802b79 100644 --- a/apps/app/components/ui/custom-menu.tsx +++ b/apps/app/components/ui/custom-menu.tsx @@ -20,6 +20,7 @@ type Props = { position?: "left" | "right"; verticalPosition?: "top" | "bottom"; customButton?: JSX.Element; + menuItemsWhiteBg?: boolean; }; type MenuItemProps = { @@ -44,6 +45,7 @@ const CustomMenu = ({ position = "right", verticalPosition = "bottom", customButton, + menuItemsWhiteBg = false, }: Props) => ( {({ open }) => ( @@ -105,7 +107,7 @@ const CustomMenu = ({ leaveTo="transform opacity-0 scale-95" >
    {children}
    diff --git a/apps/app/components/ui/datepicker.tsx b/apps/app/components/ui/datepicker.tsx index 999b46ce4..80ce7aa91 100644 --- a/apps/app/components/ui/datepicker.tsx +++ b/apps/app/components/ui/datepicker.tsx @@ -11,6 +11,7 @@ type Props = { placeholder?: string; displayShortForm?: boolean; error?: boolean; + noBorder?: boolean; className?: string; isClearable?: boolean; disabled?: boolean; @@ -23,6 +24,7 @@ export const CustomDatePicker: React.FC = ({ placeholder = "Select date", displayShortForm = false, error = false, + noBorder = false, className = "", isClearable = true, disabled = false, @@ -44,7 +46,9 @@ export const CustomDatePicker: React.FC = ({ : "" } ${error ? "border-red-500 bg-red-100" : ""} ${ disabled ? "cursor-not-allowed" : "cursor-pointer" - } w-full rounded-md border border-brand-base bg-transparent caret-transparent ${className}`} + } ${ + noBorder ? "" : "border border-brand-base" + } w-full rounded-md bg-transparent caret-transparent ${className}`} dateFormat="dd-MM-yyyy" isClearable={isClearable} disabled={disabled} diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index 0033e8e02..0f25d06b3 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -35,7 +35,7 @@ export const MultiLevelDropdown: React.FC = ({ const [openChildFor, setOpenChildFor] = useState(null); return ( - + {({ open }) => ( <>
    diff --git a/apps/app/components/ui/tooltip.tsx b/apps/app/components/ui/tooltip.tsx index 11504facd..86ca39e54 100644 --- a/apps/app/components/ui/tooltip.tsx +++ b/apps/app/components/ui/tooltip.tsx @@ -42,7 +42,7 @@ export const Tooltip: React.FC = ({ disabled={disabled} content={
    diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index 5625b710d..7e77e6dc2 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -1,7 +1,7 @@ import { IAnalyticsParams, IJiraMetadata } from "types"; const paramsToKey = (params: any) => { - const { state, priority, assignees, created_by, labels, target_date } = params; + const { state, priority, assignees, created_by, labels, target_date, sub_issue } = params; let stateKey = state ? state.split(",") : []; let priorityKey = priority ? priority.split(",") : []; @@ -12,6 +12,7 @@ const paramsToKey = (params: any) => { const type = params.type ? params.type.toUpperCase() : "NULL"; const groupBy = params.group_by ? params.group_by.toUpperCase() : "NULL"; const orderBy = params.order_by ? params.order_by.toUpperCase() : "NULL"; + const subIssue = sub_issue ? sub_issue.toUpperCase() : "NULL"; // sorting each keys in ascending order stateKey = stateKey.sort().join("_"); @@ -20,7 +21,7 @@ const paramsToKey = (params: any) => { createdByKey = createdByKey.sort().join("_"); labelsKey = labelsKey.sort().join("_"); - return `${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${targetDateKey}`; + return `${stateKey}_${priorityKey}_${assigneesKey}_${createdByKey}_${type}_${groupBy}_${orderBy}_${labelsKey}_${targetDateKey}_${subIssue}`; }; const inboxParamsToKey = (params: any) => { diff --git a/apps/app/constants/spreadsheet.ts b/apps/app/constants/spreadsheet.ts new file mode 100644 index 000000000..5ef60f40d --- /dev/null +++ b/apps/app/constants/spreadsheet.ts @@ -0,0 +1,60 @@ +import { + CalendarDaysIcon, + PlayIcon, + Squares2X2Icon, + TagIcon, + UserGroupIcon, +} from "@heroicons/react/24/outline"; + +export const SPREADSHEET_COLUMN = [ + { + propertyName: "title", + colName: "Title", + colSize: "440px", + }, + { + propertyName: "state", + colName: "State", + colSize: "128px", + icon: Squares2X2Icon, + ascendingOrder: "state__name", + descendingOrder: "-state__name", + }, + { + propertyName: "priority", + colName: "Priority", + colSize: "128px", + }, + { + propertyName: "assignee", + colName: "Assignees", + colSize: "128px", + icon: UserGroupIcon, + ascendingOrder: "assignees__name", + descendingOrder: "-assignees__name", + }, + { + propertyName: "labels", + colName: "Labels", + colSize: "128px", + icon: TagIcon, + ascendingOrder: "labels__name", + descendingOrder: "-labels__name", + }, + { + propertyName: "due_date", + colName: "Due Date", + colSize: "128px", + icon: CalendarDaysIcon, + ascendingOrder: "target_date", + descendingOrder: "-target_date", + }, + { + propertyName: "estimate", + colName: "Estimate", + colSize: "128px", + icon: PlayIcon, + ascendingOrder: "estimate_point", + descendingOrder: "-estimate_point", + }, +]; diff --git a/apps/app/hooks/use-spreadsheet-issues-view.tsx b/apps/app/hooks/use-spreadsheet-issues-view.tsx new file mode 100644 index 000000000..6e7b66bec --- /dev/null +++ b/apps/app/hooks/use-spreadsheet-issues-view.tsx @@ -0,0 +1,125 @@ +import { useContext } from "react"; + +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// contexts +import { issueViewContext } from "contexts/issue-view.context"; +// services +import issuesService from "services/issues.service"; +import cyclesService from "services/cycles.service"; +import modulesService from "services/modules.service"; +// types +import { IIssue } from "types"; +// fetch-keys +import { + CYCLE_ISSUES_WITH_PARAMS, + MODULE_ISSUES_WITH_PARAMS, + PROJECT_ISSUES_LIST_WITH_PARAMS, + VIEW_ISSUES, +} from "constants/fetch-keys"; + +const useSpreadsheetIssuesView = () => { + const { + issueView, + orderBy, + setOrderBy, + filters, + setFilters, + resetFilterToDefault, + setNewFilterDefaultView, + setIssueView, + } = useContext(issueViewContext); + + const router = useRouter(); + const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; + + const params: any = { + order_by: orderBy, + assignees: filters?.assignees ? filters?.assignees.join(",") : undefined, + state: filters?.state ? filters?.state.join(",") : undefined, + priority: filters?.priority ? filters?.priority.join(",") : undefined, + type: filters?.type ? filters?.type : undefined, + labels: filters?.labels ? filters?.labels.join(",") : undefined, + issue__assignees__id: filters?.issue__assignees__id + ? filters?.issue__assignees__id.join(",") + : undefined, + issue__labels__id: filters?.issue__labels__id + ? filters?.issue__labels__id.join(",") + : undefined, + created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, + sub_issue: "false", + }; + + const { data: projectSpreadsheetIssues } = useSWR( + workspaceSlug && projectId + ? PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params) + : null, + workspaceSlug && projectId + ? () => + issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params) + : null + ); + + const { data: cycleSpreadsheetIssues } = useSWR( + workspaceSlug && projectId && cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) + : null, + workspaceSlug && projectId && cycleId + ? () => + cyclesService.getCycleIssuesWithParams( + workspaceSlug.toString(), + projectId.toString(), + cycleId.toString(), + params + ) + : null + ); + + const { data: moduleSpreadsheetIssues } = useSWR( + workspaceSlug && projectId && moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) + : null, + workspaceSlug && projectId && moduleId + ? () => + modulesService.getModuleIssuesWithParams( + workspaceSlug.toString(), + projectId.toString(), + moduleId.toString(), + params + ) + : null + ); + + const { data: viewSpreadsheetIssues } = useSWR( + workspaceSlug && projectId && viewId && params ? VIEW_ISSUES(viewId.toString(), params) : null, + workspaceSlug && projectId && viewId && params + ? () => + issuesService.getIssuesWithParams(workspaceSlug.toString(), projectId.toString(), params) + : null + ); + + const spreadsheetIssues = cycleId + ? (cycleSpreadsheetIssues as IIssue[]) + : moduleId + ? (moduleSpreadsheetIssues as IIssue[]) + : viewId + ? (viewSpreadsheetIssues as IIssue[]) + : (projectSpreadsheetIssues as IIssue[]); + + return { + issueView, + spreadsheetIssues: spreadsheetIssues ?? [], + orderBy, + setOrderBy, + filters, + setFilters, + params, + resetFilterToDefault, + setNewFilterDefaultView, + setIssueView, + } as const; +}; + +export default useSpreadsheetIssuesView; diff --git a/apps/app/hooks/use-sub-issue.tsx b/apps/app/hooks/use-sub-issue.tsx new file mode 100644 index 000000000..8eb30fd0b --- /dev/null +++ b/apps/app/hooks/use-sub-issue.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; + +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// services +import issuesService from "services/issues.service"; +// types +import { ISubIssueResponse } from "types"; +// fetch-keys +import { SUB_ISSUES } from "constants/fetch-keys"; + +const useSubIssue = (issueId: string, isExpanded: boolean) => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const shouldFetch = workspaceSlug && projectId && issueId && isExpanded; + + const { data: subIssuesResponse, isLoading } = useSWR( + shouldFetch ? SUB_ISSUES(issueId as string) : null, + shouldFetch + ? () => + issuesService.subIssues(workspaceSlug as string, projectId as string, issueId as string) + : null + ); + + return { + subIssues: subIssuesResponse?.sub_issues ?? [], + isLoading, + }; +}; + +export default useSubIssue; diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index a8881924b..a33a04ffc 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -247,11 +247,25 @@ export interface IIssueFilterOptions { created_by: string[] | null; } -export type TIssueViewOptions = "list" | "kanban" | "calendar" | "gantt_chart"; +export type TIssueViewOptions = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt_chart"; export type TIssueGroupByOptions = "state" | "priority" | "labels" | "created_by" | null; -export type TIssueOrderByOptions = "-created_at" | "-updated_at" | "priority" | "sort_order"; +export type TIssueOrderByOptions = + | "-created_at" + | "-updated_at" + | "priority" + | "sort_order" + | "state__name" + | "-state__name" + | "assignees__name" + | "-assignees__name" + | "labels__name" + | "-labels__name" + | "target_date" + | "-target_date" + | "estimate__point" + | "-estimate__point"; export interface IIssueViewOptions { group_by: TIssueGroupByOptions; From 8e094aa89597a7d6e11c0b986247aa7048072fa7 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:28:06 +0530 Subject: [PATCH 13/57] chore: triage state filtering (#1372) --- apiserver/plane/api/views/analytic.py | 4 +++- apiserver/plane/api/views/importer.py | 8 +++++--- apiserver/plane/api/views/issue.py | 2 +- apiserver/plane/api/views/state.py | 6 ++++-- apiserver/plane/db/models/issue.py | 6 ++++-- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apiserver/plane/api/views/analytic.py b/apiserver/plane/api/views/analytic.py index a096c2700..e537af84a 100644 --- a/apiserver/plane/api/views/analytic.py +++ b/apiserver/plane/api/views/analytic.py @@ -3,6 +3,7 @@ from django.db.models import ( Count, Sum, F, + Q ) from django.db.models.functions import ExtractMonth @@ -59,10 +60,11 @@ class AnalyticsEndpoint(BaseAPIView): colors = ( State.objects.filter( + ~Q(name="Triage"), workspace__slug=slug, project_id__in=filters.get("project__in") ).values(key, "color") if filters.get("project__in", False) - else State.objects.filter(workspace__slug=slug).values(key, "color") + else State.objects.filter(~Q(name="Triage"), workspace__slug=slug).values(key, "color") ) if x_axis in ["labels__name"] or segment in ["labels__name"]: diff --git a/apiserver/plane/api/views/importer.py b/apiserver/plane/api/views/importer.py index e045a2ec1..28d490740 100644 --- a/apiserver/plane/api/views/importer.py +++ b/apiserver/plane/api/views/importer.py @@ -7,7 +7,7 @@ from rest_framework.response import Response from sentry_sdk import capture_exception # Django imports -from django.db.models import Max +from django.db.models import Max, Q # Module imports from plane.api.views import BaseAPIView @@ -309,11 +309,13 @@ class BulkImportIssuesEndpoint(BaseAPIView): # Get the default state default_state = State.objects.filter( - project_id=project_id, default=True + ~Q(name="Triage"), project_id=project_id, default=True ).first() # if there is no default state assign any random state if default_state is None: - default_state = State.objects.filter(project_id=project_id).first() + default_state = State.objects.filter( + ~Q(name="Triage"), sproject_id=project_id + ).first() # Get the maximum sequence_id last_id = IssueSequence.objects.filter(project_id=project_id).aggregate( diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 35583fea2..fa7d9d6ec 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -607,7 +607,7 @@ class SubIssuesEndpoint(BaseAPIView): ) state_distribution = ( - State.objects.filter(workspace__slug=slug, project_id=project_id) + State.objects.filter(~Q(name="Triage"), workspace__slug=slug, project_id=project_id) .annotate( state_count=Count( "state_issue", diff --git a/apiserver/plane/api/views/state.py b/apiserver/plane/api/views/state.py index 29cba7a74..4fe0c8260 100644 --- a/apiserver/plane/api/views/state.py +++ b/apiserver/plane/api/views/state.py @@ -3,13 +3,13 @@ from itertools import groupby # Django imports from django.db import IntegrityError +from django.db.models import Q # Third party imports from rest_framework.response import Response from rest_framework import status from sentry_sdk import capture_exception - # Module imports from . import BaseViewSet, BaseAPIView from plane.api.serializers import StateSerializer @@ -34,6 +34,7 @@ class StateViewSet(BaseViewSet): .filter(workspace__slug=self.kwargs.get("slug")) .filter(project_id=self.kwargs.get("project_id")) .filter(project__project_projectmember__member=self.request.user) + .filter(~Q(name="Triage")) .select_related("project") .select_related("workspace") .distinct() @@ -80,7 +81,8 @@ class StateViewSet(BaseViewSet): def destroy(self, request, slug, project_id, pk): try: state = State.objects.get( - pk=pk, project_id=project_id, workspace__slug=slug + ~Q(name="Triage"), + pk=pk, project_id=project_id, workspace__slug=slug, ) if state.default: diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 7efe86d46..1ecad6424 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -98,11 +98,13 @@ class Issue(ProjectBaseModel): from plane.db.models import State default_state = State.objects.filter( - project=self.project, default=True + ~models.Q(name="Triage"), project=self.project, default=True ).first() # if there is no default state assign any random state if default_state is None: - random_state = State.objects.filter(project=self.project).first() + random_state = State.objects.filter( + ~models.Q(name="Triage"), project=self.project + ).first() self.state = random_state if random_state.group == "started": self.start_date = timezone.now().date() From fef83d31536049f0b7d48b2361b0e343119b56f1 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:28:13 +0530 Subject: [PATCH 14/57] fix: inbox issue update (#1373) --- apiserver/plane/api/views/inbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index 24fff54ae..ada76c9b3 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -250,7 +250,7 @@ class InboxIssueViewSet(BaseViewSet): pk=inbox_issue.issue_id, workspace__slug=slug, project_id=project_id ) # Only allow guests and viewers to edit name and description - if project_member <= 10: + if project_member.role <= 10: # viewers and guests since only viewers and guests issue_data = { "name": issue_data.get("name", issue.name), From df41daf71b332bf03f9ac1ab993be45878dc75f1 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 23 Jun 2023 17:28:21 +0530 Subject: [PATCH 15/57] fix: auth error messages (#1376) --- apiserver/plane/api/views/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/authentication.py b/apiserver/plane/api/views/authentication.py index 385ec7568..068fae5a9 100644 --- a/apiserver/plane/api/views/authentication.py +++ b/apiserver/plane/api/views/authentication.py @@ -72,7 +72,7 @@ class SignUpEndpoint(BaseAPIView): # Check if the user already exists if User.objects.filter(email=email).exists(): return Response( - {"error": "User already exist please sign in"}, + {"error": "User with this email already exists"}, status=status.HTTP_400_BAD_REQUEST, ) From ff87137e404a02fc650c674ada5843ca2cc48cbe Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 19:29:17 +0530 Subject: [PATCH 16/57] fix: onboarding tracker (#1360) --- apps/app/services/user.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/app/services/user.service.ts b/apps/app/services/user.service.ts index c6b8494a4..3fe8852a0 100644 --- a/apps/app/services/user.service.ts +++ b/apps/app/services/user.service.ts @@ -58,7 +58,6 @@ class UserService extends APIService { if (trackEvent) trackEventServices.trackUserOnboardingCompleteEvent( { - ...response.data, user_role: userRole ?? "None", }, user From ca799a2b02587ce576776bf591e76f87aa84a95e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 19:29:36 +0530 Subject: [PATCH 17/57] fix: update task for ai requests (#1368) --- apps/app/components/issues/form.tsx | 2 +- apps/app/components/pages/create-update-block-inline.tsx | 2 +- apps/app/components/pages/single-page-block.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index cc5cae3f1..e7be252b4 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -200,7 +200,7 @@ export const IssueForm: FC = ({ projectId as string, { prompt: issueName, - task: "Generate a proper description for this issue in context of a project management software.", + task: "Generate a proper description for this issue.", }, user ) diff --git a/apps/app/components/pages/create-update-block-inline.tsx b/apps/app/components/pages/create-update-block-inline.tsx index 91c1108e4..0b3fcb17c 100644 --- a/apps/app/components/pages/create-update-block-inline.tsx +++ b/apps/app/components/pages/create-update-block-inline.tsx @@ -195,7 +195,7 @@ export const CreateUpdateBlockInline: React.FC = ({ projectId as string, { prompt: watch("name"), - task: "Generate a proper description for this issue in context of a project management software.", + task: "Generate a proper description for this issue.", }, user ) diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index 3efbd33eb..7255abbc8 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -194,7 +194,7 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index, projectId as string, { prompt: block.name, - task: "Generate a proper description for this issue in context of a project management software.", + task: "Generate a proper description for this issue.", }, user ) From 428d0dbac9fad925640312490df793084a04e247 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 19:30:11 +0530 Subject: [PATCH 18/57] fix: words breaking abruptly (#1371) --- .../app/components/analytics/custom-analytics/sidebar.tsx | 8 ++++---- apps/app/components/analytics/project-modal.tsx | 2 +- .../components/analytics/scope-and-demand/leaderboard.tsx | 2 +- apps/app/components/breadcrumbs/index.tsx | 2 +- apps/app/components/core/board-view/single-issue.tsx | 4 ++-- apps/app/components/core/sidebar/links-list.tsx | 2 +- apps/app/components/cycles/active-cycle-details.tsx | 2 +- apps/app/components/cycles/delete-cycle-modal.tsx | 2 +- apps/app/components/cycles/single-cycle-card.tsx | 4 ++-- apps/app/components/cycles/single-cycle-list.tsx | 8 ++++++-- apps/app/components/estimates/delete-estimate-modal.tsx | 4 ++-- apps/app/components/inbox/decline-issue-modal.tsx | 2 +- apps/app/components/inbox/delete-issue-modal.tsx | 2 +- apps/app/components/integration/delete-import-modal.tsx | 2 +- apps/app/components/issues/delete-issue-modal.tsx | 2 +- apps/app/components/issues/sub-issues-list.tsx | 2 +- apps/app/components/modules/delete-module-modal.tsx | 2 +- apps/app/components/modules/single-module-card.tsx | 2 +- apps/app/components/pages/delete-page-modal.tsx | 2 +- apps/app/components/pages/single-page-block.tsx | 2 +- apps/app/components/project/delete-project-modal.tsx | 8 ++++---- apps/app/components/project/single-project-card.tsx | 2 +- apps/app/components/ui/multi-level-dropdown.tsx | 2 +- apps/app/components/views/delete-view-modal.tsx | 2 +- apps/app/components/workspace/delete-workspace-modal.tsx | 8 ++++---- 25 files changed, 42 insertions(+), 38 deletions(-) diff --git a/apps/app/components/analytics/custom-analytics/sidebar.tsx b/apps/app/components/analytics/custom-analytics/sidebar.tsx index 5f4700f29..b533df519 100644 --- a/apps/app/components/analytics/custom-analytics/sidebar.tsx +++ b/apps/app/components/analytics/custom-analytics/sidebar.tsx @@ -237,7 +237,7 @@ export const AnalyticsSidebar: React.FC = ({ {project?.name.charAt(0)} )} -
    +
    {project.name} ({project.identifier}) @@ -276,7 +276,7 @@ export const AnalyticsSidebar: React.FC = ({ {projectId ? ( cycleId && cycleDetails ? (
    -

    Analytics for {cycleDetails.name}

    +

    Analytics for {cycleDetails.name}

    Lead
    @@ -304,7 +304,7 @@ export const AnalyticsSidebar: React.FC = ({
    ) : moduleId && moduleDetails ? (
    -

    Analytics for {moduleDetails.name}

    +

    Analytics for {moduleDetails.name}

    Lead
    @@ -352,7 +352,7 @@ export const AnalyticsSidebar: React.FC = ({ {projectDetails?.name.charAt(0)} )} -

    {projectDetails?.name}

    +

    {projectDetails?.name}

    diff --git a/apps/app/components/analytics/project-modal.tsx b/apps/app/components/analytics/project-modal.tsx index da308582f..5fdb6682d 100644 --- a/apps/app/components/analytics/project-modal.tsx +++ b/apps/app/components/analytics/project-modal.tsx @@ -160,7 +160,7 @@ export const AnalyticsProjectModal: React.FC = ({ isOpen, onClose }) => { }`} >
    -

    +

    Analytics for{" "} {cycleId ? cycleDetails?.name : moduleId ? moduleDetails?.name : projectDetails?.name}

    diff --git a/apps/app/components/analytics/scope-and-demand/leaderboard.tsx b/apps/app/components/analytics/scope-and-demand/leaderboard.tsx index 855f9eff4..72b892eeb 100644 --- a/apps/app/components/analytics/scope-and-demand/leaderboard.tsx +++ b/apps/app/components/analytics/scope-and-demand/leaderboard.tsx @@ -33,7 +33,7 @@ export const AnalyticsLeaderboard: React.FC = ({ users, title }) => ( {user.firstName !== "" ? user.firstName[0] : "?"}
    )} - + {user.firstName !== "" ? `${user.firstName} ${user.lastName}` : "No assignee"}
    diff --git a/apps/app/components/breadcrumbs/index.tsx b/apps/app/components/breadcrumbs/index.tsx index 240faefa2..6e2c85785 100644 --- a/apps/app/components/breadcrumbs/index.tsx +++ b/apps/app/components/breadcrumbs/index.tsx @@ -52,7 +52,7 @@ const BreadcrumbItem: React.FC = ({ title, link, icon }) =>

    {icon} - {title} + {title}

    )} diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 3571efa41..5c0cc5102 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -338,8 +338,8 @@ export const SingleBoardIssue: React.FC = ({ {issue.project_detail.identifier}-{issue.sequence_id}
    )} -
    - {truncateText(issue.name, 120)} +
    + {issue.name}
    diff --git a/apps/app/components/core/sidebar/links-list.tsx b/apps/app/components/core/sidebar/links-list.tsx index 951332f3f..590ebc758 100644 --- a/apps/app/components/core/sidebar/links-list.tsx +++ b/apps/app/components/core/sidebar/links-list.tsx @@ -53,7 +53,7 @@ export const LinksList: React.FC = ({ links, handleDeleteLink, userAuth }
    -
    {link.title}
    +
    {link.title}

    Added {timeAgo(link.created_at)}
    diff --git a/apps/app/components/cycles/active-cycle-details.tsx b/apps/app/components/cycles/active-cycle-details.tsx index b2f27d037..b15dbd3ca 100644 --- a/apps/app/components/cycles/active-cycle-details.tsx +++ b/apps/app/components/cycles/active-cycle-details.tsx @@ -226,7 +226,7 @@ export const ActiveCycleDetails: React.FC = () => { /> -

    +

    {truncateText(cycle.name, 70)}

    diff --git a/apps/app/components/cycles/delete-cycle-modal.tsx b/apps/app/components/cycles/delete-cycle-modal.tsx index 3f2de2913..d60e3ddce 100644 --- a/apps/app/components/cycles/delete-cycle-modal.tsx +++ b/apps/app/components/cycles/delete-cycle-modal.tsx @@ -143,7 +143,7 @@ export const DeleteCycleModal: React.FC = ({

    Are you sure you want to delete cycle-{" "} - + {data?.name} ? All of the data related to the cycle will be permanently removed. This diff --git a/apps/app/components/cycles/single-cycle-card.tsx b/apps/app/components/cycles/single-cycle-card.tsx index c6a6365b0..c00429a43 100644 --- a/apps/app/components/cycles/single-cycle-card.tsx +++ b/apps/app/components/cycles/single-cycle-card.tsx @@ -150,8 +150,8 @@ export const SingleCycleCard: React.FC = ({ }`} /> - -

    + +

    {truncateText(cycle.name, 15)}

    diff --git a/apps/app/components/cycles/single-cycle-list.tsx b/apps/app/components/cycles/single-cycle-list.tsx index d957b5ab7..fa725b83a 100644 --- a/apps/app/components/cycles/single-cycle-list.tsx +++ b/apps/app/components/cycles/single-cycle-list.tsx @@ -173,8 +173,12 @@ export const SingleCycleList: React.FC = ({ }`} />
    - -

    + +

    {truncateText(cycle.name, 70)}

    diff --git a/apps/app/components/estimates/delete-estimate-modal.tsx b/apps/app/components/estimates/delete-estimate-modal.tsx index c456ceab6..5a4f9ccfa 100644 --- a/apps/app/components/estimates/delete-estimate-modal.tsx +++ b/apps/app/components/estimates/delete-estimate-modal.tsx @@ -74,9 +74,9 @@ export const DeleteEstimateModal: React.FC = ({

    -

    +

    Are you sure you want to delete estimate-{" "} - {data.name} + {data.name} {""}? All of the data related to the estiamte will be permanently removed. This action cannot be undone.

    diff --git a/apps/app/components/inbox/decline-issue-modal.tsx b/apps/app/components/inbox/decline-issue-modal.tsx index 941841659..11f1db5de 100644 --- a/apps/app/components/inbox/decline-issue-modal.tsx +++ b/apps/app/components/inbox/decline-issue-modal.tsx @@ -72,7 +72,7 @@ export const DeclineIssueModal: React.FC = ({ isOpen, handleClose, data,

    Are you sure you want to decline issue{" "} - + {data?.project_detail?.identifier}-{data?.sequence_id} {""}? This action cannot be undone. diff --git a/apps/app/components/inbox/delete-issue-modal.tsx b/apps/app/components/inbox/delete-issue-modal.tsx index c6f5320a2..f188ff1aa 100644 --- a/apps/app/components/inbox/delete-issue-modal.tsx +++ b/apps/app/components/inbox/delete-issue-modal.tsx @@ -127,7 +127,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data })

    Are you sure you want to delete issue{" "} - + {data?.project_detail?.identifier}-{data?.sequence_id} {""}? The issue will only be deleted from the inbox and this action cannot be diff --git a/apps/app/components/integration/delete-import-modal.tsx b/apps/app/components/integration/delete-import-modal.tsx index cd0b12a2a..57af3fbfc 100644 --- a/apps/app/components/integration/delete-import-modal.tsx +++ b/apps/app/components/integration/delete-import-modal.tsx @@ -104,7 +104,7 @@ export const DeleteImportModal: React.FC = ({ isOpen, handleClose, data,

    Are you sure you want to delete import from{" "} - + {data?.service} ? All of the data related to the import will be permanently removed. This diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index ffdebb314..000b21387 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -151,7 +151,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data, u

    Are you sure you want to delete issue{" "} - + {data?.project_detail.identifier}-{data?.sequence_id} {""}? All of the data related to the issue will be permanently removed. This diff --git a/apps/app/components/issues/sub-issues-list.tsx b/apps/app/components/issues/sub-issues-list.tsx index ac550348e..3558c8d74 100644 --- a/apps/app/components/issues/sub-issues-list.tsx +++ b/apps/app/components/issues/sub-issues-list.tsx @@ -282,7 +282,7 @@ export const SubIssuesList: FC = ({ parentIssue, user }) => { {issue.project_detail.identifier}-{issue.sequence_id} - {issue.name} + {issue.name}

    {!isNotAllowed && ( diff --git a/apps/app/components/modules/delete-module-modal.tsx b/apps/app/components/modules/delete-module-modal.tsx index f2a9ec7ee..deece2ea5 100644 --- a/apps/app/components/modules/delete-module-modal.tsx +++ b/apps/app/components/modules/delete-module-modal.tsx @@ -111,7 +111,7 @@ export const DeleteModuleModal: React.FC = ({ isOpen, setIsOpen, data, us

    Are you sure you want to delete module-{" "} - + {data?.name} ? All of the data related to the module will be permanently removed. This diff --git a/apps/app/components/modules/single-module-card.tsx b/apps/app/components/modules/single-module-card.tsx index 21eb25bbb..ac92bdcdd 100644 --- a/apps/app/components/modules/single-module-card.tsx +++ b/apps/app/components/modules/single-module-card.tsx @@ -138,7 +138,7 @@ export const SingleModuleCard: React.FC = ({ module, handleEditModule, us -

    +

    {truncateText(module.name, 75)}

    diff --git a/apps/app/components/pages/delete-page-modal.tsx b/apps/app/components/pages/delete-page-modal.tsx index 6277870d1..eaa7c2189 100644 --- a/apps/app/components/pages/delete-page-modal.tsx +++ b/apps/app/components/pages/delete-page-modal.tsx @@ -136,7 +136,7 @@ export const DeletePageModal: React.FC = ({

    Are you sure you want to delete Page-{" "} - + {data?.name} ? All of the data related to the page will be permanently removed. This diff --git a/apps/app/components/pages/single-page-block.tsx b/apps/app/components/pages/single-page-block.tsx index 7255abbc8..898f4aba5 100644 --- a/apps/app/components/pages/single-page-block.tsx +++ b/apps/app/components/pages/single-page-block.tsx @@ -417,7 +417,7 @@ export const SinglePageBlock: React.FC = ({ block, projectDetails, index,

    setCreateBlockForm(true)} >
    diff --git a/apps/app/components/project/delete-project-modal.tsx b/apps/app/components/project/delete-project-modal.tsx index 5a4be1706..eabe85f2d 100644 --- a/apps/app/components/project/delete-project-modal.tsx +++ b/apps/app/components/project/delete-project-modal.tsx @@ -128,13 +128,13 @@ export const DeleteProjectModal: React.FC = ({

    Are you sure you want to delete project{" "} - {selectedProject?.name}? All - of the data related to the project will be permanently removed. This action - cannot be undone + {selectedProject?.name}? + All of the data related to the project will be permanently removed. This + action cannot be undone

    -

    +

    Enter the project name{" "} {selectedProject?.name}{" "} to continue: diff --git a/apps/app/components/project/single-project-card.tsx b/apps/app/components/project/single-project-card.tsx index 66ef6aa2a..04e56652d 100644 --- a/apps/app/components/project/single-project-card.tsx +++ b/apps/app/components/project/single-project-card.tsx @@ -195,7 +195,7 @@ export const SingleProjectCard: React.FC = ({ ) : null}

    -

    +

    {truncateText(project.description ?? "", 100)}

    diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index 0f25d06b3..e93fba887 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -127,7 +127,7 @@ export const MultiLevelDropdown: React.FC = ({ }} className={`${ child.selected ? "bg-brand-surface-2" : "" - } flex w-full items-center whitespace-nowrap break-all rounded px-1 py-1.5 text-left capitalize text-brand-secondary hover:bg-brand-surface-2`} + } flex w-full items-center whitespace-nowrap break-words rounded px-1 py-1.5 text-left capitalize text-brand-secondary hover:bg-brand-surface-2`} > {child.label} diff --git a/apps/app/components/views/delete-view-modal.tsx b/apps/app/components/views/delete-view-modal.tsx index c57c29dc3..fa5e6781c 100644 --- a/apps/app/components/views/delete-view-modal.tsx +++ b/apps/app/components/views/delete-view-modal.tsx @@ -115,7 +115,7 @@ export const DeleteViewModal: React.FC = ({ isOpen, data, setIsOpen, user

    Are you sure you want to delete view-{" "} - + {data?.name} ? All of the data related to the view will be permanently removed. This diff --git a/apps/app/components/workspace/delete-workspace-modal.tsx b/apps/app/components/workspace/delete-workspace-modal.tsx index 344d700b0..b9f3e60f4 100644 --- a/apps/app/components/workspace/delete-workspace-modal.tsx +++ b/apps/app/components/workspace/delete-workspace-modal.tsx @@ -120,14 +120,14 @@ export const DeleteWorkspaceModal: React.FC = ({ isOpen, data, onClose, u

    Are you sure you want to delete workspace{" "} - {data?.name}? All of the data - related to the workspace will be permanently removed. This action cannot be - undone. + {data?.name}? All of the + data related to the workspace will be permanently removed. This action cannot + be undone.

    -

    +

    Enter the workspace name{" "} {selectedWorkspace?.name}{" "} to continue: From b87e2fff4cb72a1601b270026c80a7aaacbc512e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 19:30:32 +0530 Subject: [PATCH 19/57] chore: show error messages from request (#1375) --- apps/app/pages/index.tsx | 34 +++++++++------------ apps/app/pages/reset-password.tsx | 6 ++-- apps/app/pages/sign-up.tsx | 23 ++++++-------- apps/app/services/authentication.service.ts | 3 +- 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/apps/app/pages/index.tsx b/apps/app/pages/index.tsx index ab2633241..abe317415 100644 --- a/apps/app/pages/index.tsx +++ b/apps/app/pages/index.tsx @@ -46,14 +46,12 @@ const HomePage: NextPage = () => { } else { throw Error("Cant find credentials"); } - } catch (error: any) { - console.log(error); + } catch (err: any) { setToastAlert({ title: "Error signing in!", type: "error", message: - error?.error || - "Something went wrong. Please try again later or contact the support team.", + err?.error || "Something went wrong. Please try again later or contact the support team.", }); } }; @@ -71,13 +69,12 @@ const HomePage: NextPage = () => { } else { throw Error("Cant find credentials"); } - } catch (error: any) { + } catch (err: any) { setToastAlert({ title: "Error signing in!", type: "error", message: - error?.error || - "Something went wrong. Please try again later or contact the support team.", + err?.error || "Something went wrong. Please try again later or contact the support team.", }); } }; @@ -88,22 +85,23 @@ const HomePage: NextPage = () => { .then((response) => { try { if (response) mutateUser(); - } catch (error: any) { - console.log(error); + } catch (err: any) { setToastAlert({ - title: "Error signing in!", type: "error", + title: "Error!", message: - error?.error || + err?.error || "Something went wrong. Please try again later or contact the support team.", }); } }) - .catch(() => + .catch((err) => setToastAlert({ - title: "Oops!", type: "error", - message: "Enter the correct email address and password to sign in", + title: "Error!", + message: + err?.error || + "Something went wrong. Please try again later or contact the support team.", }) ); }; @@ -111,14 +109,12 @@ const HomePage: NextPage = () => { const handleEmailCodeSignIn = async (response: any) => { try { if (response) mutateUser(); - } catch (error: any) { - console.log(error); + } catch (err: any) { setToastAlert({ - title: "Error signing in!", type: "error", + title: "Error!", message: - error?.error || - "Something went wrong. Please try again later or contact the support team.", + err?.error || "Something went wrong. Please try again later or contact the support team.", }); } }; diff --git a/apps/app/pages/reset-password.tsx b/apps/app/pages/reset-password.tsx index 86ab556b1..82ee32114 100644 --- a/apps/app/pages/reset-password.tsx +++ b/apps/app/pages/reset-password.tsx @@ -63,11 +63,13 @@ const ResetPasswordPage: NextPage = () => { }); router.push("/"); }) - .catch(() => + .catch((err) => setToastAlert({ type: "error", title: "Error!", - message: "Something went wrong. Please try again.", + message: + err?.error || + "Something went wrong. Please try again later or contact the support team.", }) ); }; diff --git a/apps/app/pages/sign-up.tsx b/apps/app/pages/sign-up.tsx index eeffb53bd..2c86d96ff 100644 --- a/apps/app/pages/sign-up.tsx +++ b/apps/app/pages/sign-up.tsx @@ -47,20 +47,15 @@ const SignUp: NextPage = () => { if (response) await mutateUser(); router.push("/"); }) - .catch((err) => { - if (err.status === 400) - setToastAlert({ - type: "error", - title: "Error!", - message: "An user already exists with this Email ID.", - }); - else - setToastAlert({ - type: "error", - title: "Error!", - message: "Something went wrong. Please try again later or contact the support team.", - }); - }); + .catch((err) => + setToastAlert({ + type: "error", + title: "Error!", + message: + err?.error || + "Something went wrong. Please try again later or contact the support team.", + }) + ); }; return ( diff --git a/apps/app/services/authentication.service.ts b/apps/app/services/authentication.service.ts index f0d19da24..86f55e329 100644 --- a/apps/app/services/authentication.service.ts +++ b/apps/app/services/authentication.service.ts @@ -28,7 +28,7 @@ class AuthService extends APIService { return response?.data; }) .catch((error) => { - throw error?.response; + throw error?.response?.data; }); } @@ -51,6 +51,7 @@ class AuthService extends APIService { throw error?.response?.data; }); } + async magicSignIn(data: any) { const response = await this.post("/api/magic-sign-in/", data, { headers: {} }); if (response?.status === 200) { From ca7d3309d31e44336d68e57fcd572e8699373074 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Fri, 23 Jun 2023 19:30:53 +0530 Subject: [PATCH 20/57] chore: accept issue confirmation modal (#1377) * chore: accept issue confirmation modal * chore: add inbox option to the command menu * fix: status colors not loading * chore: show state name on the inbox issue sidebar --- .../command-palette/command-pallette.tsx | 54 ++++++++--- .../components/inbox/accept-issue-modal.tsx | 92 +++++++++++++++++++ .../components/inbox/inbox-action-headers.tsx | 26 +++--- .../components/inbox/inbox-main-content.tsx | 4 +- apps/app/components/inbox/index.ts | 1 + .../components/inbox/issues-list-sidebar.tsx | 5 +- .../issues/sidebar-select/state.tsx | 29 ++++-- .../components/ui/multi-level-dropdown.tsx | 2 +- apps/app/styles/command-pallette.css | 4 +- 9 files changed, 177 insertions(+), 40 deletions(-) create mode 100644 apps/app/components/inbox/accept-issue-modal.tsx diff --git a/apps/app/components/command-palette/command-pallette.tsx b/apps/app/components/command-palette/command-pallette.tsx index cb2330b24..32634f18c 100644 --- a/apps/app/components/command-palette/command-pallette.tsx +++ b/apps/app/components/command-palette/command-pallette.tsx @@ -9,6 +9,7 @@ import { ChatBubbleOvalLeftEllipsisIcon, DocumentTextIcon, FolderPlusIcon, + InboxIcon, LinkIcon, MagnifyingGlassIcon, RocketLaunchIcon, @@ -34,6 +35,7 @@ import { Dialog, Transition } from "@headlessui/react"; // cmdk import { Command } from "cmdk"; // hooks +import useProjectDetails from "hooks/use-project-details"; import useTheme from "hooks/use-theme"; import useToast from "hooks/use-toast"; import useUser from "hooks/use-user"; @@ -64,10 +66,11 @@ import { // services import issuesService from "services/issues.service"; import workspaceService from "services/workspace.service"; +import inboxService from "services/inbox.service"; // types import { IIssue, IWorkspaceSearchResults } from "types"; // fetch keys -import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; +import { INBOX_LIST, ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; export const CommandPalette: React.FC = () => { const [isPaletteOpen, setIsPaletteOpen] = useState(false); @@ -105,6 +108,8 @@ export const CommandPalette: React.FC = () => { const { workspaceSlug, projectId, issueId, inboxId } = router.query; const { user } = useUser(); + const { projectDetails } = useProjectDetails(); + const { setToastAlert } = useToast(); const { toggleCollapsed } = useTheme(); @@ -116,6 +121,13 @@ export const CommandPalette: React.FC = () => { : null ); + const { data: inboxList } = useSWR( + workspaceSlug && projectId ? INBOX_LIST(projectId as string) : null, + workspaceSlug && projectId + ? () => inboxService.getInboxes(workspaceSlug as string, projectId as string) + : null + ); + const updateIssue = useCallback( async (formData: Partial) => { if (!workspaceSlug || !projectId || !issueId) return; @@ -321,9 +333,9 @@ export const CommandPalette: React.FC = () => { setDeleteIssueModal(true); }; - const goToSettings = (path: string = "") => { + const redirect = (path: string) => { setIsPaletteOpen(false); - router.push(`/${workspaceSlug}/settings/${path}`); + router.push(path); }; return ( @@ -396,7 +408,7 @@ export const CommandPalette: React.FC = () => { leaveFrom="opacity-100" leaveTo="opacity-0" > -

    +
    @@ -409,14 +421,14 @@ export const CommandPalette: React.FC = () => { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - + { if (value.toLowerCase().includes(search.toLowerCase())) return 1; return 0; }} onKeyDown={(e) => { - // when seach is empty and page is undefined + // when search is empty and page is undefined // when user tries to close the modal with esc if (e.key === "Escape" && !page && !searchTerm) { setIsPaletteOpen(false); @@ -698,6 +710,24 @@ export const CommandPalette: React.FC = () => { D + + {projectDetails && projectDetails.inbox_view && ( + + + redirect( + `/${workspaceSlug}/projects/${projectId}/inbox/${inboxList?.[0]?.id}` + ) + } + className="focus:outline-none" + > +
    + + Open inbox +
    +
    +
    + )} )} @@ -814,7 +844,7 @@ export const CommandPalette: React.FC = () => { {page === "settings" && workspaceSlug && ( <> goToSettings()} + onSelect={() => redirect(`/${workspaceSlug}/settings`)} className="focus:outline-none" >
    @@ -823,7 +853,7 @@ export const CommandPalette: React.FC = () => {
    goToSettings("members")} + onSelect={() => redirect(`/${workspaceSlug}/settings/members`)} className="focus:outline-none" >
    @@ -832,7 +862,7 @@ export const CommandPalette: React.FC = () => {
    goToSettings("billing")} + onSelect={() => redirect(`/${workspaceSlug}/settings/billing`)} className="focus:outline-none" >
    @@ -841,7 +871,7 @@ export const CommandPalette: React.FC = () => {
    goToSettings("integrations")} + onSelect={() => redirect(`/${workspaceSlug}/settings/integrations`)} className="focus:outline-none" >
    @@ -850,12 +880,12 @@ export const CommandPalette: React.FC = () => {
    goToSettings("import-export")} + onSelect={() => redirect(`/${workspaceSlug}/settings/import-export`)} className="focus:outline-none" >
    - Import/ Export + Import/Export
    diff --git a/apps/app/components/inbox/accept-issue-modal.tsx b/apps/app/components/inbox/accept-issue-modal.tsx new file mode 100644 index 000000000..6427c562c --- /dev/null +++ b/apps/app/components/inbox/accept-issue-modal.tsx @@ -0,0 +1,92 @@ +import React, { useState } from "react"; + +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// icons +import { CheckCircleIcon } from "@heroicons/react/24/outline"; +// ui +import { SecondaryButton, PrimaryButton } from "components/ui"; +// types +import type { IInboxIssue } from "types"; + +type Props = { + isOpen: boolean; + handleClose: () => void; + data: IInboxIssue | undefined; + onSubmit: () => Promise; +}; + +export const AcceptIssueModal: React.FC = ({ isOpen, handleClose, data, onSubmit }) => { + const [isAccepting, setIsAccepting] = useState(false); + + const onClose = () => { + setIsAccepting(false); + handleClose(); + }; + + const handleAccept = () => { + setIsAccepting(true); + + onSubmit().finally(() => setIsAccepting(false)); + }; + + return ( + + + +
    + + +
    +
    + + +
    +
    + + + +

    Accept Issue

    +
    +
    + +

    + Are you sure you want to accept issue{" "} + + {data?.project_detail?.identifier}-{data?.sequence_id} + + {""}? Once accepted, this issue will be added to the project issues list. +

    +
    +
    + Cancel + + {isAccepting ? "Accepting..." : "Accept Issue"} + +
    +
    +
    +
    +
    +
    +
    +
    + ); +}; diff --git a/apps/app/components/inbox/inbox-action-headers.tsx b/apps/app/components/inbox/inbox-action-headers.tsx index 5702d560b..ac144ba0b 100644 --- a/apps/app/components/inbox/inbox-action-headers.tsx +++ b/apps/app/components/inbox/inbox-action-headers.tsx @@ -18,6 +18,7 @@ import useUserAuth from "hooks/use-user-auth"; import useToast from "hooks/use-toast"; // components import { + AcceptIssueModal, DeclineIssueModal, DeleteIssueModal, FiltersDropdown, @@ -41,9 +42,9 @@ import type { IInboxIssueDetail, TInboxStatus } from "types"; import { INBOX_ISSUE_DETAILS } from "constants/fetch-keys"; export const InboxActionHeader = () => { - const [isAccepting, setIsAccepting] = useState(false); const [date, setDate] = useState(new Date()); const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); + const [acceptIssueModal, setAcceptIssueModal] = useState(false); const [declineIssueModal, setDeclineIssueModal] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false); @@ -102,14 +103,6 @@ export const InboxActionHeader = () => { }); }; - const handleAcceptIssue = () => { - setIsAccepting(true); - - markInboxStatus({ - status: 1, - }).finally(() => setIsAccepting(false)); - }; - const issue = inboxIssues?.find((issue) => issue.bridge_id === inboxIssueId); const currentIssueIndex = inboxIssues?.findIndex((issue) => issue.bridge_id === inboxIssueId) ?? 0; @@ -144,6 +137,16 @@ export const InboxActionHeader = () => { }).finally(() => setSelectDuplicateIssue(false)); }} /> + setAcceptIssueModal(false)} + data={inboxIssues?.find((i) => i.bridge_id === inboxIssueId)} + onSubmit={async () => { + await markInboxStatus({ + status: 1, + }).finally(() => setAcceptIssueModal(false)); + }} + /> setDeclineIssueModal(false)} @@ -252,11 +255,10 @@ export const InboxActionHeader = () => { setAcceptIssueModal(true)} > - {isAccepting ? "Accepting..." : "Accept"} + Accept
    )} diff --git a/apps/app/components/inbox/inbox-main-content.tsx b/apps/app/components/inbox/inbox-main-content.tsx index d03368944..a75c00317 100644 --- a/apps/app/components/inbox/inbox-main-content.tsx +++ b/apps/app/components/inbox/inbox-main-content.tsx @@ -227,7 +227,9 @@ export const InboxMainContent: React.FC = () => { issueStatus === 0 && new Date(issueDetails.issue_inbox[0].snoozed_till ?? "") < new Date() ? "text-red-500 border-red-500 bg-red-500/10" - : `${inboxStatusDetails?.textColor} ${inboxStatusDetails?.bgColor} ${inboxStatusDetails?.borderColor}` + : inboxStatusDetails + ? `${inboxStatusDetails.textColor} ${inboxStatusDetails.bgColor} ${inboxStatusDetails.borderColor}` + : "" }`} > {issueStatus === -2 ? ( diff --git a/apps/app/components/inbox/index.ts b/apps/app/components/inbox/index.ts index 7cdd8ee9d..38cea0348 100644 --- a/apps/app/components/inbox/index.ts +++ b/apps/app/components/inbox/index.ts @@ -1,3 +1,4 @@ +export * from "./accept-issue-modal"; export * from "./decline-issue-modal"; export * from "./delete-issue-modal"; export * from "./filters-dropdown"; diff --git a/apps/app/components/inbox/issues-list-sidebar.tsx b/apps/app/components/inbox/issues-list-sidebar.tsx index 9f5c85db1..6126be117 100644 --- a/apps/app/components/inbox/issues-list-sidebar.tsx +++ b/apps/app/components/inbox/issues-list-sidebar.tsx @@ -11,7 +11,7 @@ export const IssuesListSidebar = () => { const router = useRouter(); const { inboxIssueId } = router.query; - const { issues: inboxIssues } = useInboxView(); + const { issues: inboxIssues, filtersLength } = useInboxView(); return (
    @@ -29,7 +29,8 @@ export const IssuesListSidebar = () => {
    ) : (
    - No issues found for the selected filters. Try changing the filters. + {filtersLength > 0 && + "No issues found for the selected filters. Try changing the filters."}
    ) ) : ( diff --git a/apps/app/components/issues/sidebar-select/state.tsx b/apps/app/components/issues/sidebar-select/state.tsx index 8abb362db..02d1dd5cb 100644 --- a/apps/app/components/issues/sidebar-select/state.tsx +++ b/apps/app/components/issues/sidebar-select/state.tsx @@ -27,7 +27,7 @@ type Props = { export const SidebarStateSelect: React.FC = ({ value, onChange, userAuth }) => { const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId, inboxIssueId } = router.query; const { data: stateGroups } = useSWR( workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, @@ -50,15 +50,24 @@ export const SidebarStateSelect: React.FC = ({ value, onChange, userAuth
    - {getStateGroupIcon( - selectedState?.group ?? "backlog", - "16", - "16", - selectedState?.color ?? "" - )} - {addSpaceIfCamelCase(selectedState?.name ?? "")} -
    + selectedState ? ( +
    + {getStateGroupIcon( + selectedState?.group ?? "backlog", + "16", + "16", + selectedState?.color ?? "" + )} + {addSpaceIfCamelCase(selectedState?.name ?? "")} +
    + ) : inboxIssueId ? ( +
    + {getStateGroupIcon("backlog", "16", "16", "#ff7700")} + Triage +
    + ) : ( + "None" + ) } value={value} onChange={onChange} diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index e93fba887..b0997972f 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -35,7 +35,7 @@ export const MultiLevelDropdown: React.FC = ({ const [openChildFor, setOpenChildFor] = useState(null); return ( - + {({ open }) => ( <>
    diff --git a/apps/app/styles/command-pallette.css b/apps/app/styles/command-pallette.css index 5362f308a..a421eeba9 100644 --- a/apps/app/styles/command-pallette.css +++ b/apps/app/styles/command-pallette.css @@ -31,9 +31,9 @@ } [cmdk-item]:hover { - background-color: rgba(var(--color-bg-base)); + background-color: rgba(var(--color-bg-surface-2)); } [cmdk-item][aria-selected="true"] { - background-color: rgba(var(--color-bg-base)); + background-color: rgba(var(--color-bg-surface-2)); } From 160cc014a731a36626e97b29059cf759120610e2 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 23 Jun 2023 21:33:24 +0530 Subject: [PATCH 21/57] feat: spreadsheet view improvements (#1379) * feat: quick menu for spreadsheet view added ,style: spreadsheet view column updated ,fix: z-index issue * feat: sorting indicator, style: spreadsheet column --- .../core/spreadsheet-view/single-issue.tsx | 23 ++- .../spreadsheet-view/spreadsheet-columns.tsx | 180 +++++++++++------- .../spreadsheet-view/spreadsheet-view.tsx | 2 +- apps/app/constants/spreadsheet.ts | 2 + 4 files changed, 125 insertions(+), 82 deletions(-) diff --git a/apps/app/components/core/spreadsheet-view/single-issue.tsx b/apps/app/components/core/spreadsheet-view/single-issue.tsx index d3086e254..f763e706c 100644 --- a/apps/app/components/core/spreadsheet-view/single-issue.tsx +++ b/apps/app/components/core/spreadsheet-view/single-issue.tsx @@ -128,15 +128,15 @@ export const SingleSpreadsheetIssue: React.FC = ({ className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max" style={{ gridTemplateColumns }} > -
    +
    - {properties.key && ( - <> -
    +
    + {properties.key && ( + {issue.project_detail?.identifier}-{issue.sequence_id} -
    - - )} + + )} +
    {issue.sub_issues_count > 0 && ( @@ -237,9 +237,14 @@ export const SingleSpreadsheetIssue: React.FC = ({ />
    )} -
    +
    {!isNotAllowed && ( - +
    diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx index 9f615f165..85d05a288 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx @@ -19,13 +19,17 @@ export const SpreadsheetColumns: React.FC = ({ columnData, gridTemplateCo "spreadsheetViewSorting", "" ); + const { storedValue: activeSortingProperty, setValue: setActiveSortingProperty } = + useLocalStorage("spreadsheetViewActiveSortingProperty", ""); const { orderBy, setOrderBy } = useSpreadsheetIssuesView(); const handleOrderBy = (order: TIssueOrderByOptions, itemKey: string) => { setOrderBy(order); setSelectedMenuItem(`${order}_${itemKey}`); + setActiveSortingProperty(order === "-created_at" ? "" : itemKey); }; + return (
    = ({ columnData, gridTemplateCo if (col.isActive) { return (
    - {col.propertyName === "title" || col.propertyName === "priority" ? ( + {col.propertyName === "title" ? (
    - {col.icon ? ( -
    ) : ( + {activeSortingProperty === col.propertyName && ( +
    + +
    + )} + {col.icon ? ( = ({ columnData, gridTemplateCo : "text-brand-secondary hover:text-brand-base" }`} > -
    +
    {col.propertyName === "assignee" || col.propertyName === "labels" ? ( <> - A-Z - Ascending + + + + + A + + Z ) : col.propertyName === "due_date" ? ( <> - 1-9 - Ascending - - ) : col.propertyName === "estimate" ? ( - <> - 0 + + + + + New - 10 + Old ) : ( <> + + + + First Last @@ -146,7 +162,7 @@ export const SpreadsheetColumns: React.FC = ({ columnData, gridTemplateCo
    = ({ columnData, gridTemplateCo : "text-brand-secondary hover:text-brand-base" }`} > -
    +
    {col.propertyName === "assignee" || col.propertyName === "labels" ? ( <> - Z-A - Descending + + + + + Z + + A ) : col.propertyName === "due_date" ? ( <> - 9-1 - Descending - - ) : col.propertyName === "estimate" ? ( - <> - 10 + + + + + Old - 0 + New ) : ( <> + + + + Last First @@ -198,38 +240,32 @@ export const SpreadsheetColumns: React.FC = ({ columnData, gridTemplateCo />
    - { - handleOrderBy("-created_at", col.propertyName); - }} - > -
    -
    - - None -
    - - -
    -
    + key={col.property} + onClick={() => { + handleOrderBy("-created_at", col.propertyName); + }} + > +
    +
    + + + + + Clear sorting +
    +
    + + )} )}
    diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx index 6f36b2dbb..c47ddd805 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx @@ -56,7 +56,7 @@ export const SpreadsheetView: React.FC = ({ return (
    -
    +
    {spreadsheetIssues ? ( diff --git a/apps/app/constants/spreadsheet.ts b/apps/app/constants/spreadsheet.ts index 5ef60f40d..8e4bdb21d 100644 --- a/apps/app/constants/spreadsheet.ts +++ b/apps/app/constants/spreadsheet.ts @@ -24,6 +24,8 @@ export const SPREADSHEET_COLUMN = [ propertyName: "priority", colName: "Priority", colSize: "128px", + ascendingOrder: "priority", + descendingOrder: "-priority", }, { propertyName: "assignee", From ccbe773ce1d7a76e2f515b85b2f59aeaa8c82499 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 23 Jun 2023 22:18:03 +0530 Subject: [PATCH 22/57] fix: state and priority ordering (#1378) --- apiserver/plane/api/views/issue.py | 40 +++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index fa7d9d6ec..dba7a7a2f 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -152,8 +152,9 @@ class IssueViewSet(BaseViewSet): filters = issue_filters(request.query_params, "GET") show_sub_issues = request.GET.get("show_sub_issues", "true") - # Custom ordering for priority + # Custom ordering for priority and state priority_order = ["urgent", "high", "medium", "low", None] + state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] order_by_param = request.GET.get("order_by", "-created_at") @@ -178,7 +179,13 @@ class IssueViewSet(BaseViewSet): ) ) - if order_by_param == "priority": + # Priority Ordering + if order_by_param == "priority" or order_by_param == "-priority": + priority_order = ( + priority_order + if order_by_param == "priority" + else priority_order[::-1] + ) issue_queryset = issue_queryset.annotate( priority_order=Case( *[ @@ -188,6 +195,29 @@ class IssueViewSet(BaseViewSet): output_field=CharField(), ) ).order_by("priority_order") + + # State Ordering + elif order_by_param in [ + "state__name", + "state__group", + "-state__name", + "-state__group", + ]: + state_order = ( + state_order + if order_by_param in ["state__name", "state__group"] + else state_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + state_order=Case( + *[ + When(state__group=state_group, then=Value(i)) + for i, state_group in enumerate(state_order) + ], + default=Value(len(state_order)), + output_field=CharField(), + ) + ).order_by("state_order") else: issue_queryset = issue_queryset.order_by(order_by_param) @@ -209,7 +239,7 @@ class IssueViewSet(BaseViewSet): return Response(issues, status=status.HTTP_200_OK) except Exception as e: - capture_exception(e) + print(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, @@ -607,7 +637,9 @@ class SubIssuesEndpoint(BaseAPIView): ) state_distribution = ( - State.objects.filter(~Q(name="Triage"), workspace__slug=slug, project_id=project_id) + State.objects.filter( + ~Q(name="Triage"), workspace__slug=slug, project_id=project_id + ) .annotate( state_count=Count( "state_issue", From ddaa8df1c5542bee9e3866a54491b4f783a35a03 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sat, 24 Jun 2023 00:35:00 +0530 Subject: [PATCH 23/57] fix: progress chart to show ideal line only when data is present (#1384) --- .../core/sidebar/progress-chart.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/app/components/core/sidebar/progress-chart.tsx b/apps/app/components/core/sidebar/progress-chart.tsx index 47af406cc..8c7dba11e 100644 --- a/apps/app/components/core/sidebar/progress-chart.tsx +++ b/apps/app/components/core/sidebar/progress-chart.tsx @@ -93,16 +93,19 @@ const ProgressChart: React.FC = ({ distribution, startDate, endDate, tota id: "ideal", color: "#a9bbd0", fill: "transparent", - data: [ - { - x: chartData[0].currentDate, - y: totalIssues, - }, - { - x: chartData[chartData.length - 1].currentDate, - y: 0, - }, - ], + data: + chartData.length > 0 + ? [ + { + x: chartData[0].currentDate, + y: totalIssues, + }, + { + x: chartData[chartData.length - 1].currentDate, + y: 0, + }, + ] + : [], }, ]} layers={["grid", "markers", "areas", DashedLine, "slices", "points", "axes", "legends"]} From 5ada1e539784299ed6d14e3476e8e06164ed8a0d Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Sat, 24 Jun 2023 00:35:43 +0530 Subject: [PATCH 24/57] fix: spreadsheet view bug fixes (#1383) * fix: due date sorting fix * fix: update and delete sub-issue fix --- .../core/spreadsheet-view/single-issue.tsx | 4 +-- .../spreadsheet-view/spreadsheet-issues.tsx | 4 +-- .../components/issues/delete-issue-modal.tsx | 28 +++++++++++++++---- apps/app/components/issues/modal.tsx | 1 + apps/app/constants/spreadsheet.ts | 4 +-- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/apps/app/components/core/spreadsheet-view/single-issue.tsx b/apps/app/components/core/spreadsheet-view/single-issue.tsx index f763e706c..bae89d8bb 100644 --- a/apps/app/components/core/spreadsheet-view/single-issue.tsx +++ b/apps/app/components/core/spreadsheet-view/single-issue.tsx @@ -38,7 +38,7 @@ type Props = { expanded: boolean; handleToggleExpand: (issueId: string) => void; properties: Properties; - handleEditIssue: () => void; + handleEditIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void; gridTemplateColumns: string; user: ICurrentUserResponse | undefined; @@ -245,7 +245,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ > {!isNotAllowed && ( - + handleEditIssue(issue)}>
    Edit issue diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx index 8652b3a7e..0edfbceb1 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx @@ -59,7 +59,7 @@ export const SpreadsheetIssues: React.FC = ({ handleToggleExpand={handleToggleExpand} gridTemplateColumns={gridTemplateColumns} properties={properties} - handleEditIssue={() => handleEditIssue(issue)} + handleEditIssue={handleEditIssue} handleDeleteIssue={handleDeleteIssue} user={user} userAuth={userAuth} @@ -78,7 +78,7 @@ export const SpreadsheetIssues: React.FC = ({ setExpandedIssues={setExpandedIssues} gridTemplateColumns={gridTemplateColumns} properties={properties} - handleEditIssue={() => handleEditIssue(subIssue)} + handleEditIssue={handleEditIssue} handleDeleteIssue={handleDeleteIssue} user={user} userAuth={userAuth} diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index 000b21387..6ac9282ad 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -18,12 +18,13 @@ import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; // ui import { SecondaryButton, DangerButton } from "components/ui"; // types -import type { IIssue, ICurrentUserResponse } from "types"; +import type { IIssue, ICurrentUserResponse, ISubIssueResponse } from "types"; // fetch-keys import { CYCLE_ISSUES_WITH_PARAMS, MODULE_ISSUES_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS, + SUB_ISSUES, VIEW_ISSUES, } from "constants/fetch-keys"; @@ -84,12 +85,27 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data, u : viewId ? VIEW_ISSUES(viewId.toString(), spreadsheetParams) : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", spreadsheetParams); + if (data.parent) { + mutate( + SUB_ISSUES(data.parent.toString()), + (prevData) => { + if (!prevData) return prevData; + const updatedArray = (prevData.sub_issues ?? []).filter((i) => i.id !== data.id); - mutate( - spreadsheetFetchKey, - (prevData) => (prevData ?? []).filter((p) => p.id !== data.id), - false - ); + return { + ...prevData, + sub_issues: updatedArray, + }; + }, + false + ); + } else { + mutate( + spreadsheetFetchKey, + (prevData) => (prevData ?? []).filter((p) => p.id !== data.id), + false + ); + } } else { if (cycleId) mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); else if (moduleId) mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index d88ad674d..dd5262453 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -276,6 +276,7 @@ export const CreateUpdateIssueModal: React.FC = ({ } else { if (issueView === "calendar") mutate(calendarFetchKey); if (issueView === "spreadsheet") mutate(spreadsheetFetchKey); + if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString())); mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject ?? "", params)); } diff --git a/apps/app/constants/spreadsheet.ts b/apps/app/constants/spreadsheet.ts index 8e4bdb21d..d58ebc861 100644 --- a/apps/app/constants/spreadsheet.ts +++ b/apps/app/constants/spreadsheet.ts @@ -48,8 +48,8 @@ export const SPREADSHEET_COLUMN = [ colName: "Due Date", colSize: "128px", icon: CalendarDaysIcon, - ascendingOrder: "target_date", - descendingOrder: "-target_date", + ascendingOrder: "-target_date", + descendingOrder: "target_date", }, { propertyName: "estimate", From 56a35e74bbf6782f6275baeb3fdd716a8d5e78c5 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Sat, 24 Jun 2023 01:25:18 +0530 Subject: [PATCH 25/57] fix: spreadsheet bug fixes (#1385) * fix: sub issue accordion fix * chore: assignees sort order updated --- apps/app/components/issues/delete-issue-modal.tsx | 1 + apps/app/constants/spreadsheet.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/app/components/issues/delete-issue-modal.tsx b/apps/app/components/issues/delete-issue-modal.tsx index 6ac9282ad..c4fb3b00a 100644 --- a/apps/app/components/issues/delete-issue-modal.tsx +++ b/apps/app/components/issues/delete-issue-modal.tsx @@ -99,6 +99,7 @@ export const DeleteIssueModal: React.FC = ({ isOpen, handleClose, data, u }, false ); + mutate(spreadsheetFetchKey); } else { mutate( spreadsheetFetchKey, diff --git a/apps/app/constants/spreadsheet.ts b/apps/app/constants/spreadsheet.ts index d58ebc861..b55cbdb23 100644 --- a/apps/app/constants/spreadsheet.ts +++ b/apps/app/constants/spreadsheet.ts @@ -32,8 +32,8 @@ export const SPREADSHEET_COLUMN = [ colName: "Assignees", colSize: "128px", icon: UserGroupIcon, - ascendingOrder: "assignees__name", - descendingOrder: "-assignees__name", + ascendingOrder: "assignees__first_name", + descendingOrder: "-assignees__first_name", }, { propertyName: "labels", From 7acd4ef1af709dd10384da88036077aa98f6c048 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Sat, 24 Jun 2023 17:52:12 +0530 Subject: [PATCH 26/57] fix: issue duplication when ordering by labels and assignee (#1388) --- apiserver/plane/api/views/issue.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index dba7a7a2f..36b3411fc 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -15,6 +15,7 @@ from django.db.models import ( Value, CharField, When, + Max, ) from django.core.serializers.json import DjangoJSONEncoder from django.utils.decorators import method_decorator @@ -195,7 +196,7 @@ class IssueViewSet(BaseViewSet): output_field=CharField(), ) ).order_by("priority_order") - + # State Ordering elif order_by_param in [ "state__name", @@ -218,6 +219,22 @@ class IssueViewSet(BaseViewSet): output_field=CharField(), ) ).order_by("state_order") + # assignee and label ordering + elif order_by_param in [ + "labels__name", + "-labels__name", + "assignees__first_name", + "-assignees__first_name", + ]: + issue_queryset = issue_queryset.annotate( + max_values=Max( + order_by_param[1::] + if order_by_param.startswith("-") + else order_by_param + ) + ).order_by( + "-max_values" if order_by_param.startswith("-") else "max_values" + ) else: issue_queryset = issue_queryset.order_by(order_by_param) @@ -239,7 +256,7 @@ class IssueViewSet(BaseViewSet): return Response(issues, status=status.HTTP_200_OK) except Exception as e: - print(e) + capture_exception(e) return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, From fd30ea9a20a679d9b1608b51b325b0f16705f556 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Sat, 24 Jun 2023 18:09:06 +0530 Subject: [PATCH 27/57] feat: editable label option added in all view , fix: view page list and kanban view mutation fix, chore: code refactor (#1390) * feat: editable label select component added in spreadsheet view * feat: editable label select option added in all view, chore: code refactor * fix: view page list and kanban view mutation fix and sub issue mutation, chore: refactor partial update issue function * fix: build fix --- .../core/board-view/single-issue.tsx | 139 ++++++++-------- .../core/calendar-view/single-issue.tsx | 94 ++++++----- .../core/list-view/single-issue.tsx | 113 ++++++------- .../core/spreadsheet-view/single-issue.tsx | 100 +++++++----- .../components/issues/my-issues-list-item.tsx | 6 +- .../issues/view-select/assignee.tsx | 4 +- .../issues/view-select/due-date.tsx | 4 +- .../issues/view-select/estimate.tsx | 4 +- .../components/issues/view-select/index.ts | 1 + .../components/issues/view-select/label.tsx | 148 ++++++++++++++++++ .../issues/view-select/priority.tsx | 4 +- .../components/issues/view-select/state.tsx | 4 +- 12 files changed, 390 insertions(+), 231 deletions(-) create mode 100644 apps/app/components/issues/view-select/label.tsx diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 5c0cc5102..cde1b2e38 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -23,6 +23,7 @@ import { ViewAssigneeSelect, ViewDueDateSelect, ViewEstimateSelect, + ViewLabelSelect, ViewPrioritySelect, ViewStateSelect, } from "components/issues"; @@ -44,7 +45,14 @@ import { LayerDiagonalIcon } from "components/icons"; import { handleIssuesMutation } from "constants/issue"; import { copyTextToClipboard, truncateText } from "helpers/string.helper"; // types -import { ICurrentUserResponse, IIssue, Properties, TIssueGroupByOptions, UserAuth } from "types"; +import { + ICurrentUserResponse, + IIssue, + ISubIssueResponse, + Properties, + TIssueGroupByOptions, + UserAuth, +} from "types"; // fetch-keys import { CYCLE_DETAILS, @@ -52,6 +60,8 @@ import { MODULE_DETAILS, MODULE_ISSUES_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS, + SUB_ISSUES, + VIEW_ISSUES, } from "constants/fetch-keys"; type Props = { @@ -101,86 +111,68 @@ export const SingleBoardIssue: React.FC = ({ const { orderBy, params } = useIssuesView(); const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( - (formData: Partial, issueId: string) => { + (formData: Partial, issue: IIssue) => { if (!workspaceSlug || !projectId) return; - if (cycleId) - mutate< - | { - [key: string]: IIssue[]; - } - | IIssue[] - >( - CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params), - (prevData) => - handleIssuesMutation( - formData, - groupTitle ?? "", - selectedGroup, - index, - orderBy, - prevData - ), - false - ); - else if (moduleId) - mutate< - | { - [key: string]: IIssue[]; - } - | IIssue[] - >( - MODULE_ISSUES_WITH_PARAMS(moduleId as string), - (prevData) => - handleIssuesMutation( - formData, - groupTitle ?? "", - selectedGroup, - index, - orderBy, - prevData - ), - false - ); - else { - mutate< - | { - [key: string]: IIssue[]; - } - | IIssue[] - >( - PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params), + const fetchKey = cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) + : moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) + : viewId + ? VIEW_ISSUES(viewId.toString(), params) + : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params); + + if (issue.parent) { + mutate( + SUB_ISSUES(issue.parent.toString()), (prevData) => { if (!prevData) return prevData; - return handleIssuesMutation( + return { + ...prevData, + sub_issues: (prevData.sub_issues ?? []).map((i) => { + if (i.id === issue.id) { + return { + ...i, + ...formData, + }; + } + return i; + }), + }; + }, + false + ); + } else { + mutate< + | { + [key: string]: IIssue[]; + } + | IIssue[] + >( + fetchKey, + (prevData) => + handleIssuesMutation( formData, groupTitle ?? "", selectedGroup, index, orderBy, prevData - ); - }, + ), false ); } issuesService - .patchIssue(workspaceSlug as string, projectId as string, issueId, formData, user) + .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user) .then(() => { - if (cycleId) { - mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); - mutate(CYCLE_DETAILS(cycleId as string)); - } else if (moduleId) { - mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); - mutate(MODULE_DETAILS(moduleId as string)); - } else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)); + mutate(fetchKey); }); }, [ @@ -188,6 +180,7 @@ export const SingleBoardIssue: React.FC = ({ projectId, cycleId, moduleId, + viewId, groupTitle, index, selectedGroup, @@ -370,23 +363,15 @@ export const SingleBoardIssue: React.FC = ({ isNotAllowed={isNotAllowed} /> )} - {properties.labels && issue.label_details.length > 0 && ( -
    - {issue.label_details.map((label) => ( -
    - - {label.name} -
    - ))} -
    + {properties.labels && ( + )} {properties.assignee && ( = ({ const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); const partialUpdateIssue = useCallback( - (formData: Partial, issueId: string) => { + (formData: Partial, issue: IIssue) => { if (!workspaceSlug || !projectId) return; const fetchKey = cycleId @@ -79,25 +81,54 @@ export const SingleCalendarIssue: React.FC = ({ ? VIEW_ISSUES(viewId.toString(), params) : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params); - mutate( - fetchKey, - (prevData) => - (prevData ?? []).map((p) => { - if (p.id === issueId) { - return { - ...p, - ...formData, - assignees: formData?.assignees_list ?? p.assignees, - }; - } + if (issue.parent) { + mutate( + SUB_ISSUES(issue.parent.toString()), + (prevData) => { + if (!prevData) return prevData; - return p; - }), - false - ); + return { + ...prevData, + sub_issues: (prevData.sub_issues ?? []).map((i) => { + if (i.id === issue.id) { + return { + ...i, + ...formData, + }; + } + return i; + }), + }; + }, + false + ); + } else { + mutate( + fetchKey, + (prevData) => + (prevData ?? []).map((p) => { + if (p.id === issue.id) { + return { + ...p, + ...formData, + assignees: formData?.assignees_list ?? p.assignees, + }; + } + + return p; + }), + false + ); + } issuesService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, formData, user) + .patchIssue( + workspaceSlug as string, + projectId as string, + issue.id as string, + formData, + user + ) .then(() => { mutate(fetchKey); }) @@ -207,25 +238,14 @@ export const SingleCalendarIssue: React.FC = ({ isNotAllowed={isNotAllowed} /> )} - {properties.labels && issue.label_details.length > 0 ? ( -
    - {issue.label_details.map((label) => ( - - - {label.name} - - ))} -
    - ) : ( - "" + {properties.labels && ( + )} {properties.assignee && ( = ({ const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 }); const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId } = router.query; + const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { setToastAlert } = useToast(); const { groupByProperty: selectedGroup, orderBy, params } = useIssueView(); const partialUpdateIssue = useCallback( - (formData: Partial, issueId: string) => { + (formData: Partial, issue: IIssue) => { if (!workspaceSlug || !projectId) return; - if (cycleId) + const fetchKey = cycleId + ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) + : moduleId + ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) + : viewId + ? VIEW_ISSUES(viewId.toString(), params) + : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params); + + if (issue.parent) { + mutate( + SUB_ISSUES(issue.parent.toString()), + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + sub_issues: (prevData.sub_issues ?? []).map((i) => { + if (i.id === issue.id) { + return { + ...i, + ...formData, + }; + } + return i; + }), + }; + }, + false + ); + } else { mutate< | { [key: string]: IIssue[]; } | IIssue[] >( - CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params), + fetchKey, (prevData) => handleIssuesMutation( formData, @@ -109,49 +141,12 @@ export const SingleListIssue: React.FC = ({ ), false ); - - if (moduleId) - mutate< - | { - [key: string]: IIssue[]; - } - | IIssue[] - >( - MODULE_ISSUES_WITH_PARAMS(moduleId as string, params), - (prevData) => - handleIssuesMutation( - formData, - groupTitle ?? "", - selectedGroup, - index, - orderBy, - prevData - ), - false - ); - - mutate< - | { - [key: string]: IIssue[]; - } - | IIssue[] - >( - PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params), - (prevData) => - handleIssuesMutation(formData, groupTitle ?? "", selectedGroup, index, orderBy, prevData), - false - ); + } issuesService - .patchIssue(workspaceSlug as string, projectId as string, issueId, formData, user) + .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user) .then(() => { - if (cycleId) { - mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params)); - mutate(CYCLE_DETAILS(cycleId as string)); - } else if (moduleId) { - mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params)); - mutate(MODULE_DETAILS(moduleId as string)); - } else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params)); + mutate(fetchKey); }); }, [ @@ -159,6 +154,7 @@ export const SingleListIssue: React.FC = ({ projectId, cycleId, moduleId, + viewId, groupTitle, index, selectedGroup, @@ -275,25 +271,14 @@ export const SingleListIssue: React.FC = ({ isNotAllowed={isNotAllowed} /> )} - {properties.labels && issue.label_details.length > 0 ? ( -
    - {issue.label_details.map((label) => ( - - - {label.name} - - ))} -
    - ) : ( - "" + {properties.labels && ( + )} {properties.assignee && ( = ({ const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( - (formData: Partial, issueId: string) => { + (formData: Partial, issue: IIssue) => { if (!workspaceSlug || !projectId) return; const fetchKey = cycleId @@ -78,23 +80,52 @@ export const SingleSpreadsheetIssue: React.FC = ({ ? VIEW_ISSUES(viewId.toString(), params) : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params); - mutate( - fetchKey, - (prevData) => - (prevData ?? []).map((p) => { - if (p.id === issueId) { - return { - ...p, - ...formData, - }; - } - return p; - }), - false - ); + if (issue.parent) { + mutate( + SUB_ISSUES(issue.parent.toString()), + (prevData) => { + if (!prevData) return prevData; + + return { + ...prevData, + sub_issues: (prevData.sub_issues ?? []).map((i) => { + if (i.id === issue.id) { + return { + ...i, + ...formData, + }; + } + return i; + }), + }; + }, + false + ); + } else { + mutate( + fetchKey, + (prevData) => + (prevData ?? []).map((p) => { + if (p.id === issue.id) { + return { + ...p, + ...formData, + }; + } + return p; + }), + false + ); + } issuesService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, formData, user) + .patchIssue( + workspaceSlug as string, + projectId as string, + issue.id as string, + formData, + user + ) .then(() => { mutate(fetchKey); }) @@ -191,30 +222,19 @@ export const SingleSpreadsheetIssue: React.FC = ({ />
    )} - {properties.labels ? ( - issue.label_details.length > 0 ? ( -
    - {issue.label_details.slice(0, 4).map((label, index) => ( -
    - -
    - ))} - {issue.label_details.length > 4 ? +{issue.label_details.length - 4} : null} -
    - ) : ( -
    - No Labels -
    - ) - ) : ( - "" + {properties.labels && ( +
    + +
    )} + {properties.due_date && (
    = ({ issue, properties, projectId const { setToastAlert } = useToast(); const partialUpdateIssue = useCallback( - (formData: Partial, issueId: string) => { + (formData: Partial, issue: IIssue) => { if (!workspaceSlug) return; mutate( USER_ISSUE(workspaceSlug as string), (prevData) => prevData?.map((p) => { - if (p.id === issueId) return { ...p, ...formData }; + if (p.id === issue.id) return { ...p, ...formData }; return p; }), @@ -59,7 +59,7 @@ export const MyIssuesListItem: React.FC = ({ issue, properties, projectId ); issuesService - .patchIssue(workspaceSlug as string, projectId as string, issueId, formData, user) + .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user) .then((res) => { mutate(USER_ISSUE(workspaceSlug as string)); }) diff --git a/apps/app/components/issues/view-select/assignee.tsx b/apps/app/components/issues/view-select/assignee.tsx index 1dbfbabba..8bfa797fe 100644 --- a/apps/app/components/issues/view-select/assignee.tsx +++ b/apps/app/components/issues/view-select/assignee.tsx @@ -18,7 +18,7 @@ import { PROJECT_MEMBERS } from "constants/fetch-keys"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial, issueId: string) => void; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; position?: "left" | "right"; selfPositioned?: boolean; tooltipPosition?: "left" | "right"; @@ -108,7 +108,7 @@ export const ViewAssigneeSelect: React.FC = ({ if (newData.includes(data)) newData.splice(newData.indexOf(data), 1); else newData.push(data); - partialUpdateIssue({ assignees_list: data }, issue.id); + partialUpdateIssue({ assignees_list: data }, issue); trackEventServices.trackIssuePartialPropertyUpdateEvent( { diff --git a/apps/app/components/issues/view-select/due-date.tsx b/apps/app/components/issues/view-select/due-date.tsx index f74b62689..163816a99 100644 --- a/apps/app/components/issues/view-select/due-date.tsx +++ b/apps/app/components/issues/view-select/due-date.tsx @@ -11,7 +11,7 @@ import { ICurrentUserResponse, IIssue } from "types"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial, issueId: string) => void; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; noBorder?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; @@ -48,7 +48,7 @@ export const ViewDueDateSelect: React.FC = ({ priority: issue.priority, state: issue.state, }, - issue.id + issue ); trackEventServices.trackIssuePartialPropertyUpdateEvent( { diff --git a/apps/app/components/issues/view-select/estimate.tsx b/apps/app/components/issues/view-select/estimate.tsx index 02a3e0710..0f932405f 100644 --- a/apps/app/components/issues/view-select/estimate.tsx +++ b/apps/app/components/issues/view-select/estimate.tsx @@ -15,7 +15,7 @@ import { ICurrentUserResponse, IIssue } from "types"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial, issueId: string) => void; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; position?: "left" | "right"; selfPositioned?: boolean; customButton?: boolean; @@ -54,7 +54,7 @@ export const ViewEstimateSelect: React.FC = ({ { - partialUpdateIssue({ estimate_point: val }, issue.id); + partialUpdateIssue({ estimate_point: val }, issue); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, diff --git a/apps/app/components/issues/view-select/index.ts b/apps/app/components/issues/view-select/index.ts index 55ecfcbdb..a05cf61b6 100644 --- a/apps/app/components/issues/view-select/index.ts +++ b/apps/app/components/issues/view-select/index.ts @@ -3,3 +3,4 @@ export * from "./due-date"; export * from "./estimate"; export * from "./priority"; export * from "./state"; +export * from "./label"; diff --git a/apps/app/components/issues/view-select/label.tsx b/apps/app/components/issues/view-select/label.tsx new file mode 100644 index 000000000..33df5cf9f --- /dev/null +++ b/apps/app/components/issues/view-select/label.tsx @@ -0,0 +1,148 @@ +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// services +import issuesService from "services/issues.service"; +// component +import { CreateLabelModal } from "components/labels"; +// ui +import { CustomSearchSelect, Tooltip } from "components/ui"; +// icons +import { PlusIcon, TagIcon } from "@heroicons/react/24/outline"; +// types +import { ICurrentUserResponse, IIssue, IIssueLabels } from "types"; +// fetch-keys +import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; + +type Props = { + issue: IIssue; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + position?: "left" | "right"; + selfPositioned?: boolean; + tooltipPosition?: "left" | "right"; + customButton?: boolean; + user: ICurrentUserResponse | undefined; + isNotAllowed: boolean; +}; + +export const ViewLabelSelect: React.FC = ({ + issue, + partialUpdateIssue, + position = "left", + selfPositioned = false, + tooltipPosition = "right", + user, + isNotAllowed, + customButton = false, +}) => { + const [labelModal, setLabelModal] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { data: issueLabels } = useSWR( + projectId ? PROJECT_ISSUE_LABELS(projectId.toString()) : null, + workspaceSlug && projectId + ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) + : null + ); + + const options = issueLabels?.map((label) => ({ + value: label.id, + query: label.name, + content: ( +
    + + {label.name} +
    + ), + })); + + const labelsLabel = ( + 0 + ? issue.label_details.map((label) => label.name ?? "").join(", ") + : "No Label" + } + > +
    + {issue.label_details.length > 0 ? ( + <> + {issue.label_details.slice(0, 4).map((label, index) => ( +
    + +
    + ))} + {issue.label_details.length > 4 ? +{issue.label_details.length - 4} : null} + + ) : ( + <> + + + )} +
    +
    + ); + + const footerOption = ( + + ); + + return ( + <> + {projectId && ( + setLabelModal(false)} + projectId={projectId.toString()} + user={user} + /> + )} + { + partialUpdateIssue({ labels_list: data }, issue); + }} + options={options} + {...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })} + multiple + noChevron + position={position} + disabled={isNotAllowed} + selfPositioned={selfPositioned} + footerOption={footerOption} + dropdownWidth="w-full min-w-[12rem]" + /> + + ); +}; diff --git a/apps/app/components/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index 499546931..0afd2dd98 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -17,7 +17,7 @@ import { capitalizeFirstLetter } from "helpers/string.helper"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial, issueId: string) => void; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; position?: "left" | "right"; selfPositioned?: boolean; noBorder?: boolean; @@ -41,7 +41,7 @@ export const ViewPrioritySelect: React.FC = ({ { - partialUpdateIssue({ priority: data }, issue.id); + partialUpdateIssue({ priority: data }, issue); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug, diff --git a/apps/app/components/issues/view-select/state.tsx b/apps/app/components/issues/view-select/state.tsx index c097c7326..5ec0f71c7 100644 --- a/apps/app/components/issues/view-select/state.tsx +++ b/apps/app/components/issues/view-select/state.tsx @@ -19,7 +19,7 @@ import { STATES_LIST } from "constants/fetch-keys"; type Props = { issue: IIssue; - partialUpdateIssue: (formData: Partial, issueId: string) => void; + partialUpdateIssue: (formData: Partial, issue: IIssue) => void; position?: "left" | "right"; selfPositioned?: boolean; customButton?: boolean; @@ -83,7 +83,7 @@ export const ViewStateSelect: React.FC = ({ priority: issue.priority, target_date: issue.target_date, }, - issue.id + issue ); trackEventServices.trackIssuePartialPropertyUpdateEvent( { From 06ce89a03ab5f98623659fef4ab4086788129926 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Sat, 24 Jun 2023 18:12:11 +0530 Subject: [PATCH 28/57] fix: sidebar toggle button on workspace dashboard (#1389) --- apps/app/layouts/app-layout/app-header.tsx | 9 +++++++-- .../auth-layout/project-authorization-wrapper.tsx | 15 +++++++-------- .../workspace-authorization-wrapper.tsx | 15 +++++++-------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/apps/app/layouts/app-layout/app-header.tsx b/apps/app/layouts/app-layout/app-header.tsx index 95f998dec..1f99e044e 100644 --- a/apps/app/layouts/app-layout/app-header.tsx +++ b/apps/app/layouts/app-layout/app-header.tsx @@ -6,10 +6,15 @@ type Props = { left?: JSX.Element; right?: JSX.Element; setToggleSidebar: React.Dispatch>; + noHeader: boolean; }; -const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar }) => ( -
    +const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => ( +
    + +
    ) : ( diff --git a/apps/app/components/ui/multi-level-dropdown.tsx b/apps/app/components/ui/multi-level-dropdown.tsx index b0997972f..ee096774f 100644 --- a/apps/app/components/ui/multi-level-dropdown.tsx +++ b/apps/app/components/ui/multi-level-dropdown.tsx @@ -3,7 +3,7 @@ import { Fragment, useState } from "react"; // headless ui import { Menu, Transition } from "@headlessui/react"; // icons -import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/20/solid"; type MultiLevelDropdownProps = { @@ -127,9 +127,14 @@ export const MultiLevelDropdown: React.FC = ({ }} className={`${ child.selected ? "bg-brand-surface-2" : "" - } flex w-full items-center whitespace-nowrap break-words rounded px-1 py-1.5 text-left capitalize text-brand-secondary hover:bg-brand-surface-2`} + } flex w-full items-center justify-between whitespace-nowrap break-words rounded px-1 py-1.5 text-left capitalize text-brand-secondary hover:bg-brand-surface-2`} > {child.label} + ))}
    From c70fe91886682f04dcbe0e5b4fc6481f9e23616f Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:25:19 +0530 Subject: [PATCH 30/57] fix: create issue modal not working on workspace level (#1393) --- apps/app/components/issues/form.tsx | 8 ------- apps/app/components/issues/modal.tsx | 32 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index e7be252b4..8bf64e6ff 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -23,7 +23,6 @@ import { IssueStateSelect, } from "components/issues/select"; import { CreateStateModal } from "components/states"; -import { CreateUpdateCycleModal } from "components/cycles"; import { CreateLabelModal } from "components/labels"; // ui import { @@ -73,7 +72,6 @@ const defaultValues: Partial = { description_html: "

    ", estimate_point: null, state: "", - cycle: null, priority: null, assignees: [], assignees_list: [], @@ -122,7 +120,6 @@ export const IssueForm: FC = ({ }) => { // states const [mostSimilarIssue, setMostSimilarIssue] = useState(); - const [cycleModal, setCycleModal] = useState(false); const [stateModal, setStateModal] = useState(false); const [labelModal, setLabelModal] = useState(false); const [parentIssueListModalOpen, setParentIssueListModalOpen] = useState(false); @@ -252,11 +249,6 @@ export const IssueForm: FC = ({ projectId={projectId} user={user} /> - setCycleModal(false)} - user={user} - /> setLabelModal(false)} diff --git a/apps/app/components/issues/modal.tsx b/apps/app/components/issues/modal.tsx index dd5262453..b83bbc480 100644 --- a/apps/app/components/issues/modal.tsx +++ b/apps/app/components/issues/modal.tsx @@ -82,12 +82,17 @@ export const CreateUpdateIssueModal: React.FC = ({ const { params: inboxParams } = useInboxView(); const { params: spreadsheetParams } = useSpreadsheetIssuesView(); - if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string }; - if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string }; - const { user } = useUser(); const { setToastAlert } = useToast(); + if (cycleId) prePopulateData = { ...prePopulateData, cycle: cycleId as string }; + if (moduleId) prePopulateData = { ...prePopulateData, module: moduleId as string }; + if (router.asPath.includes("my-issues")) + prePopulateData = { + ...prePopulateData, + assignees: [...(prePopulateData?.assignees ?? []), user?.id ?? ""], + }; + const { data: issues } = useSWR( workspaceSlug && activeProject ? PROJECT_ISSUES_LIST(workspaceSlug as string, activeProject ?? "") @@ -121,7 +126,7 @@ export const CreateUpdateIssueModal: React.FC = ({ }, [handleClose]); const addIssueToCycle = async (issueId: string, cycleId: string) => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !activeProject) return; await issuesService .addIssueToCycle( @@ -142,7 +147,7 @@ export const CreateUpdateIssueModal: React.FC = ({ }; const addIssueToModule = async (issueId: string, moduleId: string) => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !activeProject) return; await modulesService .addIssuesToModule( @@ -163,7 +168,7 @@ export const CreateUpdateIssueModal: React.FC = ({ }; const addIssueToInbox = async (formData: Partial) => { - if (!workspaceSlug || !projectId || !inboxId) return; + if (!workspaceSlug || !activeProject || !inboxId) return; const payload = { issue: { @@ -178,7 +183,7 @@ export const CreateUpdateIssueModal: React.FC = ({ await inboxServices .createInboxIssue( workspaceSlug.toString(), - projectId.toString(), + activeProject.toString(), inboxId.toString(), payload, user @@ -191,7 +196,7 @@ export const CreateUpdateIssueModal: React.FC = ({ }); router.push( - `/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}` + `/${workspaceSlug}/projects/${activeProject}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}` ); mutate(INBOX_ISSUES(inboxId.toString(), inboxParams)); @@ -211,7 +216,7 @@ export const CreateUpdateIssueModal: React.FC = ({ ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams) : viewId ? VIEW_ISSUES(viewId.toString(), calendarParams) - : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", calendarParams); + : PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "", calendarParams); const spreadsheetFetchKey = cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams) @@ -219,7 +224,7 @@ export const CreateUpdateIssueModal: React.FC = ({ ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), spreadsheetParams) : viewId ? VIEW_ISSUES(viewId.toString(), spreadsheetParams) - : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", spreadsheetParams); + : PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? "", spreadsheetParams); const ganttFetchKey = cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString()) @@ -227,10 +232,10 @@ export const CreateUpdateIssueModal: React.FC = ({ ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString()) : viewId ? VIEW_ISSUES(viewId.toString(), viewGanttParams) - : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? ""); + : PROJECT_ISSUES_LIST_WITH_PARAMS(activeProject?.toString() ?? ""); const createIssue = async (payload: Partial) => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !activeProject) return; if (inboxId) await addIssueToInbox(payload); else @@ -252,7 +257,8 @@ export const CreateUpdateIssueModal: React.FC = ({ message: "Issue created successfully.", }); - if (payload.assignees_list?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE); + 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)); }) From c89592d5872dbde6d0dd7c7c3335a011db896af5 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 26 Jun 2023 14:26:38 +0530 Subject: [PATCH 31/57] chore: add tooltip to line graphs (#1392) * chore: workspace dashboard line graph tooltip * chore: burndown chart and analytics line graph tooltip added --- .../analytics/scope-and-demand/year-wise-issues.tsx | 8 ++++++++ apps/app/components/core/sidebar/progress-chart.tsx | 8 ++++++++ apps/app/components/workspace/completed-issues-graph.tsx | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/apps/app/components/analytics/scope-and-demand/year-wise-issues.tsx b/apps/app/components/analytics/scope-and-demand/year-wise-issues.tsx index 863eb664b..a37518cba 100644 --- a/apps/app/components/analytics/scope-and-demand/year-wise-issues.tsx +++ b/apps/app/components/analytics/scope-and-demand/year-wise-issues.tsx @@ -41,6 +41,14 @@ export const AnalyticsYearWiseIssues: React.FC = ({ defaultAnalytics }) = colors={(datum) => datum.color} curve="monotoneX" margin={{ top: 20 }} + enableSlices="x" + sliceTooltip={(datum) => ( +
    + {datum.slice.points[0].data.yFormatted} + issues closed in + {datum.slice.points[0].data.xFormatted} +
    + )} theme={{ background: "rgb(var(--color-bg-base))", }} diff --git a/apps/app/components/core/sidebar/progress-chart.tsx b/apps/app/components/core/sidebar/progress-chart.tsx index 8c7dba11e..e4a1ce4fb 100644 --- a/apps/app/components/core/sidebar/progress-chart.tsx +++ b/apps/app/components/core/sidebar/progress-chart.tsx @@ -117,6 +117,14 @@ const ProgressChart: React.FC = ({ distribution, startDate, endDate, tota colors={(datum) => datum.color ?? "#3F76FF"} customYAxisTickValues={[0, totalIssues]} gridXValues={chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : ""))} + enableSlices="x" + sliceTooltip={(datum) => ( +
    + {datum.slice.points[0].data.yFormatted} + issues pending on + {datum.slice.points[0].data.xFormatted} +
    + )} theme={{ background: "transparent", axis: { diff --git a/apps/app/components/workspace/completed-issues-graph.tsx b/apps/app/components/workspace/completed-issues-graph.tsx index 9a1ada618..4c7e1edb6 100644 --- a/apps/app/components/workspace/completed-issues-graph.tsx +++ b/apps/app/components/workspace/completed-issues-graph.tsx @@ -60,6 +60,14 @@ export const CompletedIssuesGraph: React.FC = ({ month, issues, setMonth margin={{ top: 20, right: 20, bottom: 20, left: 20 }} customYAxisTickValues={data.map((item) => item.completed_count)} colors={(datum) => datum.color} + enableSlices="x" + sliceTooltip={(datum) => ( +
    + {datum.slice.points[0].data.yFormatted} + issues closed in + {datum.slice.points[0].data.xFormatted} +
    + )} theme={{ background: "rgb(var(--color-bg-base))", }} From ae051b28af55e1cc0396fe2a9e069b978166db77 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:59:17 +0530 Subject: [PATCH 32/57] fix: priority filtering (#1398) --- apiserver/plane/utils/issue_filters.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index f348f642a..74acb2044 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -1,7 +1,6 @@ from django.utils.timezone import make_aware from django.utils.dateparse import parse_datetime - def filter_state(params, filter, method): if method == "GET": states = params.get("state").split(",") @@ -26,12 +25,27 @@ def filter_estimate_point(params, filter, method): def filter_priority(params, filter, method): if method == "GET": - priorties = params.get("priority").split(",") - if len(priorties) and "" not in priorties: - filter["priority__in"] = priorties + priorities = params.get("priority").split(",") + if len(priorities) and "" not in priorities: + if len(priorities) == 1 and "null" in priorities: + filter["priority__isnull"] = True + elif len(priorities) > 1 and "null" in priorities: + filter["priority__isnull"] = True + filter["priority__in"] = [p for p in priorities if p != "null"] + else: + filter["priority__in"] = [p for p in priorities if p != "null"] + else: if params.get("priority", None) and len(params.get("priority")): - filter["priority__in"] = params.get("priority") + priorities = params.get("priority") + if len(priorities) == 1 and "null" in priorities: + filter["priority__isnull"] = True + elif len(priorities) > 1 and "null" in priorities: + filter["priority__isnull"] = True + filter["priority__in"] = [p for p in priorities if p != "null"] + else: + filter["priority__in"] = [p for p in priorities if p != "null"] + return filter From ad3411284bfa30c3bac8b484558c3beb04600f35 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 26 Jun 2023 18:01:28 +0530 Subject: [PATCH 33/57] feat: add existing issue option added in spreadsheet view (#1397) --- apps/app/components/core/issues-view.tsx | 3 + .../spreadsheet-view/spreadsheet-view.tsx | 62 +++++++++++++++---- apps/app/components/ui/custom-menu.tsx | 4 +- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/apps/app/components/core/issues-view.tsx b/apps/app/components/core/issues-view.tsx index ffd49e506..7a8c8960f 100644 --- a/apps/app/components/core/issues-view.tsx +++ b/apps/app/components/core/issues-view.tsx @@ -572,8 +572,11 @@ export const IssuesView: React.FC = ({ /> ) : issueView === "spreadsheet" ? ( diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx index 5a9d2aad5..eeef42750 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx @@ -5,7 +5,7 @@ import { useRouter } from "next/router"; // components import { SpreadsheetColumns, SpreadsheetIssues } from "components/core"; -import { Icon, Spinner } from "components/ui"; +import { CustomMenu, Icon, Spinner } from "components/ui"; // hooks import useIssuesProperties from "hooks/use-issue-properties"; import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; @@ -17,15 +17,21 @@ import { SPREADSHEET_COLUMN } from "constants/spreadsheet"; import { PlusIcon } from "@heroicons/react/24/outline"; type Props = { + type: "issue" | "cycle" | "module"; handleEditIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void; + openIssuesListModal?: (() => void) | null; + isCompleted?: boolean; user: ICurrentUserResponse | undefined; userAuth: UserAuth; }; export const SpreadsheetView: React.FC = ({ + type, handleEditIssue, handleDeleteIssue, + openIssuesListModal, + isCompleted = false, user, userAuth, }) => { @@ -79,16 +85,50 @@ export const SpreadsheetView: React.FC = ({ className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max" style={{ gridTemplateColumns }} > - + {type === "issue" ? ( + + ) : ( + !isCompleted && ( + + + Add Issue + + } + position="left" + menuItemsClassName="left-5 !w-36" + noBorder + > + { + const e = new KeyboardEvent("keydown", { key: "c" }); + document.dispatchEvent(e); + }} + > + Create new + + {openIssuesListModal && ( + + Add an existing issue + + )} + + ) + )}
    ) : ( diff --git a/apps/app/components/ui/custom-menu.tsx b/apps/app/components/ui/custom-menu.tsx index 006802b79..958453b28 100644 --- a/apps/app/components/ui/custom-menu.tsx +++ b/apps/app/components/ui/custom-menu.tsx @@ -19,6 +19,7 @@ type Props = { noChevron?: boolean; position?: "left" | "right"; verticalPosition?: "top" | "bottom"; + menuItemsClassName?: string; customButton?: JSX.Element; menuItemsWhiteBg?: boolean; }; @@ -44,6 +45,7 @@ const CustomMenu = ({ noChevron = false, position = "right", verticalPosition = "bottom", + menuItemsClassName = "", customButton, menuItemsWhiteBg = false, }: Props) => ( @@ -133,7 +135,7 @@ const CustomMenu = ({ menuItemsWhiteBg ? "border-brand-surface-1 bg-brand-base" : "border-brand-base bg-brand-surface-1" - }`} + } ${menuItemsClassName}`} >
    {children}
    From 3f22ccc52857971ea5a66a6ca57ac936725ccb9f Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 26 Jun 2023 18:08:49 +0530 Subject: [PATCH 34/57] fix: filter none updated (#1399) --- apps/app/components/views/select-filters.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/components/views/select-filters.tsx b/apps/app/components/views/select-filters.tsx index 3351be667..164c4f58c 100644 --- a/apps/app/components/views/select-filters.tsx +++ b/apps/app/components/views/select-filters.tsx @@ -70,7 +70,7 @@ export const SelectFilters: React.FC = ({ value: PRIORITIES, children: [ ...PRIORITIES.map((priority) => ({ - id: priority ?? "none", + id: priority === null ? "null" : priority, label: (
    {getPriorityIcon(priority)} {priority ?? "None"} @@ -78,9 +78,9 @@ export const SelectFilters: React.FC = ({ ), value: { key: "priority", - value: priority, + value: priority === null ? "null" : priority, }, - selected: filters?.priority?.includes(priority ?? "none"), + selected: filters?.priority?.includes(priority === null ? "null" : priority), })), ], }, From 5914240290577998320d88d134c1834a191e6ee1 Mon Sep 17 00:00:00 2001 From: "M. Palanikannan" <73993394+Palanikannan1437@users.noreply.github.com> Date: Mon, 26 Jun 2023 18:28:12 +0530 Subject: [PATCH 35/57] fix: cannot change the state if it's the only state in group (#1358) * fixed loohole with groups and added tooltip * muted text when dropdown disabled --- .../states/create-update-state-inline.tsx | 46 +++++++++++-------- apps/app/components/ui/custom-select.tsx | 2 +- .../projects/[projectId]/settings/states.tsx | 2 + 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/apps/app/components/states/create-update-state-inline.tsx b/apps/app/components/states/create-update-state-inline.tsx index 8a9d81968..94062878f 100644 --- a/apps/app/components/states/create-update-state-inline.tsx +++ b/apps/app/components/states/create-update-state-inline.tsx @@ -15,7 +15,7 @@ import stateService from "services/state.service"; // hooks import useToast from "hooks/use-toast"; // ui -import { CustomSelect, Input, PrimaryButton, SecondaryButton } from "components/ui"; +import { CustomSelect, Input, PrimaryButton, SecondaryButton, Tooltip } from "components/ui"; // types import type { ICurrentUserResponse, IState, IStateResponse } from "types"; // fetch-keys @@ -28,6 +28,7 @@ type Props = { onClose: () => void; selectedGroup: StateGroup | null; user: ICurrentUserResponse | undefined; + groupLength: number; }; export type StateGroup = "backlog" | "unstarted" | "started" | "completed" | "cancelled" | null; @@ -43,6 +44,7 @@ export const CreateUpdateStateInline: React.FC = ({ onClose, selectedGroup, user, + groupLength, }) => { const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -174,9 +176,8 @@ export const CreateUpdateStateInline: React.FC = ({ {({ open }) => ( <> {watch("color") && watch("color") !== "" && ( = ({ name="group" control={control} render={({ field: { value, onChange } }) => ( - k === value.toString()) - ? GROUP_CHOICES[value.toString() as keyof typeof GROUP_CHOICES] - : "Select group" - } - input - > - {Object.keys(GROUP_CHOICES).map((key) => ( - - {GROUP_CHOICES[key as keyof typeof GROUP_CHOICES]} - - ))} - + +
    + k === value.toString()) + ? GROUP_CHOICES[value.toString() as keyof typeof GROUP_CHOICES] + : "Select group" + } + input + > + {Object.keys(GROUP_CHOICES).map((key) => ( + + {GROUP_CHOICES[key as keyof typeof GROUP_CHOICES]} + + ))} + +
    +
    )} /> )} diff --git a/apps/app/components/ui/custom-select.tsx b/apps/app/components/ui/custom-select.tsx index 86196a63e..ec55bf29a 100644 --- a/apps/app/components/ui/custom-select.tsx +++ b/apps/app/components/ui/custom-select.tsx @@ -54,7 +54,7 @@ const CustomSelect = ({ ) : ( {
    {key === activeGroup && ( { setActiveGroup(null); setSelectedState(null); @@ -128,6 +129,7 @@ const StatesSettings: NextPage = () => { setActiveGroup(null); setSelectedState(null); }} + groupLength={orderedStateGroups[key].length} data={ statesList?.find((state) => state.id === selectedState) ?? null } From b01afdf3006ba7700b1954f90136b69a09a74b2c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Tue, 27 Jun 2023 15:49:19 +0530 Subject: [PATCH 36/57] fix: spreadsheet quick action menu fix (#1404) --- .../core/spreadsheet-view/single-issue.tsx | 116 ++++++++++++------ .../spreadsheet-view/spreadsheet-columns.tsx | 2 +- .../spreadsheet-view/spreadsheet-view.tsx | 2 +- apps/app/styles/globals.css | 5 + 4 files changed, 84 insertions(+), 41 deletions(-) diff --git a/apps/app/components/core/spreadsheet-view/single-issue.tsx b/apps/app/components/core/spreadsheet-view/single-issue.tsx index 8b66f90b7..2aabbef93 100644 --- a/apps/app/components/core/spreadsheet-view/single-issue.tsx +++ b/apps/app/components/core/spreadsheet-view/single-issue.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -14,9 +14,15 @@ import { ViewPrioritySelect, ViewStateSelect, } from "components/issues"; +import { Popover2 } from "@blueprintjs/popover2"; // icons -import { CustomMenu, Icon } from "components/ui"; -import { LinkIcon, PencilIcon, TrashIcon, XMarkIcon } from "@heroicons/react/24/outline"; +import { Icon } from "components/ui"; +import { + EllipsisHorizontalIcon, + LinkIcon, + PencilIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; // hooks import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; import useToast from "hooks/use-toast"; @@ -60,6 +66,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ userAuth, nestingLevel, }) => { + const [isOpen, setIsOpen] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; @@ -163,27 +170,87 @@ export const SingleSpreadsheetIssue: React.FC = ({ className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max" style={{ gridTemplateColumns }} > -
    - -
    +
    +
    +
    {properties.key && ( - + {issue.project_detail?.identifier}-{issue.sequence_id} )} + {!isNotAllowed && ( +
    + setIsOpen(nextOpenState)} + content={ +
    + + + + + +
    + } + placement="bottom-start" + > + +
    +
    + )}
    -
    +
    {issue.sub_issues_count > 0 && ( )}
    - +
    + {issue.name} @@ -261,35 +328,6 @@ export const SingleSpreadsheetIssue: React.FC = ({ />
    )} -
    - {!isNotAllowed && ( - - handleEditIssue(issue)}> -
    - - Edit issue -
    -
    - handleDeleteIssue(issue)}> -
    - - Delete issue -
    -
    - -
    - - Copy issue link -
    -
    -
    - )} -
    ); }; diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx index 85d05a288..a0f404fba 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-columns.tsx @@ -40,7 +40,7 @@ export const SpreadsheetColumns: React.FC = ({ columnData, gridTemplateCo return (
    {col.propertyName === "title" ? ( diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx index eeef42750..5655d11de 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx @@ -62,7 +62,7 @@ export const SpreadsheetView: React.FC = ({ return (
    -
    +
    {spreadsheetIssues ? ( diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index 22d060c86..2e997caea 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -225,3 +225,8 @@ body { -webkit-box-orient: vertical; -webkit-line-clamp: 1; } + +/* popover2 styling */ +.bp4-popover2-transition-container { + z-index: 20 !important; +} From f3be2faa8f38b9b36c0c3ba09efcc5475c37e535 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Tue, 27 Jun 2023 17:12:21 +0530 Subject: [PATCH 37/57] fix: resolve z-index issue with quick action menu and disable it for completed cycles (#1405) --- apps/app/components/core/spreadsheet-view/single-issue.tsx | 6 ++++-- .../components/core/spreadsheet-view/spreadsheet-issues.tsx | 4 ++++ .../components/core/spreadsheet-view/spreadsheet-view.tsx | 3 ++- apps/app/layouts/app-layout/app-header.tsx | 2 +- apps/app/styles/globals.css | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/app/components/core/spreadsheet-view/single-issue.tsx b/apps/app/components/core/spreadsheet-view/single-issue.tsx index 2aabbef93..762de2453 100644 --- a/apps/app/components/core/spreadsheet-view/single-issue.tsx +++ b/apps/app/components/core/spreadsheet-view/single-issue.tsx @@ -49,6 +49,7 @@ type Props = { handleEditIssue: (issue: IIssue) => void; handleDeleteIssue: (issue: IIssue) => void; gridTemplateColumns: string; + isCompleted?: boolean; user: ICurrentUserResponse | undefined; userAuth: UserAuth; nestingLevel: number; @@ -62,6 +63,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ handleEditIssue, handleDeleteIssue, gridTemplateColumns, + isCompleted = false, user, userAuth, nestingLevel, @@ -170,7 +172,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ className="relative group grid auto-rows-[minmax(44px,1fr)] hover:rounded-sm hover:bg-brand-surface-2 border-b border-brand-base w-full min-w-max" style={{ gridTemplateColumns }} > -
    +
    {properties.key && ( @@ -178,7 +180,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ {issue.project_detail?.identifier}-{issue.sequence_id} )} - {!isNotAllowed && ( + {!isNotAllowed && !isCompleted && (
    void; handleDeleteIssue: (issue: IIssue) => void; gridTemplateColumns: string; + isCompleted?: boolean; user: ICurrentUserResponse | undefined; userAuth: UserAuth; nestingLevel?: number; @@ -30,6 +31,7 @@ export const SpreadsheetIssues: React.FC = ({ properties, handleEditIssue, handleDeleteIssue, + isCompleted = false, user, userAuth, nestingLevel = 0, @@ -61,6 +63,7 @@ export const SpreadsheetIssues: React.FC = ({ properties={properties} handleEditIssue={handleEditIssue} handleDeleteIssue={handleDeleteIssue} + isCompleted={isCompleted} user={user} userAuth={userAuth} nestingLevel={nestingLevel} @@ -80,6 +83,7 @@ export const SpreadsheetIssues: React.FC = ({ properties={properties} handleEditIssue={handleEditIssue} handleDeleteIssue={handleDeleteIssue} + isCompleted={isCompleted} user={user} userAuth={userAuth} nestingLevel={nestingLevel + 1} diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx index 5655d11de..15034a633 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-view.tsx @@ -62,7 +62,7 @@ export const SpreadsheetView: React.FC = ({ return (
    -
    +
    {spreadsheetIssues ? ( @@ -77,6 +77,7 @@ export const SpreadsheetView: React.FC = ({ properties={properties} handleEditIssue={handleEditIssue} handleDeleteIssue={handleDeleteIssue} + isCompleted={isCompleted} user={user} userAuth={userAuth} /> diff --git a/apps/app/layouts/app-layout/app-header.tsx b/apps/app/layouts/app-layout/app-header.tsx index 1f99e044e..ea7d5558c 100644 --- a/apps/app/layouts/app-layout/app-header.tsx +++ b/apps/app/layouts/app-layout/app-header.tsx @@ -11,7 +11,7 @@ type Props = { const Header: React.FC = ({ breadcrumbs, left, right, setToggleSidebar, noHeader }) => (
    diff --git a/apps/app/styles/globals.css b/apps/app/styles/globals.css index 2e997caea..ccfda2248 100644 --- a/apps/app/styles/globals.css +++ b/apps/app/styles/globals.css @@ -228,5 +228,5 @@ body { /* popover2 styling */ .bp4-popover2-transition-container { - z-index: 20 !important; + z-index: 1 !important; } From b0cdcfd91eecea273265787cd0e0c57241f98198 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 27 Jun 2023 18:01:42 +0530 Subject: [PATCH 38/57] fixL inbox status colors (#1408) --- .../components/inbox/inbox-main-content.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/app/components/inbox/inbox-main-content.tsx b/apps/app/components/inbox/inbox-main-content.tsx index a75c00317..a05239a95 100644 --- a/apps/app/components/inbox/inbox-main-content.tsx +++ b/apps/app/components/inbox/inbox-main-content.tsx @@ -38,8 +38,6 @@ import { renderShortNumericDateFormat } from "helpers/date-time.helper"; import type { IInboxIssue, IIssue } from "types"; // fetch-keys import { INBOX_ISSUES, INBOX_ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; -// constants -import { INBOX_STATUS } from "constants/inbox"; const defaultValues = { name: "", @@ -192,7 +190,6 @@ export const InboxMainContent: React.FC = () => { }, [issueDetails, reset, inboxIssueId]); const issueStatus = issueDetails?.issue_inbox[0].status; - const inboxStatusDetails = INBOX_STATUS.find((s) => s.value === issueStatus); if (!inboxIssueId) return ( @@ -224,11 +221,18 @@ export const InboxMainContent: React.FC = () => {
    From 7f1def2041a3bb3cf6992811318019eeab1433f8 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 27 Jun 2023 18:01:57 +0530 Subject: [PATCH 39/57] fix: priority none filter (#1407) --- apps/app/components/core/filters-list.tsx | 2 +- apps/app/components/inbox/filters-dropdown.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/app/components/core/filters-list.tsx b/apps/app/components/core/filters-list.tsx index 70f980aae..e080c8e9a 100644 --- a/apps/app/components/core/filters-list.tsx +++ b/apps/app/components/core/filters-list.tsx @@ -135,7 +135,7 @@ export const FilterList: React.FC = ({ filters, setFilters }) => { }`} > {getPriorityIcon(priority)} - {priority ? priority : "None"} + {priority === "null" ? "None" : priority} diff --git a/apps/app/components/inbox/filters-dropdown.tsx b/apps/app/components/inbox/filters-dropdown.tsx index 1b6af608c..c12fcb8d2 100644 --- a/apps/app/components/inbox/filters-dropdown.tsx +++ b/apps/app/components/inbox/filters-dropdown.tsx @@ -39,7 +39,7 @@ export const FiltersDropdown: React.FC = () => { value: PRIORITIES, children: [ ...PRIORITIES.map((priority) => ({ - id: priority ?? "none", + id: priority === null ? "null" : priority, label: (
    {getPriorityIcon(priority)} {priority ?? "None"} @@ -47,9 +47,9 @@ export const FiltersDropdown: React.FC = () => { ), value: { key: "priority", - value: priority, + value: priority === null ? "null" : priority, }, - selected: filters?.priority?.includes(priority ?? "none"), + selected: filters?.priority?.includes(priority === null ? "null" : priority), })), ], }, From 7fbf0e635833b38b7dcbe149f36151ac1c110616 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 27 Jun 2023 18:08:37 +0530 Subject: [PATCH 40/57] fix: progress chart x-axis values (#1409) * fix: x-axis dates value in the progress chart * chore: loader for active cycle --- apps/app/components/core/sidebar/progress-chart.tsx | 2 +- apps/app/components/cycles/active-cycle-details.tsx | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/app/components/core/sidebar/progress-chart.tsx b/apps/app/components/core/sidebar/progress-chart.tsx index e4a1ce4fb..5fa25e863 100644 --- a/apps/app/components/core/sidebar/progress-chart.tsx +++ b/apps/app/components/core/sidebar/progress-chart.tsx @@ -52,7 +52,7 @@ const ProgressChart: React.FC = ({ distribution, startDate, endDate, tota const maxDates = 4; const totalDates = dates.length; - if (totalDates <= maxDates) return dates; + if (totalDates <= maxDates) return dates.map((d) => renderShortNumericDateFormat(d)); else { const interval = Math.ceil(totalDates / maxDates); const limitedDates = []; diff --git a/apps/app/components/cycles/active-cycle-details.tsx b/apps/app/components/cycles/active-cycle-details.tsx index b15dbd3ca..e7a1990e9 100644 --- a/apps/app/components/cycles/active-cycle-details.tsx +++ b/apps/app/components/cycles/active-cycle-details.tsx @@ -101,6 +101,13 @@ export const ActiveCycleDetails: React.FC = () => { : null ) as { data: IIssue[] | undefined }; + if (!currentCycle) + return ( + + + + ); + if (!cycle) return (
    From df0b7d78e6da98caae98e00ed1f8efaf2155485e Mon Sep 17 00:00:00 2001 From: Rhea Jain <65884341+rhea0110@users.noreply.github.com> Date: Tue, 27 Jun 2023 18:57:28 +0530 Subject: [PATCH 41/57] Update README.md (#1411) Edited the security email. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf0af7fe2..7e9422f18 100644 --- a/README.md +++ b/README.md @@ -165,4 +165,4 @@ Our [Code of Conduct](https://github.com/makeplane/plane/blob/master/CODE_OF_CON ## ⛓️ Security -If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email security@plane.so to disclose any security vulnerabilities. +If you believe you have found a security vulnerability in Plane, we encourage you to responsibly disclose this and not open a public issue. We will investigate all legitimate reports. Email engineering@plane.so to disclose any security vulnerabilities. From 0bdd26ce12d750f321bbb0eee5dba768672f0ea7 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 27 Jun 2023 22:54:52 +0530 Subject: [PATCH 42/57] fix: github importer issue (#1414) --- apiserver/plane/api/views/importer.py | 24 +++++++++++++++++--- apiserver/plane/utils/integrations/github.py | 2 +- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/api/views/importer.py b/apiserver/plane/api/views/importer.py index 28d490740..63e2d38a1 100644 --- a/apiserver/plane/api/views/importer.py +++ b/apiserver/plane/api/views/importer.py @@ -42,16 +42,34 @@ from plane.utils.html_processor import strip_tags class ServiceIssueImportSummaryEndpoint(BaseAPIView): + def get(self, request, slug, service): try: if service == "github": + owner = request.GET.get("owner", False) + repo = request.GET.get("repo", False) + + if not owner or not repo: + return Response( + {"error": "Owner and repo are required"}, + status=status.HTTP_400_BAD_REQUEST, + ) + workspace_integration = WorkspaceIntegration.objects.get( integration__provider="github", workspace__slug=slug ) - access_tokens_url = workspace_integration.metadata["access_tokens_url"] - owner = request.GET.get("owner") - repo = request.GET.get("repo") + access_tokens_url = workspace_integration.metadata.get( + "access_tokens_url", False + ) + + if not access_tokens_url: + return Response( + { + "error": "There was an error during the installation of the GitHub app. To resolve this issue, we recommend reinstalling the GitHub app." + }, + status=status.HTTP_400_BAD_REQUEST, + ) issue_count, labels, collaborators = get_github_repo_details( access_tokens_url, owner, repo diff --git a/apiserver/plane/utils/integrations/github.py b/apiserver/plane/utils/integrations/github.py index d9aecece1..45cb5925a 100644 --- a/apiserver/plane/utils/integrations/github.py +++ b/apiserver/plane/utils/integrations/github.py @@ -113,7 +113,7 @@ def get_github_repo_details(access_tokens_url, owner, repo): last_url = labels_response.links.get("last").get("url") parsed_url = urlparse(last_url) last_page_value = parse_qs(parsed_url.query)["page"][0] - total_labels = total_labels + 100 * (last_page_value - 1) + total_labels = total_labels + 100 * (int(last_page_value) - 1) # Get labels in last page last_page_labels = requests.get(last_url, headers=headers).json() From ed0106200e6a6bb6518d83435c2cafa1ae8234bf Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:06:52 +0530 Subject: [PATCH 43/57] dev: migrations for removal of timelineissues and shortcut (#1417) --- .../db/migrations/0034_auto_20230628_1046.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 apiserver/plane/db/migrations/0034_auto_20230628_1046.py diff --git a/apiserver/plane/db/migrations/0034_auto_20230628_1046.py b/apiserver/plane/db/migrations/0034_auto_20230628_1046.py new file mode 100644 index 000000000..cdd722f59 --- /dev/null +++ b/apiserver/plane/db/migrations/0034_auto_20230628_1046.py @@ -0,0 +1,39 @@ +# Generated by Django 3.2.19 on 2023-06-28 05:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0033_auto_20230618_2125'), + ] + + operations = [ + migrations.RemoveField( + model_name='timelineissue', + name='created_by', + ), + migrations.RemoveField( + model_name='timelineissue', + name='issue', + ), + migrations.RemoveField( + model_name='timelineissue', + name='project', + ), + migrations.RemoveField( + model_name='timelineissue', + name='updated_by', + ), + migrations.RemoveField( + model_name='timelineissue', + name='workspace', + ), + migrations.DeleteModel( + name='Shortcut', + ), + migrations.DeleteModel( + name='TimelineIssue', + ), + ] From 1a668c19a574942d3a9b2453016e6aaa62f03c2b Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 28 Jun 2023 17:27:19 +0530 Subject: [PATCH 44/57] style: issue detail page layout (#1424) --- .../[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 93bfecfe0..cb57ff9fd 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -120,10 +120,10 @@ const IssueDetailsPage: NextPage = () => { > {issueDetails && projectId ? (
    -
    +
    -
    +
    Date: Wed, 28 Jun 2023 17:27:43 +0530 Subject: [PATCH 45/57] style: adjust tooltip position and spacing in workspace id and title in spreadsheet (#1420) --- .../core/board-view/single-issue.tsx | 2 -- .../core/spreadsheet-view/single-issue.tsx | 18 ++++++++++++++---- .../spreadsheet-view/spreadsheet-issues.tsx | 4 ++++ .../core/spreadsheet-view/spreadsheet-view.tsx | 1 + .../components/issues/view-select/assignee.tsx | 6 +++--- .../components/issues/view-select/due-date.tsx | 8 +++++++- .../components/issues/view-select/estimate.tsx | 4 +++- .../components/issues/view-select/label.tsx | 6 +++--- .../components/issues/view-select/priority.tsx | 8 +++++++- .../components/issues/view-select/state.tsx | 3 +++ 10 files changed, 45 insertions(+), 15 deletions(-) diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index cde1b2e38..24a3e35a7 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -368,7 +368,6 @@ export const SingleBoardIssue: React.FC = ({ issue={issue} partialUpdateIssue={partialUpdateIssue} isNotAllowed={isNotAllowed} - tooltipPosition="left" user={user} selfPositioned /> @@ -378,7 +377,6 @@ export const SingleBoardIssue: React.FC = ({ issue={issue} partialUpdateIssue={partialUpdateIssue} isNotAllowed={isNotAllowed} - tooltipPosition="left" user={user} selfPositioned /> diff --git a/apps/app/components/core/spreadsheet-view/single-issue.tsx b/apps/app/components/core/spreadsheet-view/single-issue.tsx index 762de2453..22ffb0fb4 100644 --- a/apps/app/components/core/spreadsheet-view/single-issue.tsx +++ b/apps/app/components/core/spreadsheet-view/single-issue.tsx @@ -43,6 +43,7 @@ import { copyTextToClipboard } from "helpers/string.helper"; type Props = { issue: IIssue; + index: number; expanded: boolean; handleToggleExpand: (issueId: string) => void; properties: Properties; @@ -57,6 +58,7 @@ type Props = { export const SingleSpreadsheetIssue: React.FC = ({ issue, + index, expanded, handleToggleExpand, properties, @@ -165,6 +167,8 @@ export const SingleSpreadsheetIssue: React.FC = ({ const paddingLeft = `${nestingLevel * 68}px`; + const tooltipPosition = index === 0 ? "bottom" : "top"; + const isNotAllowed = userAuth.isGuest || userAuth.isViewer; return ( @@ -241,16 +245,16 @@ export const SingleSpreadsheetIssue: React.FC = ({ )}
    -
    - {issue.sub_issues_count > 0 && ( + {issue.sub_issues_count > 0 && ( +
    - )} -
    +
    + )}
    @@ -265,6 +269,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ issue={issue} partialUpdateIssue={partialUpdateIssue} position="left" + tooltipPosition={tooltipPosition} customButton user={user} isNotAllowed={isNotAllowed} @@ -277,6 +282,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ issue={issue} partialUpdateIssue={partialUpdateIssue} position="left" + tooltipPosition={tooltipPosition} noBorder user={user} isNotAllowed={isNotAllowed} @@ -289,6 +295,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ issue={issue} partialUpdateIssue={partialUpdateIssue} position="left" + tooltipPosition={tooltipPosition} customButton user={user} isNotAllowed={isNotAllowed} @@ -301,6 +308,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ issue={issue} partialUpdateIssue={partialUpdateIssue} position="left" + tooltipPosition={tooltipPosition} customButton user={user} isNotAllowed={isNotAllowed} @@ -313,6 +321,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ = ({ issue={issue} partialUpdateIssue={partialUpdateIssue} position="left" + tooltipPosition={tooltipPosition} user={user} isNotAllowed={isNotAllowed} /> diff --git a/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx b/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx index c52cfa59a..1e05eba4e 100644 --- a/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx +++ b/apps/app/components/core/spreadsheet-view/spreadsheet-issues.tsx @@ -10,6 +10,7 @@ import { ICurrentUserResponse, IIssue, Properties, UserAuth } from "types"; type Props = { key: string; issue: IIssue; + index: number; expandedIssues: string[]; setExpandedIssues: React.Dispatch>; properties: Properties; @@ -24,6 +25,7 @@ type Props = { export const SpreadsheetIssues: React.FC = ({ key, + index, issue, expandedIssues, setExpandedIssues, @@ -57,6 +59,7 @@ export const SpreadsheetIssues: React.FC = ({
    = ({ = ({ {spreadsheetIssues.map((issue: IIssue, index) => ( , issue: IIssue) => void; position?: "left" | "right"; + tooltipPosition?: "top" | "bottom"; selfPositioned?: boolean; - tooltipPosition?: "left" | "right"; customButton?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; @@ -32,7 +32,7 @@ export const ViewAssigneeSelect: React.FC = ({ partialUpdateIssue, position = "left", selfPositioned = false, - tooltipPosition = "right", + tooltipPosition = "top", user, isNotAllowed, customButton = false, @@ -69,7 +69,7 @@ export const ViewAssigneeSelect: React.FC = ({ const assigneeLabel = ( 0 diff --git a/apps/app/components/issues/view-select/due-date.tsx b/apps/app/components/issues/view-select/due-date.tsx index 163816a99..f5dcbc982 100644 --- a/apps/app/components/issues/view-select/due-date.tsx +++ b/apps/app/components/issues/view-select/due-date.tsx @@ -12,6 +12,7 @@ import { ICurrentUserResponse, IIssue } from "types"; type Props = { issue: IIssue; partialUpdateIssue: (formData: Partial, issue: IIssue) => void; + tooltipPosition?: "top" | "bottom"; noBorder?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; @@ -20,6 +21,7 @@ type Props = { export const ViewDueDateSelect: React.FC = ({ issue, partialUpdateIssue, + tooltipPosition = "top", noBorder = false, user, isNotAllowed, @@ -28,7 +30,11 @@ export const ViewDueDateSelect: React.FC = ({ const { workspaceSlug } = router.query; return ( - +
    , issue: IIssue) => void; position?: "left" | "right"; + tooltipPosition?: "top" | "bottom"; selfPositioned?: boolean; customButton?: boolean; user: ICurrentUserResponse | undefined; @@ -27,6 +28,7 @@ export const ViewEstimateSelect: React.FC = ({ issue, partialUpdateIssue, position = "left", + tooltipPosition = "top", selfPositioned = false, customButton = false, user, @@ -40,7 +42,7 @@ export const ViewEstimateSelect: React.FC = ({ const estimateValue = estimatePoints?.find((e) => e.key === issue.estimate_point)?.value; const estimateLabels = ( - +
    {estimateValue ?? "None"} diff --git a/apps/app/components/issues/view-select/label.tsx b/apps/app/components/issues/view-select/label.tsx index 33df5cf9f..53dca9382 100644 --- a/apps/app/components/issues/view-select/label.tsx +++ b/apps/app/components/issues/view-select/label.tsx @@ -22,7 +22,7 @@ type Props = { partialUpdateIssue: (formData: Partial, issue: IIssue) => void; position?: "left" | "right"; selfPositioned?: boolean; - tooltipPosition?: "left" | "right"; + tooltipPosition?: "top" | "bottom"; customButton?: boolean; user: ICurrentUserResponse | undefined; isNotAllowed: boolean; @@ -33,7 +33,7 @@ export const ViewLabelSelect: React.FC = ({ partialUpdateIssue, position = "left", selfPositioned = false, - tooltipPosition = "right", + tooltipPosition = "top", user, isNotAllowed, customButton = false, @@ -68,7 +68,7 @@ export const ViewLabelSelect: React.FC = ({ const labelsLabel = ( 0 diff --git a/apps/app/components/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index 0afd2dd98..e7a674ec7 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -19,6 +19,7 @@ type Props = { issue: IIssue; partialUpdateIssue: (formData: Partial, issue: IIssue) => void; position?: "left" | "right"; + tooltipPosition?: "top" | "bottom"; selfPositioned?: boolean; noBorder?: boolean; user: ICurrentUserResponse | undefined; @@ -29,6 +30,7 @@ export const ViewPrioritySelect: React.FC = ({ issue, partialUpdateIssue, position = "left", + tooltipPosition = "top", selfPositioned = false, noBorder = false, user, @@ -75,7 +77,11 @@ export const ViewPrioritySelect: React.FC = ({ : "border-brand-base" } items-center`} > - + {getPriorityIcon( issue.priority && issue.priority !== "" ? issue.priority ?? "" : "None", diff --git a/apps/app/components/issues/view-select/state.tsx b/apps/app/components/issues/view-select/state.tsx index 5ec0f71c7..4a9f585e2 100644 --- a/apps/app/components/issues/view-select/state.tsx +++ b/apps/app/components/issues/view-select/state.tsx @@ -21,6 +21,7 @@ type Props = { issue: IIssue; partialUpdateIssue: (formData: Partial, issue: IIssue) => void; position?: "left" | "right"; + tooltipPosition?: "top" | "bottom"; selfPositioned?: boolean; customButton?: boolean; user: ICurrentUserResponse | undefined; @@ -31,6 +32,7 @@ export const ViewStateSelect: React.FC = ({ issue, partialUpdateIssue, position = "left", + tooltipPosition = "top", selfPositioned = false, customButton = false, user, @@ -64,6 +66,7 @@ export const ViewStateSelect: React.FC = ({
    {selectedOption && From c743d43ce2ddb1417a1421b07360a13505b609db Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Wed, 28 Jun 2023 19:08:33 +0530 Subject: [PATCH 46/57] fix: charts design and mutation (#1426) * fix: pie chart overlap issue * fix: burndown chart mutation * fix: burndown chart mutation --- .../components/core/board-view/single-issue.tsx | 3 +++ .../components/core/list-view/single-issue.tsx | 3 +++ .../core/spreadsheet-view/single-issue.tsx | 5 +++++ .../components/workspace/issues-pie-chart.tsx | 16 +++++++++++++--- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/app/components/core/board-view/single-issue.tsx b/apps/app/components/core/board-view/single-issue.tsx index 24a3e35a7..6753e84cd 100644 --- a/apps/app/components/core/board-view/single-issue.tsx +++ b/apps/app/components/core/board-view/single-issue.tsx @@ -173,6 +173,9 @@ export const SingleBoardIssue: React.FC = ({ .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user) .then(() => { mutate(fetchKey); + + if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); + if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); }); }, [ diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index f4d749452..774fbb02d 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -147,6 +147,9 @@ export const SingleListIssue: React.FC = ({ .patchIssue(workspaceSlug as string, projectId as string, issue.id, formData, user) .then(() => { mutate(fetchKey); + + if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); + if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); }); }, [ diff --git a/apps/app/components/core/spreadsheet-view/single-issue.tsx b/apps/app/components/core/spreadsheet-view/single-issue.tsx index 22ffb0fb4..ada1e3689 100644 --- a/apps/app/components/core/spreadsheet-view/single-issue.tsx +++ b/apps/app/components/core/spreadsheet-view/single-issue.tsx @@ -30,7 +30,9 @@ import useToast from "hooks/use-toast"; import issuesService from "services/issues.service"; // constant import { + CYCLE_DETAILS, CYCLE_ISSUES_WITH_PARAMS, + MODULE_DETAILS, MODULE_ISSUES_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS, SUB_ISSUES, @@ -142,6 +144,9 @@ export const SingleSpreadsheetIssue: React.FC = ({ mutate(SUB_ISSUES(issue.parent as string)); } else { mutate(fetchKey); + + if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); + if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); } }) .catch((error) => { diff --git a/apps/app/components/workspace/issues-pie-chart.tsx b/apps/app/components/workspace/issues-pie-chart.tsx index ada35d080..c83ef1de2 100644 --- a/apps/app/components/workspace/issues-pie-chart.tsx +++ b/apps/app/components/workspace/issues-pie-chart.tsx @@ -25,8 +25,12 @@ export const IssuesPieChart: React.FC = ({ groupedIssues }) => ( })) ?? [] } height="320px" - innerRadius={0.5} - arcLinkLabel={(cell) => `${capitalizeFirstLetter(cell.label.toString())} (${cell.value})`} + innerRadius={0.6} + cornerRadius={5} + padAngle={2} + enableArcLabels + arcLabelsTextColor="#000000" + enableArcLinkLabels={false} legends={[ { anchor: "right", @@ -53,8 +57,14 @@ export const IssuesPieChart: React.FC = ({ groupedIssues }) => ( ]} activeInnerRadiusOffset={5} colors={(datum) => datum.data.color} + tooltip={(datum) => ( +
    + {datum.datum.label} issues:{" "} + {datum.datum.value} +
    + )} theme={{ - background: "rgb(var(--color-bg-base))", + background: "transparent", }} />
    From 1ed7935bf0cb56b244cc26d8a4063573da1e93bc Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:52:44 +0530 Subject: [PATCH 47/57] docs: update readme.md for self hosting setup and remove pnpm from contribution guidelines (#1423) --- CONTRIBUTING.md | 1 - README.md | 8 -------- 2 files changed, 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75ccb884c..6baa0bb07 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,7 +23,6 @@ You can open a new issue with this [issue form](https://github.com/makeplane/pla - Python version 3.8+ - Postgres version v14 - Redis version v6.2.7 -- pnpm version 7.22.0 ### Setup the project diff --git a/README.md b/README.md index 7e9422f18..1e600df5e 100644 --- a/README.md +++ b/README.md @@ -61,14 +61,6 @@ chmod +x setup.sh > If running in a cloud env replace localhost with public facing IP address of the VM -- Export Environment Variables - -```bash -set -a -source .env -set +a -``` - - Run Docker compose up ```bash From 3c07da433ab09b69d8a2aee965e7736f67b06c6c Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Thu, 29 Jun 2023 18:16:01 +0530 Subject: [PATCH 48/57] fix: overflow fix --- apps/app/components/gantt-chart/blocks/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/components/gantt-chart/blocks/index.tsx b/apps/app/components/gantt-chart/blocks/index.tsx index d5eadf2a0..31e7839cc 100644 --- a/apps/app/components/gantt-chart/blocks/index.tsx +++ b/apps/app/components/gantt-chart/blocks/index.tsx @@ -18,7 +18,7 @@ export const GanttChartBlocks: FC<{ return (
    From 8229b12c9cecd2ba7919d5b35671a1db2c89a00f Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Thu, 29 Jun 2023 18:17:52 +0530 Subject: [PATCH 49/57] style: recent page overflow fix --- apps/app/components/pages/pages-list/recent-pages-list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/components/pages/pages-list/recent-pages-list.tsx b/apps/app/components/pages/pages-list/recent-pages-list.tsx index 44225aee5..ce66a6ce1 100644 --- a/apps/app/components/pages/pages-list/recent-pages-list.tsx +++ b/apps/app/components/pages/pages-list/recent-pages-list.tsx @@ -41,7 +41,7 @@ export const RecentPagesList: React.FC = ({ viewType }) => { if (pages[key].length === 0) return null; return ( -
    +

    {replaceUnderscoreIfSnakeCase(key)}

    From f2a4e026a5f0b114cfa98be1bb70c910251e20fa Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Thu, 29 Jun 2023 18:19:38 +0530 Subject: [PATCH 50/57] fix: cycle and module sidebar overflow fix --- apps/app/components/cycles/sidebar.tsx | 8 ++++++-- apps/app/components/modules/sidebar.tsx | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/app/components/cycles/sidebar.tsx b/apps/app/components/cycles/sidebar.tsx index d61bd1943..e4b48869f 100644 --- a/apps/app/components/cycles/sidebar.tsx +++ b/apps/app/components/cycles/sidebar.tsx @@ -408,7 +408,11 @@ export const CycleDetailsSidebar: React.FC = ({
    -

    {cycle.name}

    +
    +

    + {cycle.name} +

    +
    {!isCompleted && ( setCycleDeleteModal(true)}> @@ -427,7 +431,7 @@ export const CycleDetailsSidebar: React.FC = ({
    - + {cycle.description}
    diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index de8714968..4c7feacbd 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -322,7 +322,11 @@ export const ModuleDetailsSidebar: React.FC = ({ module, isOpen, moduleIs
    -

    {module.name}

    +
    +

    + {module.name} +

    +
    setModuleDeleteModal(true)}> @@ -339,7 +343,7 @@ export const ModuleDetailsSidebar: React.FC = ({ module, isOpen, moduleIs
    - + {module.description}
    From d8eeb9e17ac944e61da354742074dc333222121f Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Thu, 29 Jun 2023 18:20:22 +0530 Subject: [PATCH 51/57] fix: cycle list item fix --- apps/app/components/cycles/single-cycle-list.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/app/components/cycles/single-cycle-list.tsx b/apps/app/components/cycles/single-cycle-list.tsx index fa725b83a..423580383 100644 --- a/apps/app/components/cycles/single-cycle-list.tsx +++ b/apps/app/components/cycles/single-cycle-list.tsx @@ -172,17 +172,19 @@ export const SingleCycleList: React.FC = ({ : "" }`} /> -
    +
    -

    +

    {truncateText(cycle.name, 70)}

    -

    {cycle.description}

    +

    + {cycle.description} +

    From ebf877b7fa2b7ce315b222a99ac1924ffd47e0f2 Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Thu, 29 Jun 2023 18:33:44 +0530 Subject: [PATCH 52/57] fix: display properties fix --- apps/app/components/core/issues-view-filter.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 679f6adc3..112862e8a 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -262,9 +262,8 @@ export const IssuesFilterView: React.FC = () => { if (key === "estimate" && !isEstimateActive) return null; if ( - (issueView === "spreadsheet" && key === "sub_issue_count") || - key === "attachment_count" || - key === "link" + (issueView === "spreadsheet" && key === "attachment_count") || + (issueView === "spreadsheet" && key === "link") ) return null; From 32bf2ed56a7b2fcd212b994007147cb9ec2fbe3b Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Thu, 29 Jun 2023 18:37:19 +0530 Subject: [PATCH 53/57] fix: display properties fix --- apps/app/components/core/issues-view-filter.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index 112862e8a..a6996793c 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -263,7 +263,8 @@ export const IssuesFilterView: React.FC = () => { if ( (issueView === "spreadsheet" && key === "attachment_count") || - (issueView === "spreadsheet" && key === "link") + (issueView === "spreadsheet" && key === "link") || + (issueView === "spreadsheet" && key === "sub_issue_count") ) return null; From d62dc25aa00e34373c7af45fdc0e5380f3a145fd Mon Sep 17 00:00:00 2001 From: tajkirkpatrick <50558516+tajkirkpatrick@users.noreply.github.com> Date: Thu, 29 Jun 2023 23:23:44 -0500 Subject: [PATCH 54/57] Add network timeout option to yarn install (#1382) Inside of the Dockerfile.web, the yarn install command is susceptible to encountering a ECONNECT timeout error on aarch64 and ARM devices (such as a raspberry pi). This explicit overwrite of the default network timeout extends the window for low power CPU devices. --- apps/app/Dockerfile.web | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/Dockerfile.web b/apps/app/Dockerfile.web index 1b9bc41d5..e0b5f29c1 100644 --- a/apps/app/Dockerfile.web +++ b/apps/app/Dockerfile.web @@ -20,7 +20,7 @@ ARG NEXT_PUBLIC_API_BASE_URL=http://localhost:8000 COPY .gitignore .gitignore COPY --from=builder /app/out/json/ . COPY --from=builder /app/out/yarn.lock ./yarn.lock -RUN yarn install +RUN yarn install --network-timeout 500000 # Build the project COPY --from=builder /app/out/full/ . From 7e2caf65bb44a3cf6ef8b9540a914febad4e7ac4 Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Fri, 30 Jun 2023 06:30:45 +0200 Subject: [PATCH 55/57] chore: Dry up docker compose environment variables (#1438) * chore: Use docker compose extensions to dry up duplicate environment variables between worker and api services. See: https://docs.docker.com/compose/compose-file/11-extension/ --- docker-compose.yml | 91 +++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 640bb723e..496ee434d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,36 @@ version: "3.8" +x-api-and-worker-env: &api-and-worker-env + DEBUG: ${DEBUG} + SENTRY_DSN: ${SENTRY_DSN} + DJANGO_SETTINGS_MODULE: plane.settings.production + DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} + REDIS_URL: redis://plane-redis:6379/ + EMAIL_HOST: ${EMAIL_HOST} + EMAIL_HOST_USER: ${EMAIL_HOST_USER} + EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} + EMAIL_PORT: ${EMAIL_PORT} + EMAIL_FROM: ${EMAIL_FROM} + EMAIL_USE_TLS: ${EMAIL_USE_TLS} + EMAIL_USE_SSL: ${EMAIL_USE_SSL} + AWS_REGION: ${AWS_REGION} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} + AWS_S3_ENDPOINT_URL: ${AWS_S3_ENDPOINT_URL} + FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT} + WEB_URL: ${WEB_URL} + GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} + DISABLE_COLLECTSTATIC: 1 + DOCKERIZED: 1 + OPENAI_API_KEY: ${OPENAI_API_KEY} + GPT_ENGINE: ${GPT_ENGINE} + SECRET_KEY: ${SECRET_KEY} + DEFAULT_EMAIL: ${DEFAULT_EMAIL} + DEFAULT_PASSWORD: ${DEFAULT_PASSWORD} + USE_MINIO: ${USE_MINIO} + ENABLE_SIGNUP: ${ENABLE_SIGNUP} + services: plane-web: container_name: planefrontend @@ -37,35 +68,7 @@ services: env_file: - .env environment: - DEBUG: ${DEBUG} - SENTRY_DSN: ${SENTRY_DSN} - DJANGO_SETTINGS_MODULE: plane.settings.production - DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} - REDIS_URL: redis://plane-redis:6379/ - EMAIL_HOST: ${EMAIL_HOST} - EMAIL_HOST_USER: ${EMAIL_HOST_USER} - EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} - EMAIL_PORT: ${EMAIL_PORT} - EMAIL_FROM: ${EMAIL_FROM} - EMAIL_USE_TLS: ${EMAIL_USE_TLS} - EMAIL_USE_SSL: ${EMAIL_USE_SSL} - AWS_REGION: ${AWS_REGION} - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} - AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} - AWS_S3_ENDPOINT_URL: ${AWS_S3_ENDPOINT_URL} - FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT} - WEB_URL: ${WEB_URL} - GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} - DISABLE_COLLECTSTATIC: 1 - DOCKERIZED: 1 - OPENAI_API_KEY: ${OPENAI_API_KEY} - GPT_ENGINE: ${GPT_ENGINE} - SECRET_KEY: ${SECRET_KEY} - DEFAULT_EMAIL: ${DEFAULT_EMAIL} - DEFAULT_PASSWORD: ${DEFAULT_PASSWORD} - USE_MINIO: ${USE_MINIO} - ENABLE_SIGNUP: ${ENABLE_SIGNUP} + <<: *api-and-worker-env depends_on: - plane-db - plane-redis @@ -80,35 +83,7 @@ services: env_file: - .env environment: - DEBUG: ${DEBUG} - SENTRY_DSN: ${SENTRY_DSN} - DJANGO_SETTINGS_MODULE: plane.settings.production - DATABASE_URL: postgres://${PGUSER}:${PGPASSWORD}@${PGHOST}:5432/${PGDATABASE} - REDIS_URL: redis://plane-redis:6379/ - EMAIL_HOST: ${EMAIL_HOST} - EMAIL_HOST_USER: ${EMAIL_HOST_USER} - EMAIL_HOST_PASSWORD: ${EMAIL_HOST_PASSWORD} - EMAIL_PORT: ${EMAIL_PORT} - EMAIL_FROM: ${EMAIL_FROM} - EMAIL_USE_TLS: ${EMAIL_USE_TLS} - EMAIL_USE_SSL: ${EMAIL_USE_SSL} - AWS_REGION: ${AWS_REGION} - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} - AWS_S3_BUCKET_NAME: ${AWS_S3_BUCKET_NAME} - AWS_S3_ENDPOINT_URL: ${AWS_S3_ENDPOINT_URL} - FILE_SIZE_LIMIT: ${FILE_SIZE_LIMIT} - WEB_URL: ${WEB_URL} - GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET} - DISABLE_COLLECTSTATIC: 1 - DOCKERIZED: 1 - OPENAI_API_KEY: ${OPENAI_API_KEY} - GPT_ENGINE: ${GPT_ENGINE} - SECRET_KEY: ${SECRET_KEY} - DEFAULT_EMAIL: ${DEFAULT_EMAIL:-captain@plane.so} - DEFAULT_PASSWORD: ${DEFAULT_PASSWORD:-password123} - USE_MINIO: ${USE_MINIO} - ENABLE_SIGNUP: ${ENABLE_SIGNUP} + <<: *api-and-worker-env depends_on: - plane-api - plane-db From fc15da826befb1f11ff04fdb2a6a989f04dd04c9 Mon Sep 17 00:00:00 2001 From: anmolsinghbhatia Date: Fri, 30 Jun 2023 18:28:12 +0530 Subject: [PATCH 56/57] style: label dropdown empty state --- apps/app/components/issues/view-select/label.tsx | 3 +++ apps/app/components/ui/custom-search-select.tsx | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/app/components/issues/view-select/label.tsx b/apps/app/components/issues/view-select/label.tsx index 53dca9382..b82b02a0f 100644 --- a/apps/app/components/issues/view-select/label.tsx +++ b/apps/app/components/issues/view-select/label.tsx @@ -118,6 +118,8 @@ export const ViewLabelSelect: React.FC = ({ ); + const noResultIcon = ; + return ( <> {projectId && ( @@ -141,6 +143,7 @@ export const ViewLabelSelect: React.FC = ({ disabled={isNotAllowed} selfPositioned={selfPositioned} footerOption={footerOption} + noResultIcon={noResultIcon} dropdownWidth="w-full min-w-[12rem]" /> diff --git a/apps/app/components/ui/custom-search-select.tsx b/apps/app/components/ui/custom-search-select.tsx index fb1c0a88c..f9ea3daa1 100644 --- a/apps/app/components/ui/custom-search-select.tsx +++ b/apps/app/components/ui/custom-search-select.tsx @@ -29,6 +29,7 @@ type CustomSearchSelectProps = { selfPositioned?: boolean; multiple?: boolean; footerOption?: JSX.Element; + noResultIcon?: JSX.Element; dropdownWidth?: string; }; export const CustomSearchSelect = ({ @@ -47,6 +48,7 @@ export const CustomSearchSelect = ({ disabled = false, selfPositioned = false, multiple = false, + noResultIcon, footerOption, dropdownWidth, }: CustomSearchSelectProps) => { @@ -171,7 +173,10 @@ export const CustomSearchSelect = ({ )) ) : ( -

    No matching results

    + + {noResultIcon && noResultIcon} +

    No matching results

    +
    ) ) : (

    Loading...

    From 110eb39a512adc78acdc34c2bd1e61c238aec7dd Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 30 Jun 2023 19:37:26 +0530 Subject: [PATCH 57/57] dev: update packages to latest version (#1431) --- apiserver/plane/settings/local.py | 1 + apiserver/plane/settings/production.py | 1 + apiserver/plane/settings/staging.py | 1 + apiserver/requirements/base.txt | 28 +++++++++++++------------- apiserver/requirements/production.txt | 6 +++--- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/apiserver/plane/settings/local.py b/apiserver/plane/settings/local.py index 1b862c013..e6f5f8e39 100644 --- a/apiserver/plane/settings/local.py +++ b/apiserver/plane/settings/local.py @@ -63,6 +63,7 @@ if os.environ.get("SENTRY_DSN", False): send_default_pii=True, environment="local", traces_sample_rate=0.7, + profiles_sample_rate=1.0, ) REDIS_HOST = "localhost" diff --git a/apiserver/plane/settings/production.py b/apiserver/plane/settings/production.py index 29b75fc8b..983931110 100644 --- a/apiserver/plane/settings/production.py +++ b/apiserver/plane/settings/production.py @@ -84,6 +84,7 @@ if bool(os.environ.get("SENTRY_DSN", False)): traces_sample_rate=1, send_default_pii=True, environment="production", + profiles_sample_rate=1.0, ) if DOCKERIZED and USE_MINIO: diff --git a/apiserver/plane/settings/staging.py b/apiserver/plane/settings/staging.py index 11ff7a372..5a43e266e 100644 --- a/apiserver/plane/settings/staging.py +++ b/apiserver/plane/settings/staging.py @@ -66,6 +66,7 @@ sentry_sdk.init( traces_sample_rate=1, send_default_pii=True, environment="staging", + profiles_sample_rate=1.0, ) # The AWS region to connect to. diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index 2bc109968..3cd196830 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -2,30 +2,30 @@ Django==3.2.19 django-braces==1.15.0 -django-taggit==3.1.0 -psycopg2==2.9.5 -django-oauth-toolkit==2.2.0 +django-taggit==4.0.0 +psycopg2==2.9.6 +django-oauth-toolkit==2.3.0 mistune==2.0.4 djangorestframework==3.14.0 -redis==4.5.4 +redis==4.6.0 django-nested-admin==4.0.2 -django-cors-headers==3.13.0 +django-cors-headers==4.1.0 whitenoise==6.3.0 -django-allauth==0.52.0 +django-allauth==0.54.0 faker==13.4.0 -django-filter==22.1 +django-filter==23.2 jsonmodels==2.6.0 djangorestframework-simplejwt==5.2.2 -sentry-sdk==1.14.0 -django-s3-storage==0.13.11 +sentry-sdk==1.26.0 +django-s3-storage==0.14.0 django-crum==0.7.9 django-guardian==2.4.0 dj_rest_auth==2.2.5 google-auth==2.16.0 google-api-python-client==2.75.0 -django-redis==5.2.0 -uvicorn==0.20.0 +django-redis==5.3.0 +uvicorn==0.22.0 channels==4.0.0 -openai==0.27.2 -slack-sdk==3.20.2 -celery==5.2.7 \ No newline at end of file +openai==0.27.8 +slack-sdk==3.21.3 +celery==5.3.1 \ No newline at end of file diff --git a/apiserver/requirements/production.txt b/apiserver/requirements/production.txt index c37e98ffd..13b3e9aed 100644 --- a/apiserver/requirements/production.txt +++ b/apiserver/requirements/production.txt @@ -1,11 +1,11 @@ -r base.txt -dj-database-url==1.2.0 +dj-database-url==2.0.0 gunicorn==20.1.0 whitenoise==6.3.0 django-storages==1.13.2 -boto3==1.26.136 -django-anymail==9.0 +boto3==1.26.163 +django-anymail==10.0 twilio==7.16.2 django-debug-toolbar==3.8.1 gevent==22.10.2