From 05eb728c407f407bbb27b88ae52a9238c1efdf09 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 27 Dec 2023 15:16:13 +0530 Subject: [PATCH 01/14] chore: cycle & module sidebar improvement (#3251) * chore: no lead option added in module lead select dropdown * chore: module lead select dropdown code refactor * chore: cycle sidebar improvement * chore: module sidebar improvement * style: cycle and module sidebar improvement * style: app sidebar improvement --- web/components/cycles/sidebar.tsx | 82 ++++++++++++----- web/components/modules/select/lead.tsx | 9 +- .../modules/sidebar-select/select-lead.tsx | 15 ++- web/components/modules/sidebar.tsx | 92 ++++++++++++++----- web/components/project/sidebar-list.tsx | 8 +- 5 files changed, 153 insertions(+), 53 deletions(-) diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index 1fd1cd05c..dcf86bb46 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -17,7 +17,16 @@ import { CycleDeleteModal } from "components/cycles/delete-modal"; import { CustomRangeDatePicker } from "components/ui"; import { Avatar, CustomMenu, Loader, LayersIcon } from "@plane/ui"; // icons -import { ChevronDown, LinkIcon, Trash2, UserCircle2, AlertCircle, ChevronRight, MoveRight } from "lucide-react"; +import { + ChevronDown, + LinkIcon, + Trash2, + UserCircle2, + AlertCircle, + ChevronRight, + CalendarCheck2, + CalendarClock, +} from "lucide-react"; // helpers import { copyUrlToClipboard } from "helpers/string.helper"; import { @@ -357,8 +366,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { -
-

{cycleDetails.name}

+
{currentCycle && ( = observer((props) => { : `${currentCycle.label}`} )} -
- +
+

{cycleDetails.name}

+
+ + {cycleDetails.description && ( + + {cycleDetails.description} + + )} + +
+
+
+ + Start Date +
+
+ - {areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} + + {areYearsEqual + ? renderShortDate(startDate, "No date selected") + : renderShortMonthDate(startDate, "No date selected")} + = observer((props) => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - + { @@ -410,16 +442,32 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { - - +
+
+ +
+
+ + Target Date +
+
+ <> - {areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")} + + {areYearsEqual + ? renderShortDate(endDate, "No date selected") + : renderShortMonthDate(endDate, "No date selected")} + = observer((props) => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - + { @@ -451,15 +499,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => {
-
- {cycleDetails.description && ( - - {cycleDetails.description} - - )} - -
diff --git a/web/components/modules/select/lead.tsx b/web/components/modules/select/lead.tsx index d84d67237..8f376618c 100644 --- a/web/components/modules/select/lead.tsx +++ b/web/components/modules/select/lead.tsx @@ -7,7 +7,7 @@ import { ProjectMemberService } from "services/project"; import { Avatar, CustomSearchSelect } from "@plane/ui"; // icons import { Combobox } from "@headlessui/react"; -import { UserCircle } from "lucide-react"; +import { UserCircle, UserCircle2 } from "lucide-react"; // fetch-keys import { PROJECT_MEMBERS } from "constants/fetch-keys"; @@ -65,9 +65,10 @@ export const ModuleLeadSelect: React.FC = ({ value, onChange }) => { value="" className="flex cursor-pointer select-none items-center justify-between gap-2 truncate rounded px-1 py-1.5 text-custom-text-200" > - - No Lead - +
+ + No lead +
} onChange={onChange} diff --git a/web/components/modules/sidebar-select/select-lead.tsx b/web/components/modules/sidebar-select/select-lead.tsx index f675925c4..91a70504f 100644 --- a/web/components/modules/sidebar-select/select-lead.tsx +++ b/web/components/modules/sidebar-select/select-lead.tsx @@ -31,6 +31,17 @@ export const SidebarLeadSelect: FC = (props) => { : null ); + const noLeadOption = { + value: "", + query: "No lead", + content: ( +
+ + No lead +
+ ), + }; + const options = members?.map((member) => ({ value: member.member.id, query: member.member.display_name, @@ -42,6 +53,8 @@ export const SidebarLeadSelect: FC = (props) => { ), })); + const leadOption = (options || []).concat(noLeadOption); + const selectedOption = members?.find((m) => m.member.id === value)?.member; return ( @@ -69,7 +82,7 @@ export const SidebarLeadSelect: FC = (props) => {
) } - options={options} + options={leadOption} maxHeight="md" onChange={onChange} /> diff --git a/web/components/modules/sidebar.tsx b/web/components/modules/sidebar.tsx index 3c9552465..3fe7bf57e 100644 --- a/web/components/modules/sidebar.tsx +++ b/web/components/modules/sidebar.tsx @@ -15,7 +15,17 @@ import ProgressChart from "components/core/sidebar/progress-chart"; import { CustomRangeDatePicker } from "components/ui"; import { CustomMenu, Loader, LayersIcon, CustomSelect, ModuleStatusIcon } from "@plane/ui"; // icon -import { AlertCircle, ChevronDown, ChevronRight, Info, LinkIcon, MoveRight, Plus, Trash2 } from "lucide-react"; +import { + AlertCircle, + CalendarCheck2, + CalendarClock, + ChevronDown, + ChevronRight, + Info, + LinkIcon, + Plus, + Trash2, +} from "lucide-react"; // helpers import { isDateGreaterThanToday, @@ -227,7 +237,13 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { else newValues.push(value); } - updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { [key]: newValues }, moduleId); + updateFilters( + workspaceSlug.toString(), + projectId.toString(), + EFilterType.FILTERS, + { [key]: newValues }, + moduleId + ); }, [workspaceSlug, projectId, moduleId, issueFilters, updateFilters] ); @@ -328,8 +344,7 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => {
-

{moduleDetails.name}

-
+
= observer((props) => { )} /> +
+

{moduleDetails.name}

+
-
- + {moduleDetails.description && ( + + {moduleDetails.description} + + )} + +
+
+
+ + + Start Date +
+
+ - {areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} + + {areYearsEqual + ? renderShortDate(startDate, "No date selected") + : renderShortMonthDate(startDate, "No date selected")} + = observer((props) => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - + { @@ -402,16 +441,32 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { - - +
+
+ +
+
+ + Target Date +
+
+ <> - {areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")} + + {areYearsEqual + ? renderShortDate(endDate, "No date selected") + : renderShortMonthDate(endDate, "No date selected")} + = observer((props) => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - + { @@ -442,15 +497,6 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => {
-
- - {moduleDetails.description && ( - - {moduleDetails.description} - - )} - -
{ > Favorites {open ? ( - + ) : ( - + )} {isAuthorizedUser && ( @@ -215,9 +215,9 @@ export const ProjectSidebarList: FC = observer(() => { > Projects {open ? ( - + ) : ( - + )} {isAuthorizedUser && ( From 78428fb564addcf514190de81cd5a0d15efee16a Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Wed, 27 Dec 2023 15:35:15 +0530 Subject: [PATCH 02/14] chore: estimates and issue parent id fixes (#3188) * chore: validations and bug fixes * chore: estimate point character limit validation --------- Co-authored-by: LAKHAN BAHETI --- apiserver/plane/app/serializers/estimate.py | 10 +++++++ apiserver/plane/app/views/estimate.py | 8 +++--- apiserver/plane/app/views/search.py | 3 ++- .../plane/bgtasks/issue_activites_task.py | 4 +-- .../create-update-estimate-modal.tsx | 26 +++++++++++++++++-- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/apiserver/plane/app/serializers/estimate.py b/apiserver/plane/app/serializers/estimate.py index 4a1cda779..2c2f26e4e 100644 --- a/apiserver/plane/app/serializers/estimate.py +++ b/apiserver/plane/app/serializers/estimate.py @@ -4,6 +4,7 @@ from .base import BaseSerializer from plane.db.models import Estimate, EstimatePoint from plane.app.serializers import WorkspaceLiteSerializer, ProjectLiteSerializer +from rest_framework import serializers class EstimateSerializer(BaseSerializer): workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") @@ -19,6 +20,15 @@ class EstimateSerializer(BaseSerializer): class EstimatePointSerializer(BaseSerializer): + + def validate(self, data): + if not data: + raise serializers.ValidationError("Estimate points are required") + value = data.get("value") + if value and len(value) > 20: + raise serializers.ValidationError("Value can't be more than 20 characters") + return data + class Meta: model = EstimatePoint fields = "__all__" diff --git a/apiserver/plane/app/views/estimate.py b/apiserver/plane/app/views/estimate.py index ec9393f5b..8f14b230b 100644 --- a/apiserver/plane/app/views/estimate.py +++ b/apiserver/plane/app/views/estimate.py @@ -53,11 +53,11 @@ class BulkEstimatePointEndpoint(BaseViewSet): ) estimate_points = request.data.get("estimate_points", []) - - if not len(estimate_points) or len(estimate_points) > 8: + + serializer = EstimatePointSerializer(data=request.data.get("estimate_points"), many=True) + if not serializer.is_valid(): return Response( - {"error": "Estimate points are required"}, - status=status.HTTP_400_BAD_REQUEST, + serializer.errors, status=status.HTTP_400_BAD_REQUEST ) estimate_serializer = EstimateSerializer(data=request.data.get("estimate")) diff --git a/apiserver/plane/app/views/search.py b/apiserver/plane/app/views/search.py index ac560643a..4ecb71127 100644 --- a/apiserver/plane/app/views/search.py +++ b/apiserver/plane/app/views/search.py @@ -50,7 +50,8 @@ class GlobalSearchEndpoint(BaseAPIView): q = Q() for field in fields: if field == "sequence_id": - sequences = re.findall(r"\d+\.\d+|\d+", query) + # Match whole integers only (exclude decimal numbers) + sequences = re.findall(r"\b\d+\b", query) for sequence_id in sequences: q |= Q(**{"sequence_id": sequence_id}) else: diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 3b2b40223..5d4c0650c 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -112,8 +112,8 @@ def track_parent( epoch, ): if current_instance.get("parent") != requested_data.get("parent"): - old_parent = Issue.objects.filter(pk=current_instance.get("parent")).first() - new_parent = Issue.objects.filter(pk=requested_data.get("parent")).first() + old_parent = Issue.objects.filter(pk=current_instance.get("parent")).first() if current_instance.get("parent") is not None else None + new_parent = Issue.objects.filter(pk=requested_data.get("parent")).first() if requested_data.get("parent") is not None else None issue_activities.append( IssueActivity( diff --git a/web/components/estimates/create-update-estimate-modal.tsx b/web/components/estimates/create-update-estimate-modal.tsx index 6f0f98e86..b24172688 100644 --- a/web/components/estimates/create-update-estimate-modal.tsx +++ b/web/components/estimates/create-update-estimate-modal.tsx @@ -129,6 +129,22 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { return; } + if ( + formData.value1.length > 20 || + formData.value2.length > 20 || + formData.value3.length > 20 || + formData.value4.length > 20 || + formData.value5.length > 20 || + formData.value6.length > 20 + ) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Estimate point cannot have more than 20 characters.", + }); + return; + } + if ( checkDuplicates([ formData.value1, @@ -269,6 +285,12 @@ export const CreateUpdateEstimateModal: React.FC = observer((props) => { ( = observer((props) => { ? "Updating Estimate..." : "Update Estimate" : isSubmitting - ? "Creating Estimate..." - : "Create Estimate"} + ? "Creating Estimate..." + : "Create Estimate"}
From 54964924f0762344f387cf2bfa722870519ee926 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Wed, 27 Dec 2023 16:15:56 +0530 Subject: [PATCH 03/14] fix: adding links to dashboard summary items --- web/components/workspace/issues-stats.tsx | 86 +++++++++-------------- 1 file changed, 33 insertions(+), 53 deletions(-) diff --git a/web/components/workspace/issues-stats.tsx b/web/components/workspace/issues-stats.tsx index 1fa5cf2df..b8cc8432f 100644 --- a/web/components/workspace/issues-stats.tsx +++ b/web/components/workspace/issues-stats.tsx @@ -7,6 +7,7 @@ import { Info } from "lucide-react"; // types import { IUserWorkspaceDashboard } from "types"; import { useRouter } from "next/router"; +import Link from "next/link"; type Props = { data: IUserWorkspaceDashboard | undefined; @@ -19,61 +20,40 @@ export const IssuesStats: React.FC = ({ data }) => {
-
-

Issues assigned to you

-
- {data ? ( -
router.push(`/${workspaceSlug}/workspace-views/assigned`)} - > - {data.assigned_issues_count} -
- ) : ( - - - - )} -
-
-
-

Pending issues

-
- {data ? ( - data.pending_issues_count - ) : ( - - - - )} -
-
+ +
+

Issues assigned to you

+
+
{data?.assigned_issues_count}
+
+
+ + +
+

Pending issues

+
{data?.pending_issues_count}
+
+
-
-

Completed issues

-
- {data ? ( - data.completed_issues_count - ) : ( - - - - )} -
-
-
-

Issues due by this week

-
- {data ? ( - data.issues_due_week_count - ) : ( - - - - )} -
-
+ +
+

Completed issues

+
{data?.completed_issues_count}
+
+ + +
+

Issues due by this week

+
{data?.issues_due_week_count}
+
+
From 10bdcc906c51811b0d5ac2c71031565139631080 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Wed, 27 Dec 2023 16:35:43 +0530 Subject: [PATCH 04/14] fix: adding links to workspace sidebar dropdown --- web/components/workspace/sidebar-dropdown.tsx | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/web/components/workspace/sidebar-dropdown.tsx b/web/components/workspace/sidebar-dropdown.tsx index 8d1cfa27d..cc3c878e4 100644 --- a/web/components/workspace/sidebar-dropdown.tsx +++ b/web/components/workspace/sidebar-dropdown.tsx @@ -16,7 +16,7 @@ import { Avatar, Loader } from "@plane/ui"; import { IWorkspace } from "types"; // Static Data -const userLinks = (workspaceSlug: string, userId: string) => [ +const WORKSPACE_DROPDOWN_ITEMS = (workspaceSlug: string, userId: string) => [ { name: "Workspace Settings", href: `/${workspaceSlug}/settings`, @@ -155,8 +155,8 @@ export const WorkspaceSidebarDropdown = observer(() => { workspaces.map((workspace: IWorkspace) => ( {() => ( - + )} )) @@ -198,17 +198,19 @@ export const WorkspaceSidebarDropdown = observer(() => {

No workspace found!

)}
- { - setTrackElement("APP_SIEDEBAR_WORKSPACE_DROPDOWN"); - router.push("/create-workspace"); - }} - className="flex w-full items-center gap-2 px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80" - > - - Create Workspace + + {() => ( + { + setTrackElement("APP_SIEDEBAR_WORKSPACE_DROPDOWN"); + }} + > + + Create Workspace + + )}
@@ -222,18 +224,20 @@ export const WorkspaceSidebarDropdown = observer(() => { )}
- {userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => ( - { - router.push(link.href); - }} - className="flex w-full cursor-pointer items-center justify-start rounded px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80" - > - {link.name} - - ))} + {WORKSPACE_DROPDOWN_ITEMS(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map( + (link, index) => ( + + {() => ( + + {link.name} + + )} + + ) + )}
Date: Thu, 28 Dec 2023 11:54:16 +0530 Subject: [PATCH 05/14] fix: adding links to the sidebar --- web/components/project/sidebar-list-item.tsx | 43 ++++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/web/components/project/sidebar-list-item.tsx b/web/components/project/sidebar-list-item.tsx index 4ed1bf2c4..56cc6130d 100644 --- a/web/components/project/sidebar-list-item.tsx +++ b/web/components/project/sidebar-list-item.tsx @@ -253,32 +253,31 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { )} {project.archive_in > 0 && ( - router.push(`/${workspaceSlug}/projects/${project?.id}/archived-issues/`)} - > -
- - Archived Issues -
+ + +
+ + Archived Issues +
+
)} - router.push(`/${workspaceSlug}/projects/${project?.id}/draft-issues`)} - > -
- - Draft Issues -
+ + +
+ + Draft Issues +
+
- router.push(`/${workspaceSlug}/projects/${project?.id}/settings`)} - > -
- - Settings -
+ + +
+ + Settings +
+
- {/* leave project */} {isViewerOrGuest && ( From 685e62a72f5a5f40d279ac785525cf26d49e03e8 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Thu, 28 Dec 2023 14:29:20 +0530 Subject: [PATCH 06/14] fix: replacing onclick redirections with link tag. (#3263) * fix: adding links to dashboard summary items * fix: adding links to workspace sidebar dropdown * fix: adding links to the sidebar --- web/components/project/sidebar-list-item.tsx | 43 +++++----- web/components/workspace/issues-stats.tsx | 86 +++++++------------ web/components/workspace/sidebar-dropdown.tsx | 58 +++++++------ 3 files changed, 85 insertions(+), 102 deletions(-) diff --git a/web/components/project/sidebar-list-item.tsx b/web/components/project/sidebar-list-item.tsx index 4ed1bf2c4..56cc6130d 100644 --- a/web/components/project/sidebar-list-item.tsx +++ b/web/components/project/sidebar-list-item.tsx @@ -253,32 +253,31 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { )} {project.archive_in > 0 && ( - router.push(`/${workspaceSlug}/projects/${project?.id}/archived-issues/`)} - > -
- - Archived Issues -
+ + +
+ + Archived Issues +
+
)} - router.push(`/${workspaceSlug}/projects/${project?.id}/draft-issues`)} - > -
- - Draft Issues -
+ + +
+ + Draft Issues +
+
- router.push(`/${workspaceSlug}/projects/${project?.id}/settings`)} - > -
- - Settings -
+ + +
+ + Settings +
+
- {/* leave project */} {isViewerOrGuest && ( diff --git a/web/components/workspace/issues-stats.tsx b/web/components/workspace/issues-stats.tsx index 1fa5cf2df..b8cc8432f 100644 --- a/web/components/workspace/issues-stats.tsx +++ b/web/components/workspace/issues-stats.tsx @@ -7,6 +7,7 @@ import { Info } from "lucide-react"; // types import { IUserWorkspaceDashboard } from "types"; import { useRouter } from "next/router"; +import Link from "next/link"; type Props = { data: IUserWorkspaceDashboard | undefined; @@ -19,61 +20,40 @@ export const IssuesStats: React.FC = ({ data }) => {
-
-

Issues assigned to you

-
- {data ? ( -
router.push(`/${workspaceSlug}/workspace-views/assigned`)} - > - {data.assigned_issues_count} -
- ) : ( - - - - )} -
-
-
-

Pending issues

-
- {data ? ( - data.pending_issues_count - ) : ( - - - - )} -
-
+ +
+

Issues assigned to you

+
+
{data?.assigned_issues_count}
+
+
+ + +
+

Pending issues

+
{data?.pending_issues_count}
+
+
-
-

Completed issues

-
- {data ? ( - data.completed_issues_count - ) : ( - - - - )} -
-
-
-

Issues due by this week

-
- {data ? ( - data.issues_due_week_count - ) : ( - - - - )} -
-
+ +
+

Completed issues

+
{data?.completed_issues_count}
+
+ + +
+

Issues due by this week

+
{data?.issues_due_week_count}
+
+
diff --git a/web/components/workspace/sidebar-dropdown.tsx b/web/components/workspace/sidebar-dropdown.tsx index 8d1cfa27d..cc3c878e4 100644 --- a/web/components/workspace/sidebar-dropdown.tsx +++ b/web/components/workspace/sidebar-dropdown.tsx @@ -16,7 +16,7 @@ import { Avatar, Loader } from "@plane/ui"; import { IWorkspace } from "types"; // Static Data -const userLinks = (workspaceSlug: string, userId: string) => [ +const WORKSPACE_DROPDOWN_ITEMS = (workspaceSlug: string, userId: string) => [ { name: "Workspace Settings", href: `/${workspaceSlug}/settings`, @@ -155,8 +155,8 @@ export const WorkspaceSidebarDropdown = observer(() => { workspaces.map((workspace: IWorkspace) => ( {() => ( - + )} )) @@ -198,17 +198,19 @@ export const WorkspaceSidebarDropdown = observer(() => {

No workspace found!

)}
- { - setTrackElement("APP_SIEDEBAR_WORKSPACE_DROPDOWN"); - router.push("/create-workspace"); - }} - className="flex w-full items-center gap-2 px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80" - > - - Create Workspace + + {() => ( + { + setTrackElement("APP_SIEDEBAR_WORKSPACE_DROPDOWN"); + }} + > + + Create Workspace + + )}
@@ -222,18 +224,20 @@ export const WorkspaceSidebarDropdown = observer(() => { )}
- {userLinks(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map((link, index) => ( - { - router.push(link.href); - }} - className="flex w-full cursor-pointer items-center justify-start rounded px-2 py-1 text-sm text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80" - > - {link.name} - - ))} + {WORKSPACE_DROPDOWN_ITEMS(workspaceSlug?.toString() ?? "", currentUser?.id ?? "").map( + (link, index) => ( + + {() => ( + + {link.name} + + )} + + ) + )}
Date: Thu, 28 Dec 2023 14:30:33 +0530 Subject: [PATCH 07/14] Style/UI improvements (#3269) * style: update `Workspace Issues` -> `All Issues` header. * style: fix issue activity text overflow issue. --- web/components/headers/global-issues.tsx | 2 +- web/components/issues/peek-overview/activity/card.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/components/headers/global-issues.tsx b/web/components/headers/global-issues.tsx index ef4c2b3f5..0b27bdd81 100644 --- a/web/components/headers/global-issues.tsx +++ b/web/components/headers/global-issues.tsx @@ -95,7 +95,7 @@ export const GlobalIssuesHeader: React.FC = observer((props) => { ) } - label={`Workspace ${activeLayout === "spreadsheet" ? "Issues" : "Views"}`} + label={`All ${activeLayout === "spreadsheet" ? "Issues" : "Views"}`} />
diff --git a/web/components/issues/peek-overview/activity/card.tsx b/web/components/issues/peek-overview/activity/card.tsx index b8acb63d9..86d1a138c 100644 --- a/web/components/issues/peek-overview/activity/card.tsx +++ b/web/components/issues/peek-overview/activity/card.tsx @@ -88,7 +88,7 @@ export const IssueActivityCard: FC = (props) => {
-
+
{activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? ( Plane ) : activityItem.actor_detail.is_bot ? ( @@ -101,8 +101,8 @@ export const IssueActivityCard: FC = (props) => { : activityItem.actor_detail.display_name} - )}{" "} - {message}{" "} + )} + {message} Date: Thu, 28 Dec 2023 17:17:04 +0530 Subject: [PATCH 08/14] fix: onboarding redirection loop and bug fixes (#3250) * chore: try and catch added in handleSignInRedirection * chore: remove unnecessary hooks * fix: handleCopyIssueLink url updated * chore: swap next_url with next_path and validate redirection logic for next_path url --- .../auth-screens/not-authorized-view.tsx | 7 ++- .../quick-action-dropdowns/archived-issue.tsx | 2 +- .../quick-action-dropdowns/cycle-issue.tsx | 2 +- .../quick-action-dropdowns/module-issue.tsx | 2 +- .../quick-action-dropdowns/project-issue.tsx | 2 +- web/hooks/use-sign-in-redirection.ts | 60 ++++++++++++------- web/hooks/use-user-auth.tsx | 20 +++++-- web/hooks/use-user.tsx | 2 +- web/layouts/auth-layout/user-wrapper.tsx | 2 +- web/pages/onboarding/index.tsx | 12 ++-- 10 files changed, 69 insertions(+), 42 deletions(-) diff --git a/web/components/auth-screens/not-authorized-view.tsx b/web/components/auth-screens/not-authorized-view.tsx index 353bb8fce..f0a3e3d90 100644 --- a/web/components/auth-screens/not-authorized-view.tsx +++ b/web/components/auth-screens/not-authorized-view.tsx @@ -18,7 +18,8 @@ type Props = { export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { const { user } = useUser(); - const { asPath: currentPath } = useRouter(); + const { query } = useRouter(); + const { next_path } = query; return ( @@ -37,7 +38,7 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { {user ? (

You have signed in as {user.email}.
- + Sign in {" "} with different account that has access to this page. @@ -45,7 +46,7 @@ export const NotAuthorizedView: React.FC = ({ actionButton, type }) => { ) : (

You need to{" "} - + Sign in {" "} with an account that has access to this page. diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx index 07631a7de..133cce1f9 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/archived-issue.tsx @@ -23,7 +23,7 @@ export const ArchivedIssueQuickActions: React.FC = (props) => const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/archived-issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/archived-issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx index 5faace440..af018a652 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/cycle-issue.tsx @@ -27,7 +27,7 @@ export const CycleIssueQuickActions: React.FC = (props) => { const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx index 7bd35e321..0ad1f610b 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/module-issue.tsx @@ -27,7 +27,7 @@ export const ModuleIssueQuickActions: React.FC = (props) => { const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx index 0f4fe28ce..12438b2a3 100644 --- a/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx +++ b/web/components/issues/issue-layouts/quick-action-dropdowns/project-issue.tsx @@ -37,7 +37,7 @@ export const ProjectIssueQuickActions: React.FC = (props) => const { setToastAlert } = useToast(); const handleCopyIssueLink = () => { - copyUrlToClipboard(`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => + copyUrlToClipboard(`${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`).then(() => setToastAlert({ type: "success", title: "Link copied", diff --git a/web/hooks/use-sign-in-redirection.ts b/web/hooks/use-sign-in-redirection.ts index 1863e510e..25d4e8bbd 100644 --- a/web/hooks/use-sign-in-redirection.ts +++ b/web/hooks/use-sign-in-redirection.ts @@ -17,36 +17,54 @@ const useSignInRedirection = (): UseSignInRedirectionProps => { const [error, setError] = useState(null); // router const router = useRouter(); - const { next_url } = router.query; + const { next_path } = router.query; // mobx store const { user: { fetchCurrentUser, fetchCurrentUserSettings }, } = useMobxStore(); + const isValidURL = (url: string): boolean => { + const disallowedSchemes = /^(https?|ftp):\/\//i; + return !disallowedSchemes.test(url); + }; + + console.log("next_path", next_path); + const handleSignInRedirection = useCallback( async (user: IUser) => { - // if the user is not onboarded, redirect them to the onboarding page - if (!user.is_onboarded) { - router.push("/onboarding"); - return; - } - // if next_url is provided, redirect the user to that url - if (next_url) { - router.push(next_url.toString()); - return; - } + try { + // if the user is not onboarded, redirect them to the onboarding page + if (!user.is_onboarded) { + router.push("/onboarding"); + return; + } + // if next_path is provided, redirect the user to that url + if (next_path) { + if (isValidURL(next_path.toString())) { + router.push(next_path.toString()); + return; + } else { + router.push("/"); + return; + } + } - // if the user is onboarded, fetch their last workspace details - await fetchCurrentUserSettings() - .then((userSettings: IUserSettings) => { - const workspaceSlug = - userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; - if (workspaceSlug) router.push(`/${workspaceSlug}`); - else router.push("/profile"); - }) - .catch((err) => setError(err)); + // Fetch the current user settings + const userSettings: IUserSettings = await fetchCurrentUserSettings(); + + // Extract workspace details + const workspaceSlug = + userSettings?.workspace?.last_workspace_slug || userSettings?.workspace?.fallback_workspace_slug; + + // Redirect based on workspace details or to profile if not available + if (workspaceSlug) router.push(`/${workspaceSlug}`); + else router.push("/profile"); + } catch (error) { + console.error("Error in handleSignInRedirection:", error); + setError(error); + } }, - [fetchCurrentUserSettings, router, next_url] + [fetchCurrentUserSettings, router, next_path] ); const updateUserInfo = useCallback(async () => { diff --git a/web/hooks/use-user-auth.tsx b/web/hooks/use-user-auth.tsx index 882c0b713..8290a4545 100644 --- a/web/hooks/use-user-auth.tsx +++ b/web/hooks/use-user-auth.tsx @@ -12,7 +12,7 @@ const workspaceService = new WorkspaceService(); const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "admin") => { const router = useRouter(); - const { next_url } = router.query; + const { next_path } = router.query; const [isRouteAccess, setIsRouteAccess] = useState(true); const { @@ -29,6 +29,11 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm shouldRetryOnError: false, }); + const isValidURL = (url: string): boolean => { + const disallowedSchemes = /^(https?|ftp):\/\//i; + return !disallowedSchemes.test(url); + }; + useEffect(() => { const handleWorkSpaceRedirection = async () => { workspaceService.userWorkspaces().then(async (userWorkspaces) => { @@ -84,8 +89,15 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm if (!isLoading) { setIsRouteAccess(() => true); if (user) { - if (next_url) router.push(next_url.toString()); - else handleUserRouteAuthentication(); + if (next_path) { + if (isValidURL(next_path.toString())) { + router.push(next_path.toString()); + return; + } else { + router.push("/"); + return; + } + } else handleUserRouteAuthentication(); } else { if (routeAuth === "sign-in") { setIsRouteAccess(() => false); @@ -97,7 +109,7 @@ const useUserAuth = (routeAuth: "sign-in" | "onboarding" | "admin" | null = "adm } } } - }, [user, isLoading, routeAuth, router, next_url]); + }, [user, isLoading, routeAuth, router, next_path]); return { isLoading: isRouteAccess, diff --git a/web/hooks/use-user.tsx b/web/hooks/use-user.tsx index 97518fed6..2203faaa2 100644 --- a/web/hooks/use-user.tsx +++ b/web/hooks/use-user.tsx @@ -31,7 +31,7 @@ export default function useUser({ redirectTo = "", redirectIfFound = false, opti ) { router.push(redirectTo); return; - // const nextLocation = router.asPath.split("?next=")[1]; + // const nextLocation = router.asPath.split("?next_path=")[1]; // if (nextLocation) { // router.push(nextLocation as string); // return; diff --git a/web/layouts/auth-layout/user-wrapper.tsx b/web/layouts/auth-layout/user-wrapper.tsx index 50b17fdb7..ccc30a382 100644 --- a/web/layouts/auth-layout/user-wrapper.tsx +++ b/web/layouts/auth-layout/user-wrapper.tsx @@ -56,7 +56,7 @@ export const UserAuthWrapper: FC = observer((props) => { if (currentUserError) { const redirectTo = router.asPath; - router.push(`/?next=${redirectTo}`); + router.push(`/?next_path=${redirectTo}`); return null; } diff --git a/web/pages/onboarding/index.tsx b/web/pages/onboarding/index.tsx index f6df0c4fc..e695b1f4c 100644 --- a/web/pages/onboarding/index.tsx +++ b/web/pages/onboarding/index.tsx @@ -11,8 +11,6 @@ import { Controller, useForm } from "react-hook-form"; import { useMobxStore } from "lib/mobx/store-provider"; // services import { WorkspaceService } from "services/workspace.service"; -// hooks -import useUserAuth from "hooks/use-user-auth"; // layouts import DefaultLayout from "layouts/default-layout"; import { UserAuthWrapper } from "layouts/auth-layout"; @@ -45,8 +43,6 @@ const OnboardingPage: NextPageWithLayout = observer(() => { const { setTheme } = useTheme(); - const {} = useUserAuth("onboarding"); - const { control, setValue } = useForm<{ full_name: string }>({ defaultValues: { full_name: "", @@ -158,8 +154,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => { currentUser?.first_name ? `${currentUser?.first_name} ${currentUser?.last_name ?? ""}` : value.length > 0 - ? value - : currentUser?.email + ? value + : currentUser?.email } src={currentUser?.avatar} size={35} @@ -174,8 +170,8 @@ const OnboardingPage: NextPageWithLayout = observer(() => { {currentUser?.first_name ? `${currentUser?.first_name} ${currentUser?.last_name ?? ""}` : value.length > 0 - ? value - : null} + ? value + : null}

)} From 62b9b259c03a994c08e88457b26ceb956152f563 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Thu, 28 Dec 2023 18:53:06 +0530 Subject: [PATCH 09/14] fix: adding issue title to the activity (#3271) --- web/components/core/activity.tsx | 15 ++------------- web/components/profile/overview/activity.tsx | 8 ++------ 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/web/components/core/activity.tsx b/web/components/core/activity.tsx index 37f8d2626..1ac34cf73 100644 --- a/web/components/core/activity.tsx +++ b/web/components/core/activity.tsx @@ -11,7 +11,6 @@ import { CopyPlus, Calendar, Link2Icon, - RocketIcon, Users2Icon, ArchiveIcon, PaperclipIcon, @@ -48,8 +47,8 @@ const IssueLink = ({ activity }: { activity: IIssueActivity }) => { rel={activity.issue === null ? "" : "noopener noreferrer"} className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > - {activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"} - + {activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"}{" "} + {activity.issue_detail?.name}
); @@ -163,7 +162,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > attachment - {showIssue && ( <> @@ -239,7 +237,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -254,7 +251,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -269,7 +265,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.old_value} - ); @@ -398,7 +393,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > link - {showIssue && ( <> @@ -420,7 +414,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > link - {showIssue && ( <> @@ -442,7 +435,6 @@ const activityDetails: { className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" > link - {showIssue && ( <> @@ -469,7 +461,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -484,7 +475,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.new_value} - ); @@ -499,7 +489,6 @@ const activityDetails: { className="inline-flex items-center gap-1 truncate font-medium text-custom-text-100 hover:underline" > {activity.old_value} - ); diff --git a/web/components/profile/overview/activity.tsx b/web/components/profile/overview/activity.tsx index 1bba6a25f..9512a98f2 100644 --- a/web/components/profile/overview/activity.tsx +++ b/web/components/profile/overview/activity.tsx @@ -1,7 +1,5 @@ import { useRouter } from "next/router"; - import useSWR from "swr"; - // services import { UserService } from "services/user.service"; // components @@ -9,7 +7,6 @@ import { ActivityMessage } from "components/core"; // ui import { ProfileEmptyState } from "components/ui"; import { Loader } from "@plane/ui"; -import { Rocket } from "lucide-react"; // image import recentActivityEmptyState from "public/empty-state/recent_activity.svg"; // helpers @@ -67,10 +64,9 @@ export const ProfileActivity = () => { href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`} target="_blank" rel="noopener noreferrer" - className="inline-flex items-center gap-1 font-medium text-custom-text-100 hover:underline" + className="inline-flex items-center gap-1 font-medium text-custom-text-200 hover:underline" > - Issue - + Issue. )} From 1d5a3a02c14b7397f1b3cffbd7d556e49d358093 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Fri, 29 Dec 2023 15:22:30 +0530 Subject: [PATCH 10/14] fix: issue parent select (#3267) --- .../issues/sidebar-select/parent.tsx | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/web/components/issues/sidebar-select/parent.tsx b/web/components/issues/sidebar-select/parent.tsx index d0a834190..70c104141 100644 --- a/web/components/issues/sidebar-select/parent.tsx +++ b/web/components/issues/sidebar-select/parent.tsx @@ -35,31 +35,34 @@ export const SidebarParentSelect: React.FC = ({ onChange, issueDetails, p issueId={issueId as string} projectId={projectId as string} /> - - + + {issueDetails?.parent && ( + )} - {issueDetails?.parent && } - +
); }; From 10ab081a0b80a5549c7e894b4caafe9a89d5861d Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Fri, 29 Dec 2023 15:24:07 +0530 Subject: [PATCH 11/14] chore: cycle current status (#3270) * dev: cycle status * chore: cycle status logic updated --------- Co-authored-by: Anmol Singh Bhatia --- apiserver/plane/app/serializers/cycle.py | 1 + apiserver/plane/app/views/cycle.py | 34 +++++++++++++++++-- .../cycles/active-cycle-details.tsx | 4 +-- web/components/cycles/cycles-board-card.tsx | 11 ++---- web/components/cycles/cycles-list-item.tsx | 11 ++---- web/components/cycles/gantt-chart/blocks.tsx | 31 ++++++++--------- web/components/cycles/sidebar.tsx | 6 +--- .../cycles/transfer-issues-modal.tsx | 4 +-- .../issue-layouts/roots/cycle-layout-root.tsx | 7 +--- web/helpers/date-time.helper.ts | 12 ------- web/store/cycle/cycles.store.ts | 5 ++- web/types/cycles.d.ts | 3 ++ 12 files changed, 63 insertions(+), 66 deletions(-) diff --git a/apiserver/plane/app/serializers/cycle.py b/apiserver/plane/app/serializers/cycle.py index 104a3dd06..63abf3a03 100644 --- a/apiserver/plane/app/serializers/cycle.py +++ b/apiserver/plane/app/serializers/cycle.py @@ -40,6 +40,7 @@ class CycleSerializer(BaseSerializer): started_estimates = serializers.IntegerField(read_only=True) workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") project_detail = ProjectLiteSerializer(read_only=True, source="project") + status = serializers.CharField(read_only=True) def validate(self, data): if ( diff --git a/apiserver/plane/app/views/cycle.py b/apiserver/plane/app/views/cycle.py index d2f82d75b..02f259de3 100644 --- a/apiserver/plane/app/views/cycle.py +++ b/apiserver/plane/app/views/cycle.py @@ -11,6 +11,10 @@ from django.db.models import ( Count, Prefetch, Sum, + Case, + When, + Value, + CharField ) from django.core import serializers from django.utils import timezone @@ -157,6 +161,28 @@ class CycleViewSet(WebhookMixin, BaseViewSet): ), ) ) + .annotate( + status=Case( + When( + Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()), + then=Value("CURRENT") + ), + When( + start_date__gt=timezone.now(), + then=Value("UPCOMING") + ), + When( + end_date__lt=timezone.now(), + then=Value("COMPLETED") + ), + When( + Q(start_date__isnull=True) & Q(end_date__isnull=True), + then=Value("DRAFT") + ), + default=Value("DRAFT"), + output_field=CharField(), + ) + ) .prefetch_related( Prefetch( "issue_cycle__issue__assignees", @@ -177,7 +203,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): queryset = self.get_queryset() cycle_view = request.GET.get("cycle_view", "all") - queryset = queryset.order_by("-is_favorite","-created_at") + queryset = queryset.order_by("-is_favorite", "-created_at") # Current Cycle if cycle_view == "current": @@ -575,7 +601,9 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet): ) ) - issues = IssueStateSerializer(issues, many=True, fields=fields if fields else None).data + issues = IssueStateSerializer( + issues, many=True, fields=fields if fields else None + ).data issue_dict = {str(issue["id"]): issue for issue in issues} return Response(issue_dict, status=status.HTTP_200_OK) @@ -805,4 +833,4 @@ class TransferCycleIssueEndpoint(BaseAPIView): updated_cycles, ["cycle_id"], batch_size=100 ) - return Response({"message": "Success"}, status=status.HTTP_200_OK) \ No newline at end of file + return Response({"message": "Success"}, status=status.HTTP_200_OK) diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index ea982099f..47beaa262 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -28,7 +28,7 @@ import { ViewIssueLabel } from "components/issues"; // icons import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react"; // helpers -import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; +import { renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types import { ICycle } from "types"; @@ -137,7 +137,7 @@ export const ActiveCycleDetails: React.FC = observer((props cancelled: cycle.cancelled_issues, }; - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase(); const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index f020b0998..d43d56872 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -10,15 +10,10 @@ import { Avatar, AvatarGroup, CustomMenu, Tooltip, LayersIcon, CycleGroupIcon } // icons import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; // helpers -import { - getDateRangeStatus, - findHowManyDaysLeft, - renderShortDate, - renderShortMonthDate, -} from "helpers/date-time.helper"; +import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types -import { ICycle } from "types"; +import { ICycle, TCycleGroups } from "types"; // store import { useMobxStore } from "lib/mobx/store-provider"; // constants @@ -45,7 +40,7 @@ export const CyclesBoardCard: FC = (props) => { const [updateModal, setUpdateModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); // computed - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups; const isCompleted = cycleStatus === "completed"; const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 86b3bffa9..9ea26ab39 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -13,15 +13,10 @@ import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon, AvatarG // icons import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // helpers -import { - getDateRangeStatus, - findHowManyDaysLeft, - renderShortDate, - renderShortMonthDate, -} from "helpers/date-time.helper"; +import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types -import { ICycle } from "types"; +import { ICycle, TCycleGroups } from "types"; // constants import { CYCLE_STATUS } from "constants/cycle"; import { EUserWorkspaceRoles } from "constants/workspace"; @@ -50,7 +45,7 @@ export const CyclesListItem: FC = (props) => { const [updateModal, setUpdateModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); // computed - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups; const isCompleted = cycleStatus === "completed"; const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); diff --git a/web/components/cycles/gantt-chart/blocks.tsx b/web/components/cycles/gantt-chart/blocks.tsx index 03614592c..76a4d9235 100644 --- a/web/components/cycles/gantt-chart/blocks.tsx +++ b/web/components/cycles/gantt-chart/blocks.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; // ui import { Tooltip, ContrastIcon } from "@plane/ui"; // helpers -import { getDateRangeStatus, renderShortDate } from "helpers/date-time.helper"; +import { renderShortDate } from "helpers/date-time.helper"; // types import { ICycle } from "types"; @@ -11,8 +11,7 @@ export const CycleGanttBlock = ({ data }: { data: ICycle }) => { const router = useRouter(); const { workspaceSlug } = router.query; - const cycleStatus = getDateRangeStatus(data?.start_date, data?.end_date); - + const cycleStatus = data.status.toLocaleLowerCase(); return (
{ cycleStatus === "current" ? "#09a953" : cycleStatus === "upcoming" - ? "#f7ae59" - : cycleStatus === "completed" - ? "#3f76ff" - : cycleStatus === "draft" - ? "rgb(var(--color-text-200))" - : "", + ? "#f7ae59" + : cycleStatus === "completed" + ? "#3f76ff" + : cycleStatus === "draft" + ? "rgb(var(--color-text-200))" + : "", }} onClick={() => router.push(`/${workspaceSlug}/projects/${data?.project}/cycles/${data?.id}`)} > @@ -52,7 +51,7 @@ export const CycleGanttSidebarBlock = ({ data }: { data: ICycle }) => { const router = useRouter(); const { workspaceSlug } = router.query; - const cycleStatus = getDateRangeStatus(data?.start_date, data?.end_date); + const cycleStatus = data.status.toLocaleLowerCase(); return (
{ cycleStatus === "current" ? "#09a953" : cycleStatus === "upcoming" - ? "#f7ae59" - : cycleStatus === "completed" - ? "#3f76ff" - : cycleStatus === "draft" - ? "rgb(var(--color-text-200))" - : "" + ? "#f7ae59" + : cycleStatus === "completed" + ? "#3f76ff" + : cycleStatus === "draft" + ? "rgb(var(--color-text-200))" + : "" }`} />
{data?.name}
diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index dcf86bb46..18c233d6c 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -31,7 +31,6 @@ import { import { copyUrlToClipboard } from "helpers/string.helper"; import { findHowManyDaysLeft, - getDateRangeStatus, isDateGreaterThanToday, renderDateFormat, renderShortDate, @@ -275,10 +274,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { [workspaceSlug, projectId, cycleId, issueFilters, updateFilters] ); - const cycleStatus = - cycleDetails?.start_date && cycleDetails?.end_date - ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) - : "draft"; + const cycleStatus = cycleDetails.status.toLocaleLowerCase(); const isCompleted = cycleStatus === "completed"; const isStartValid = new Date(`${cycleDetails?.start_date}`) <= new Date(); diff --git a/web/components/cycles/transfer-issues-modal.tsx b/web/components/cycles/transfer-issues-modal.tsx index 55555e221..dd462e360 100644 --- a/web/components/cycles/transfer-issues-modal.tsx +++ b/web/components/cycles/transfer-issues-modal.tsx @@ -15,8 +15,6 @@ import { AlertCircle, Search, X } from "lucide-react"; import { INCOMPLETE_CYCLES_LIST } from "constants/fetch-keys"; // types import { ICycle } from "types"; -//helper -import { getDateRangeStatus } from "helpers/date-time.helper"; type Props = { isOpen: boolean; @@ -138,7 +136,7 @@ export const TransferIssuesModal: React.FC = observer(({ isOpen, handleCl
{option?.name} - {getDateRangeStatus(option?.start_date, option?.end_date)} + {option.status}
diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx index f967956f0..f77dfbed4 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -17,8 +17,6 @@ import { import { TransferIssues, TransferIssuesModal } from "components/cycles"; // ui import { Spinner } from "@plane/ui"; -// helpers -import { getDateRangeStatus } from "helpers/date-time.helper"; export const CycleLayoutRoot: React.FC = observer(() => { const [transferIssuesModal, setTransferIssuesModal] = useState(false); @@ -50,10 +48,7 @@ export const CycleLayoutRoot: React.FC = observer(() => { const activeLayout = issueFilters?.displayFilters?.layout; const cycleDetails = cycleId ? cycleStore.cycle_details[cycleId.toString()] : undefined; - const cycleStatus = - cycleDetails?.start_date && cycleDetails?.end_date - ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) - : "draft"; + const cycleStatus = cycleDetails?.status.toLocaleLowerCase() ?? "draft"; return ( <> diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index fa346d5aa..ab6116d77 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -170,18 +170,6 @@ export const formatLongDateDistance = (date: string | Date) => { } }; -export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => { - if (!startDate || !endDate) return "draft"; - - const now = new Date(); - const start = new Date(startDate); - const end = new Date(endDate); - - if (start <= now && end >= now) return "current"; - else if (start > now) return "upcoming"; - else return "completed"; -}; - export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: string) => { if (!date || date === "") return null; diff --git a/web/store/cycle/cycles.store.ts b/web/store/cycle/cycles.store.ts index 96122ec14..b6602172d 100644 --- a/web/store/cycle/cycles.store.ts +++ b/web/store/cycle/cycles.store.ts @@ -7,7 +7,6 @@ import { RootStore } from "../root"; import { ProjectService } from "services/project"; import { IssueService } from "services/issue"; import { CycleService } from "services/cycle.service"; -import { getDateRangeStatus } from "helpers/date-time.helper"; export interface ICycleStore { loader: boolean; @@ -318,7 +317,7 @@ export class CycleStore implements ICycleStore { }; addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => { - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status; const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? []; const allCyclesList = this.projectCycles ?? []; @@ -379,7 +378,7 @@ export class CycleStore implements ICycleStore { }; removeCycleFromFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => { - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status; const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? []; const allCyclesList = this.projectCycles ?? []; diff --git a/web/types/cycles.d.ts b/web/types/cycles.d.ts index c3c5248aa..4f243deeb 100644 --- a/web/types/cycles.d.ts +++ b/web/types/cycles.d.ts @@ -2,6 +2,8 @@ import type { IUser, IIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft"; +export type TCycleGroups = "current" | "upcoming" | "completed" | "draft"; + export type TCycleLayout = "list" | "board" | "gantt"; export interface ICycle { @@ -24,6 +26,7 @@ export interface ICycle { owned_by: IUser; project: string; project_detail: IProjectLite; + status: TCycleGroups; sort_order: number; start_date: string | null; started_issues: number; From e584259c04da7876175f78a30ecc7c0f600b5c4e Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Fri, 29 Dec 2023 16:32:12 +0530 Subject: [PATCH 12/14] feat: add external id and external source field to issue, cycle, module, label, comment, state and inbox issues (#3277) --- ...ernal_id_cycle_external_source_and_more.py | 83 +++++++++++++++++++ apiserver/plane/db/models/cycle.py | 2 + apiserver/plane/db/models/inbox.py | 2 + apiserver/plane/db/models/issue.py | 6 ++ apiserver/plane/db/models/module.py | 2 + apiserver/plane/db/models/state.py | 2 + 6 files changed, 97 insertions(+) create mode 100644 apiserver/plane/db/migrations/0051_cycle_external_id_cycle_external_source_and_more.py diff --git a/apiserver/plane/db/migrations/0051_cycle_external_id_cycle_external_source_and_more.py b/apiserver/plane/db/migrations/0051_cycle_external_id_cycle_external_source_and_more.py new file mode 100644 index 000000000..19267dfc2 --- /dev/null +++ b/apiserver/plane/db/migrations/0051_cycle_external_id_cycle_external_source_and_more.py @@ -0,0 +1,83 @@ +# Generated by Django 4.2.7 on 2023-12-29 10:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0050_user_use_case_alter_workspace_organization_size'), + ] + + operations = [ + migrations.AddField( + model_name='cycle', + name='external_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='cycle', + name='external_source', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='inboxissue', + name='external_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='inboxissue', + name='external_source', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issue', + name='external_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issue', + name='external_source', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issuecomment', + name='external_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='issuecomment', + name='external_source', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='label', + name='external_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='label', + name='external_source', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='module', + name='external_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='module', + name='external_source', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='state', + name='external_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='state', + name='external_source', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/apiserver/plane/db/models/cycle.py b/apiserver/plane/db/models/cycle.py index 56301e3d3..e5e2c355b 100644 --- a/apiserver/plane/db/models/cycle.py +++ b/apiserver/plane/db/models/cycle.py @@ -18,6 +18,8 @@ class Cycle(ProjectBaseModel): ) view_props = models.JSONField(default=dict) sort_order = models.FloatField(default=65535) + external_source = models.CharField(max_length=255, null=True, blank=True) + external_id = models.CharField(max_length=255, blank=True, null=True) class Meta: verbose_name = "Cycle" diff --git a/apiserver/plane/db/models/inbox.py b/apiserver/plane/db/models/inbox.py index 497a20f00..6ad88e681 100644 --- a/apiserver/plane/db/models/inbox.py +++ b/apiserver/plane/db/models/inbox.py @@ -39,6 +39,8 @@ class InboxIssue(ProjectBaseModel): "db.Issue", related_name="inbox_duplicate", on_delete=models.SET_NULL, null=True ) source = models.TextField(blank=True, null=True) + external_source = models.CharField(max_length=255, null=True, blank=True) + external_id = models.CharField(max_length=255, blank=True, null=True) class Meta: verbose_name = "InboxIssue" diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 9b293a75d..54acd5c5d 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -102,6 +102,8 @@ class Issue(ProjectBaseModel): completed_at = models.DateTimeField(null=True) archived_at = models.DateField(null=True) is_draft = models.BooleanField(default=False) + external_source = models.CharField(max_length=255, null=True, blank=True) + external_id = models.CharField(max_length=255, blank=True, null=True) objects = models.Manager() issue_objects = IssueManager() @@ -366,6 +368,8 @@ class IssueComment(ProjectBaseModel): default="INTERNAL", max_length=100, ) + external_source = models.CharField(max_length=255, null=True, blank=True) + external_id = models.CharField(max_length=255, blank=True, null=True) def save(self, *args, **kwargs): self.comment_stripped = ( @@ -416,6 +420,8 @@ class Label(ProjectBaseModel): description = models.TextField(blank=True) color = models.CharField(max_length=255, blank=True) sort_order = models.FloatField(default=65535) + external_source = models.CharField(max_length=255, null=True, blank=True) + external_id = models.CharField(max_length=255, blank=True, null=True) class Meta: unique_together = ["name", "project"] diff --git a/apiserver/plane/db/models/module.py b/apiserver/plane/db/models/module.py index ae540cc6c..e485eea62 100644 --- a/apiserver/plane/db/models/module.py +++ b/apiserver/plane/db/models/module.py @@ -41,6 +41,8 @@ class Module(ProjectBaseModel): ) view_props = models.JSONField(default=dict) sort_order = models.FloatField(default=65535) + external_source = models.CharField(max_length=255, null=True, blank=True) + external_id = models.CharField(max_length=255, blank=True, null=True) class Meta: unique_together = ["name", "project"] diff --git a/apiserver/plane/db/models/state.py b/apiserver/plane/db/models/state.py index 2fa1ebe38..3370f239d 100644 --- a/apiserver/plane/db/models/state.py +++ b/apiserver/plane/db/models/state.py @@ -24,6 +24,8 @@ class State(ProjectBaseModel): max_length=20, ) default = models.BooleanField(default=False) + external_source = models.CharField(max_length=255, null=True, blank=True) + external_id = models.CharField(max_length=255, blank=True, null=True) def __str__(self): """Return name of the state""" From f132fe59aed3b4ea7ada30a1f6b7e8b9816098e7 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Fri, 29 Dec 2023 16:35:03 +0530 Subject: [PATCH 13/14] Chore/user role validation (#3260) * chore: remove `Save View` option for guest/ viewer from project issues header. * chore: allow guest/ viewer to clear applied filters in project issues. * chore: disable `date`, `label` property access for guests/ viewer in all project issue views. * chore: update `Workspace Issues` -> `All Issues` header. * chore: refactor apply/ clear filter implementation in All Issues. * Revert "chore: refactor apply/ clear filter implementation in All Issues." This reverts commit 024822d54f4061eb2686d811a2b87cd0789b6b90. * Revert "chore: allow guest/ viewer to clear applied filters in project issues." This reverts commit 3dae871d23424d55abac95e16522696ad3c2a5c4. * Revert "chore: update `Workspace Issues` -> `All Issues` header." This reverts commit 03f90be188bc6b2f98a780ae22e0a29d9c59268d. * chore: remove `cursor-pointer` style from non actionable issue properties. --- .../applied-filters/roots/project-root.tsx | 16 ++++++++++------ .../issues/issue-layouts/kanban/properties.tsx | 6 +++--- .../issues/issue-layouts/list/properties.tsx | 6 +++--- .../issues/issue-layouts/properties/date.tsx | 3 ++- .../issues/issue-layouts/properties/labels.tsx | 8 ++++++-- web/components/issues/sidebar-select/label.tsx | 4 +++- web/components/issues/view-select/due-date.tsx | 4 ++-- web/components/issues/view-select/start-date.tsx | 4 ++-- 8 files changed, 31 insertions(+), 20 deletions(-) diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx index 6d7369bb9..31317366c 100644 --- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx @@ -1,29 +1,32 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; - // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components import { AppliedFiltersList, SaveFilterView } from "components/issues"; - // types import { IIssueFilterOptions } from "types"; import { EFilterType } from "store/issues/types"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; export const ProjectAppliedFiltersRoot: React.FC = observer(() => { + // router const router = useRouter(); const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string; }; - + // mobx stores const { projectLabel: { projectLabels }, projectState: projectStateStore, projectMember: { projectMembers }, projectIssuesFilter: { issueFilters, updateFilters }, + user: { currentProjectRole }, } = useMobxStore(); - + // derived values + const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; const userFilters = issueFilters?.filters; // filters whose value not null or empty array @@ -73,8 +76,9 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => { members={projectMembers?.map((m) => m.member)} states={projectStateStore.states?.[projectId?.toString() ?? ""]} /> - - + {isEditingAllowed && ( + + )}
); }); diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx index 9590c9068..5be5a12c5 100644 --- a/web/components/issues/issue-layouts/kanban/properties.tsx +++ b/web/components/issues/issue-layouts/kanban/properties.tsx @@ -166,7 +166,7 @@ export const KanBanProperties: React.FC = observer((props) => {/* sub-issues */} {displayProperties && displayProperties?.sub_issue_count && !!issue?.sub_issues_count && ( -
+
{issue.sub_issues_count}
@@ -176,7 +176,7 @@ export const KanBanProperties: React.FC = observer((props) => {/* attachments */} {displayProperties && displayProperties?.attachment_count && !!issue?.attachment_count && ( -
+
{issue.attachment_count}
@@ -186,7 +186,7 @@ export const KanBanProperties: React.FC = observer((props) => {/* link */} {displayProperties && displayProperties?.link && !!issue?.link_count && ( -
+
{issue.link_count}
diff --git a/web/components/issues/issue-layouts/list/properties.tsx b/web/components/issues/issue-layouts/list/properties.tsx index eeff3b273..07129910f 100644 --- a/web/components/issues/issue-layouts/list/properties.tsx +++ b/web/components/issues/issue-layouts/list/properties.tsx @@ -137,7 +137,7 @@ export const ListProperties: FC = observer((props) => { {/* sub-issues */} {displayProperties && displayProperties?.sub_issue_count && !!issue?.sub_issues_count && ( -
+
{issue.sub_issues_count}
@@ -147,7 +147,7 @@ export const ListProperties: FC = observer((props) => { {/* attachments */} {displayProperties && displayProperties?.attachment_count && !!issue?.attachment_count && ( -
+
{issue.attachment_count}
@@ -157,7 +157,7 @@ export const ListProperties: FC = observer((props) => { {/* link */} {displayProperties && displayProperties?.link && !!issue?.link_count && ( -
+
{issue.link_count}
diff --git a/web/components/issues/issue-layouts/properties/date.tsx b/web/components/issues/issue-layouts/properties/date.tsx index cfe3481e3..d0bb29711 100644 --- a/web/components/issues/issue-layouts/properties/date.tsx +++ b/web/components/issues/issue-layouts/properties/date.tsx @@ -61,6 +61,7 @@ export const IssuePropertyDate: React.FC = observer((props) ref={dropdownBtn} className="border-none outline-none" onClick={(e) => e.stopPropagation()} + disabled={disabled} > = observer((props)
diff --git a/web/components/issues/issue-layouts/properties/labels.tsx b/web/components/issues/issue-layouts/properties/labels.tsx index 282268d7b..d0045c3d4 100644 --- a/web/components/issues/issue-layouts/properties/labels.tsx +++ b/web/components/issues/issue-layouts/properties/labels.tsx @@ -128,7 +128,11 @@ export const IssuePropertyLabels: React.FC = observer((pro ))} ) : ( -
+
= observer((pro ) : (
diff --git a/web/components/issues/sidebar-select/label.tsx b/web/components/issues/sidebar-select/label.tsx index ca7abd8be..6340383e9 100644 --- a/web/components/issues/sidebar-select/label.tsx +++ b/web/components/issues/sidebar-select/label.tsx @@ -92,7 +92,9 @@ export const SidebarLabelSelect: React.FC = observer((props) => { return (