From 901a7703ff2008d4ed9803cb024f7f4e2835d9d5 Mon Sep 17 00:00:00 2001 From: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:06:45 +0530 Subject: [PATCH 01/15] fix: project, module, cycle filter store infinite loop (#3994) --- web/store/cycle_filter.store.ts | 16 +++++++++------- web/store/module_filter.store.ts | 16 +++++++++------- web/store/project/project_filter.store.ts | 16 +++++++++------- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/web/store/cycle_filter.store.ts b/web/store/cycle_filter.store.ts index b610741b9..2c57b5e78 100644 --- a/web/store/cycle_filter.store.ts +++ b/web/store/cycle_filter.store.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, makeObservable, runInAction, autorun } from "mobx"; +import { action, computed, observable, makeObservable, runInAction, reaction } from "mobx"; import { computedFn } from "mobx-utils"; import set from "lodash/set"; // types @@ -49,11 +49,13 @@ export class CycleFilterStore implements ICycleFilterStore { // root store this.rootStore = _rootStore; // initialize display filters of the current project - autorun(() => { - const projectId = this.rootStore.app.router.projectId; - if (!projectId) return; - this.initProjectCycleFilters(projectId); - }); + reaction( + () => this.rootStore.app.router.projectId, + (projectId) => { + if (!projectId) return; + this.initProjectCycleFilters(projectId); + } + ); } /** @@ -97,7 +99,7 @@ export class CycleFilterStore implements ICycleFilterStore { active_tab: displayFilters?.active_tab || "active", layout: displayFilters?.layout || "list", }; - this.filters[projectId] = {}; + this.filters[projectId] = this.filters[projectId] ?? {}; }); }; diff --git a/web/store/module_filter.store.ts b/web/store/module_filter.store.ts index 7ead79e7e..ae5d07135 100644 --- a/web/store/module_filter.store.ts +++ b/web/store/module_filter.store.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, makeObservable, runInAction, autorun } from "mobx"; +import { action, computed, observable, makeObservable, runInAction, reaction } from "mobx"; import { computedFn } from "mobx-utils"; import set from "lodash/set"; // types @@ -49,11 +49,13 @@ export class ModuleFilterStore implements IModuleFilterStore { // root store this.rootStore = _rootStore; // initialize display filters of the current project - autorun(() => { - const projectId = this.rootStore.app.router.projectId; - if (!projectId) return; - this.initProjectModuleFilters(projectId); - }); + reaction( + () => this.rootStore.app.router.projectId, + (projectId) => { + if (!projectId) return; + this.initProjectModuleFilters(projectId); + } + ); } /** @@ -98,7 +100,7 @@ export class ModuleFilterStore implements IModuleFilterStore { layout: displayFilters?.layout || "list", order_by: displayFilters?.order_by || "name", }; - this.filters[projectId] = {}; + this.filters[projectId] = this.filters[projectId] ?? {}; }); }; diff --git a/web/store/project/project_filter.store.ts b/web/store/project/project_filter.store.ts index 013f2dff5..7d6aff96f 100644 --- a/web/store/project/project_filter.store.ts +++ b/web/store/project/project_filter.store.ts @@ -1,4 +1,4 @@ -import { action, computed, observable, makeObservable, runInAction, autorun } from "mobx"; +import { action, computed, observable, makeObservable, runInAction, reaction } from "mobx"; import { computedFn } from "mobx-utils"; import set from "lodash/set"; // types @@ -49,11 +49,13 @@ export class ProjectFilterStore implements IProjectFilterStore { // root store this.rootStore = _rootStore; // initialize display filters of the current workspace - autorun(() => { - const workspaceSlug = this.rootStore.app.router.workspaceSlug; - if (!workspaceSlug) return; - this.initWorkspaceFilters(workspaceSlug); - }); + reaction( + () => this.rootStore.app.router.workspaceSlug, + (workspaceSlug) => { + if (!workspaceSlug) return; + this.initWorkspaceFilters(workspaceSlug); + } + ); } /** @@ -96,7 +98,7 @@ export class ProjectFilterStore implements IProjectFilterStore { this.displayFilters[workspaceSlug] = { order_by: displayFilters?.order_by || "created_at", }; - this.filters[workspaceSlug] = {}; + this.filters[workspaceSlug] = this.filters[workspaceSlug] ?? {}; }); }; From 4ea616f1cd0a2a52cbfa50c19c0aaced203a7302 Mon Sep 17 00:00:00 2001 From: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Date: Wed, 20 Mar 2024 18:07:11 +0530 Subject: [PATCH 02/15] fix: Labels overflow in peek, detail view (#3995) --- .../issues/issue-detail/label/create-label.tsx | 17 +++++++++++++---- .../issue-detail/label/select/label-select.tsx | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/web/components/issues/issue-detail/label/create-label.tsx b/web/components/issues/issue-detail/label/create-label.tsx index f20fa4721..160ff0071 100644 --- a/web/components/issues/issue-detail/label/create-label.tsx +++ b/web/components/issues/issue-detail/label/create-label.tsx @@ -7,6 +7,8 @@ import { IIssueLabel } from "@plane/types"; // hooks import { Input, TOAST_TYPE, setToast } from "@plane/ui"; import { useIssueDetail } from "@/hooks/store"; +// helpers +import { cn } from "helpers/common.helper"; // ui // types import { TLabelOperations } from "./root"; @@ -29,6 +31,7 @@ export const LabelCreate: FC = (props) => { // hooks const { issue: { getIssueById }, + peekIssue, } = useIssueDetail(); // state const [isCreateToggle, setIsCreateToggle] = useState(false); @@ -82,13 +85,13 @@ export const LabelCreate: FC = (props) => { {isCreateToggle && ( -
+
( - + <> {value && value?.trim() !== "" && ( @@ -110,8 +113,14 @@ export const LabelCreate: FC = (props) => { leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - - onChange(value.hex)} /> + + onChange(value.hex)} + /> diff --git a/web/components/issues/issue-detail/label/select/label-select.tsx b/web/components/issues/issue-detail/label/select/label-select.tsx index 1bacb34be..2882a1e0e 100644 --- a/web/components/issues/issue-detail/label/select/label-select.tsx +++ b/web/components/issues/issue-detail/label/select/label-select.tsx @@ -56,7 +56,7 @@ export const IssueLabelSelect: React.FC = observer((props) => query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase())); const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: "bottom-start", + placement: "bottom-end", modifiers: [ { name: "preventOverflow", From 621624e29fad978f9c9516a0ad1c90f710546301 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Wed, 20 Mar 2024 18:07:35 +0530 Subject: [PATCH 03/15] [WEB-383] fix: rendering state and members from different projects in sub-issues (#3996) * fix: rendering the state and memebers from different projects in sub issues and exception handling while creating an issue from different project * chore: handled different project issue properties in a new function handler --- .../issues/sub-issues/properties.tsx | 4 +- .../issue/issue-details/sub_issues.store.ts | 46 ++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/web/components/issues/sub-issues/properties.tsx b/web/components/issues/sub-issues/properties.tsx index 33f346b6d..6c611468a 100644 --- a/web/components/issues/sub-issues/properties.tsx +++ b/web/components/issues/sub-issues/properties.tsx @@ -72,8 +72,8 @@ export const IssueProperty: React.FC = (props) => { } disabled={!disabled} multiple - buttonVariant={issue.assignee_ids.length > 0 ? "transparent-without-text" : "border-without-text"} - buttonClassName={issue.assignee_ids.length > 0 ? "hover:bg-transparent px-0" : ""} + buttonVariant={(issue?.assignee_ids || []).length > 0 ? "transparent-without-text" : "border-without-text"} + buttonClassName={(issue?.assignee_ids || []).length > 0 ? "hover:bg-transparent px-0" : ""} />
diff --git a/web/store/issue/issue-details/sub_issues.store.ts b/web/store/issue/issue-details/sub_issues.store.ts index ca1528928..f4bd20772 100644 --- a/web/store/issue/issue-details/sub_issues.store.ts +++ b/web/store/issue/issue-details/sub_issues.store.ts @@ -1,10 +1,9 @@ import concat from "lodash/concat"; import pull from "lodash/pull"; import set from "lodash/set"; +import uniq from "lodash/uniq"; import update from "lodash/update"; import { action, makeObservable, observable, runInAction } from "mobx"; -// services -import { IssueService } from "@/services/issue"; // types import { TIssue, @@ -13,6 +12,9 @@ import { TIssueSubIssuesIdMap, TSubIssuesStateDistribution, } from "@plane/types"; +// services +import { IssueService } from "@/services/issue"; +// store import { IIssueDetail } from "./root.store"; export interface IIssueSubIssuesStoreActions { @@ -48,6 +50,7 @@ export interface IIssueSubIssuesStore extends IIssueSubIssuesStoreActions { subIssuesByIssueId: (issueId: string) => string[] | undefined; subIssueHelpersByIssueId: (issueId: string) => TSubIssueHelpers; // actions + fetchOtherProjectProperties: (workspaceSlug: string, projectIds: string[]) => Promise; setSubIssueHelpers: (parentIssueId: string, key: TSubIssueHelpersKeys, value: string) => void; } @@ -74,6 +77,7 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { updateSubIssue: action, removeSubIssue: action, deleteSubIssue: action, + fetchOtherProjectProperties: action, }); // root store this.rootIssueDetailStore = rootStore; @@ -116,6 +120,12 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { this.rootIssueDetailStore.rootIssueStore.issues.addIssue(subIssues); + // fetch other issues states and members when sub-issues are from different project + if (subIssues && subIssues.length > 0) { + const otherProjectIds = uniq(subIssues.map((issue) => issue.project_id).filter((id) => id !== projectId)); + this.fetchOtherProjectProperties(workspaceSlug, otherProjectIds); + } + runInAction(() => { set(this.subIssuesStateDistribution, parentIssueId, subIssuesStateDistribution); set( @@ -140,6 +150,12 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { const subIssuesStateDistribution = response?.state_distribution; const subIssues = response.sub_issues as TIssue[]; + // fetch other issues states and members when sub-issues are from different project + if (subIssues && subIssues.length > 0) { + const otherProjectIds = uniq(subIssues.map((issue) => issue.project_id).filter((id) => id !== projectId)); + this.fetchOtherProjectProperties(workspaceSlug, otherProjectIds); + } + runInAction(() => { Object.keys(subIssuesStateDistribution).forEach((key) => { const stateGroup = key as keyof TSubIssuesStateDistribution; @@ -292,4 +308,30 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore { throw error; } }; + + fetchOtherProjectProperties = async (workspaceSlug: string, projectIds: string[]) => { + try { + if (projectIds.length > 0) { + for (const projectId of projectIds) { + // fetching other project states + this.rootIssueDetailStore.rootIssueStore.rootStore.state.fetchProjectStates(workspaceSlug, projectId); + // fetching other project members + this.rootIssueDetailStore.rootIssueStore.rootStore.memberRoot.project.fetchProjectMembers( + workspaceSlug, + projectId + ); + // fetching other project labels + this.rootIssueDetailStore.rootIssueStore.rootStore.label.fetchProjectLabels(workspaceSlug, projectId); + // fetching other project cycles + this.rootIssueDetailStore.rootIssueStore.rootStore.cycle.fetchAllCycles(workspaceSlug, projectId); + // fetching other project modules + this.rootIssueDetailStore.rootIssueStore.rootStore.module.fetchModules(workspaceSlug, projectId); + // fetching other project estimates + this.rootIssueDetailStore.rootIssueStore.rootStore.estimate.fetchProjectEstimates(workspaceSlug, projectId); + } + } + } catch (error) { + throw error; + } + }; } From e02fa4d9e7b1e537f1449f88c1df97db67e1c028 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Wed, 20 Mar 2024 18:08:21 +0530 Subject: [PATCH 04/15] fix: autofocus on the title element when we are creating the bulk issues in the issue create modal (#3998) --- web/components/issues/issue-modal/draft-issue-layout.tsx | 3 +++ web/components/issues/issue-modal/form.tsx | 5 ++++- web/components/issues/issue-modal/modal.tsx | 7 ++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/web/components/issues/issue-modal/draft-issue-layout.tsx b/web/components/issues/issue-modal/draft-issue-layout.tsx index c7bf17682..55f8d711f 100644 --- a/web/components/issues/issue-modal/draft-issue-layout.tsx +++ b/web/components/issues/issue-modal/draft-issue-layout.tsx @@ -16,6 +16,7 @@ import { IssueDraftService } from "@/services/issue"; export interface DraftIssueProps { changesMade: Partial | null; data?: Partial; + issueTitleRef: React.MutableRefObject; isCreateMoreToggleEnabled: boolean; onCreateMoreToggleChange: (value: boolean) => void; onChange: (formData: Partial | null) => void; @@ -31,6 +32,7 @@ export const DraftIssueLayout: React.FC = observer((props) => { const { changesMade, data, + issueTitleRef, onChange, onClose, onSubmit, @@ -107,6 +109,7 @@ export const DraftIssueLayout: React.FC = observer((props) => { isCreateMoreToggleEnabled={isCreateMoreToggleEnabled} onCreateMoreToggleChange={onCreateMoreToggleChange} data={data} + issueTitleRef={issueTitleRef} onChange={onChange} onClose={handleClose} onSubmit={onSubmit} diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index 9d11f4e68..b6c773335 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -52,6 +52,7 @@ const defaultValues: Partial = { export interface IssueFormProps { data?: Partial; + issueTitleRef: React.MutableRefObject; isCreateMoreToggleEnabled: boolean; onCreateMoreToggleChange: (value: boolean) => void; onChange?: (formData: Partial | null) => void; @@ -93,6 +94,7 @@ const getTabIndex = (key: string) => TAB_INDICES.findIndex((tabIndex) => tabInde export const IssueFormRoot: FC = observer((props) => { const { data, + issueTitleRef, onChange, onClose, onSubmit, @@ -366,11 +368,12 @@ export const IssueFormRoot: FC = observer((props) => { onChange(e.target.value); handleFormChange(); }} - ref={ref} + ref={issueTitleRef || ref} hasError={Boolean(errors.name)} placeholder="Issue Title" className="w-full resize-none text-xl" tabIndex={getTabIndex("name")} + autoFocus /> )} /> diff --git a/web/components/issues/issue-modal/modal.tsx b/web/components/issues/issue-modal/modal.tsx index b0e08dfd1..609392d4f 100644 --- a/web/components/issues/issue-modal/modal.tsx +++ b/web/components/issues/issue-modal/modal.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { useRouter } from "next/router"; import { Dialog, Transition } from "@headlessui/react"; @@ -46,6 +46,8 @@ export const CreateUpdateIssueModal: React.FC = observer((prop storeType = EIssuesStoreType.PROJECT, isDraft = false, } = props; + // ref + const issueTitleRef = useRef(null); // states const [changesMade, setChangesMade] = useState | null>(null); const [createMore, setCreateMore] = useState(false); @@ -169,6 +171,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop path: router.asPath, }); !createMore && handleClose(); + if (createMore) issueTitleRef && issueTitleRef?.current?.focus(); return response; } catch (error) { setToast({ @@ -268,6 +271,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop cycle_id: data?.cycle_id ? data?.cycle_id : cycleId ? cycleId : null, module_ids: data?.module_ids ? data?.module_ids : moduleId ? [moduleId] : null, }} + issueTitleRef={issueTitleRef} onChange={handleFormChange} onClose={handleClose} onSubmit={handleFormSubmit} @@ -278,6 +282,7 @@ export const CreateUpdateIssueModal: React.FC = observer((prop /> ) : ( Date: Wed, 20 Mar 2024 18:08:42 +0530 Subject: [PATCH 05/15] fix: project inifinite loader when we go the workspace projects for the first time (#3999) --- web/components/project/card-list.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/components/project/card-list.tsx b/web/components/project/card-list.tsx index 19830c7c0..4e01dabd4 100644 --- a/web/components/project/card-list.tsx +++ b/web/components/project/card-list.tsx @@ -19,8 +19,6 @@ export const ProjectCardList = observer(() => { const { workspaceProjectIds, filteredProjectIds, getProjectById } = useProject(); const { searchQuery } = useProjectFilter(); - if (!filteredProjectIds) return ; - if (workspaceProjectIds?.length === 0) return ( { }} /> ); + + if (!filteredProjectIds) return ; + if (filteredProjectIds.length === 0) return (
From 7142889c23bb151e1a8036714868bc6e21265e19 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:19:14 +0530 Subject: [PATCH 06/15] [WEB-413] chore: project active cycle UI revamp (#3997) * chore: project active cycle ui revamp * chore: resolved liniting issues --------- Co-authored-by: gurusainath --- .../core/sidebar/progress-chart.tsx | 5 +- .../cycles/active-cycle/cycle-stats.tsx | 260 ++++++++++++ web/components/cycles/active-cycle/header.tsx | 77 ++++ web/components/cycles/active-cycle/index.ts | 4 + .../cycles/active-cycle/productivity.tsx | 46 +++ .../cycles/active-cycle/progress.tsx | 79 ++++ web/components/cycles/active-cycle/root.tsx | 389 ++---------------- .../active-cycle/upcoming-cycles-list.tsx | 44 +- 8 files changed, 533 insertions(+), 371 deletions(-) create mode 100644 web/components/cycles/active-cycle/cycle-stats.tsx create mode 100644 web/components/cycles/active-cycle/header.tsx create mode 100644 web/components/cycles/active-cycle/productivity.tsx create mode 100644 web/components/cycles/active-cycle/progress.tsx diff --git a/web/components/core/sidebar/progress-chart.tsx b/web/components/core/sidebar/progress-chart.tsx index a1c5f3a13..68b1708fe 100644 --- a/web/components/core/sidebar/progress-chart.tsx +++ b/web/components/core/sidebar/progress-chart.tsx @@ -12,6 +12,7 @@ type Props = { startDate: string | Date; endDate: string | Date; totalIssues: number; + className?: string; }; const styleById = { @@ -40,7 +41,7 @@ const DashedLine = ({ series, lineGenerator, xScale, yScale }: any) => /> )); -const ProgressChart: React.FC = ({ distribution, startDate, endDate, totalIssues }) => { +const ProgressChart: React.FC = ({ distribution, startDate, endDate, totalIssues, className = "" }) => { const chartData = Object.keys(distribution ?? []).map((key) => ({ currentDate: renderFormattedDateWithoutYear(key), pending: distribution[key], @@ -73,7 +74,7 @@ const ProgressChart: React.FC = ({ distribution, startDate, endDate, tota }; return ( -
+
= observer((props) => { + const { workspaceSlug, projectId, cycle } = props; + + const { storedValue: tab, setValue: setTab } = useLocalStorage("activeCycleTab", "Assignees"); + + const currentValue = (tab: string | null) => { + switch (tab) { + case "Priority-Issues": + return 0; + case "Assignees": + return 1; + case "Labels": + return 2; + default: + return 0; + } + }; + const { + issues: { fetchActiveCycleIssues }, + } = useIssues(EIssuesStoreType.CYCLE); + + const { currentProjectDetails } = useProject(); + + const { data: activeCycleIssues } = useSWR( + workspaceSlug && projectId && cycle.id ? CYCLE_ISSUES_WITH_PARAMS(cycle.id, { priority: "urgent,high" }) : null, + workspaceSlug && projectId && cycle.id ? () => fetchActiveCycleIssues(workspaceSlug, projectId, cycle.id) : null + ); + + const cycleIssues = activeCycleIssues ?? []; + + return ( +
+ { + switch (i) { + case 0: + return setTab("Priority-Issues"); + case 1: + return setTab("Assignees"); + case 2: + return setTab("Labels"); + + default: + return setTab("Priority-Issues"); + } + }} + > + + + cn( + "relative z-[1] font-semibold text-xs rounded-[3px] py-1.5 text-custom-text-400 focus:outline-none transition duration-500", + { + "text-custom-text-300 bg-custom-background-100": selected, + "hover:text-custom-text-300": !selected, + } + ) + } + > + Priority Issues + + + cn( + "relative z-[1] font-semibold text-xs rounded-[3px] py-1.5 text-custom-text-400 focus:outline-none transition duration-500", + { + "text-custom-text-300 bg-custom-background-100": selected, + "hover:text-custom-text-300": !selected, + } + ) + } + > + Assignees + + + cn( + "relative z-[1] font-semibold text-xs rounded-[3px] py-1.5 text-custom-text-400 focus:outline-none transition duration-500", + { + "text-custom-text-300 bg-custom-background-100": selected, + "hover:text-custom-text-300": !selected, + } + ) + } + > + Labels + + + + + +
+ {cycleIssues ? ( + cycleIssues.length > 0 ? ( + cycleIssues.map((issue: TIssue) => ( + +
+ + + + + {currentProjectDetails?.identifier}-{issue.sequence_id} + + + + {issue.name} + +
+
+ {}} + projectId={projectId?.toString() ?? ""} + disabled + buttonVariant="background-with-text" + buttonContainerClassName="cursor-pointer max-w-24" + showTooltip + /> + {issue.target_date && ( + +
+ + + {renderFormattedDateWithoutYear(issue.target_date)} + +
+
+ )} +
+ + )) + ) : ( +
+ There are no high priority issues present in this cycle. +
+ ) + ) : ( + + + + + + )} +
+
+ + + {cycle.distribution?.assignees?.map((assignee, index) => { + if (assignee.assignee_id) + return ( + + + + {assignee.display_name} +
+ } + completed={assignee.completed_issues} + total={assignee.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} + /> + ))} + + + +
+ ); +}); diff --git a/web/components/cycles/active-cycle/header.tsx b/web/components/cycles/active-cycle/header.tsx new file mode 100644 index 000000000..98ed91c1d --- /dev/null +++ b/web/components/cycles/active-cycle/header.tsx @@ -0,0 +1,77 @@ +import { FC } from "react"; +import Link from "next/link"; +// types +import { ICycle, TCycleGroups } from "@plane/types"; +// ui +import { Tooltip, CycleGroupIcon, getButtonStyling, Avatar, AvatarGroup } from "@plane/ui"; +// helpers +import { renderFormattedDate, findHowManyDaysLeft } from "@/helpers/date-time.helper"; +import { truncateText } from "@/helpers/string.helper"; +// hooks +import { useMember } from "@/hooks/store"; + +export type ActiveCycleHeaderProps = { + cycle: ICycle; + workspaceSlug: string; + projectId: string; +}; + +export const ActiveCycleHeader: FC = (props) => { + const { cycle, workspaceSlug, projectId } = props; + // store + const { getUserDetails } = useMember(); + const cycleOwnerDetails = cycle && cycle.owned_by_id ? getUserDetails(cycle.owned_by_id) : undefined; + + const daysLeft = findHowManyDaysLeft(cycle.end_date) ?? 0; + const currentCycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups; + + const cycleAssignee = (cycle.distribution?.assignees ?? []).filter((assignee) => assignee.display_name); + + return ( +
+
+ + +

{truncateText(cycle.name, 70)}

+
+ + + {`${daysLeft} ${daysLeft > 1 ? "days" : "day"} left`} + + +
+
+
+
+ + {cycleAssignee.length > 0 && ( + + + {cycleAssignee.map((member) => ( + + ))} + + + )} +
+
+ + View Cycle + +
+
+ ); +}; diff --git a/web/components/cycles/active-cycle/index.ts b/web/components/cycles/active-cycle/index.ts index 73d5d1e98..d88ccc3e8 100644 --- a/web/components/cycles/active-cycle/index.ts +++ b/web/components/cycles/active-cycle/index.ts @@ -1,4 +1,8 @@ export * from "./root"; +export * from "./header"; export * from "./stats"; export * from "./upcoming-cycles-list-item"; export * from "./upcoming-cycles-list"; +export * from "./cycle-stats"; +export * from "./progress"; +export * from "./productivity"; diff --git a/web/components/cycles/active-cycle/productivity.tsx b/web/components/cycles/active-cycle/productivity.tsx new file mode 100644 index 000000000..59c2ac3c9 --- /dev/null +++ b/web/components/cycles/active-cycle/productivity.tsx @@ -0,0 +1,46 @@ +import { FC } from "react"; +// types +import { ICycle } from "@plane/types"; +// components +import ProgressChart from "@/components/core/sidebar/progress-chart"; + +export type ActiveCycleProductivityProps = { + cycle: ICycle; +}; + +export const ActiveCycleProductivity: FC = (props) => { + const { cycle } = props; + + return ( +
+
+

Issue burndown

+
+ +
+
+
+
+ + Ideal +
+
+ + Current +
+
+ {`Pending issues - ${cycle.backlog_issues + cycle.unstarted_issues + cycle.started_issues}`} +
+
+ +
+
+
+ ); +}; diff --git a/web/components/cycles/active-cycle/progress.tsx b/web/components/cycles/active-cycle/progress.tsx new file mode 100644 index 000000000..dea3b496a --- /dev/null +++ b/web/components/cycles/active-cycle/progress.tsx @@ -0,0 +1,79 @@ +import { FC } from "react"; +// types +import { ICycle } from "@plane/types"; +// ui +import { LinearProgressIndicator } from "@plane/ui"; +// constants +import { CYCLE_STATE_GROUPS_DETAILS } from "@/constants/cycle"; + +export type ActiveCycleProgressProps = { + cycle: ICycle; +}; + +export const ActiveCycleProgress: FC = (props) => { + const { cycle } = props; + + const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({ + id: index, + name: group.title, + value: cycle.total_issues > 0 ? (cycle[group.key as keyof ICycle] as number) : 0, + color: group.color, + })); + + const groupedIssues: any = { + completed: cycle.completed_issues, + started: cycle.started_issues, + unstarted: cycle.unstarted_issues, + backlog: cycle.backlog_issues, + }; + + return ( +
+
+
+

Progress

+ + {`${cycle.completed_issues + cycle.cancelled_issues}/${cycle.total_issues - cycle.cancelled_issues} ${ + cycle.completed_issues + cycle.cancelled_issues > 1 ? "Issues" : "Issue" + } closed`} + +
+ +
+ +
+ {Object.keys(groupedIssues).map((group, index) => ( + <> + {groupedIssues[group] > 0 && ( +
+
+
+ + {group} +
+ {`${groupedIssues[group]} ${ + groupedIssues[group] > 1 ? "Issues" : "Issue" + }`} +
+
+ )} + + ))} + {cycle.cancelled_issues > 0 && ( + + + {`${cycle.cancelled_issues} cancelled ${ + cycle.cancelled_issues > 1 ? "issues are" : "issue is" + } excluded from this report.`}{" "} + + + )} +
+
+ ); +}; diff --git a/web/components/cycles/active-cycle/root.tsx b/web/components/cycles/active-cycle/root.tsx index 83acd1521..bd2c3b613 100644 --- a/web/components/cycles/active-cycle/root.tsx +++ b/web/components/cycles/active-cycle/root.tsx @@ -1,48 +1,20 @@ -import { MouseEvent } from "react"; import { observer } from "mobx-react-lite"; -import Link from "next/link"; import useSWR from "swr"; -// hooks -import { ArrowRight, CalendarCheck, CalendarDays, Star, Target } from "lucide-react"; -import { ICycle, TCycleGroups } from "@plane/types"; -import { - AvatarGroup, - Loader, - Tooltip, - LinearProgressIndicator, - LayersIcon, - StateGroupIcon, - PriorityIcon, - Avatar, - CycleGroupIcon, - setPromiseToast, - getButtonStyling, -} from "@plane/ui"; -import { SingleProgressStats } from "@/components/core"; // ui +import { Loader } from "@plane/ui"; // components -import ProgressChart from "@/components/core/sidebar/progress-chart"; -import { ActiveCycleProgressStats, UpcomingCyclesList } from "@/components/cycles"; -import { StateDropdown } from "@/components/dropdowns"; -import { EmptyState } from "@/components/empty-state"; -// icons -// helpers -// types -// constants -import { CYCLE_STATE_GROUPS_DETAILS } from "@/constants/cycle"; -import { EmptyStateType } from "@/constants/empty-state"; -import { CYCLE_ISSUES_WITH_PARAMS } from "@/constants/fetch-keys"; -import { EIssuesStoreType } from "@/constants/issue"; -import { cn } from "@/helpers/common.helper"; import { - renderFormattedDate, - findHowManyDaysLeft, - renderFormattedDateWithoutYear, - getDate, -} from "@/helpers/date-time.helper"; -import { truncateText } from "@/helpers/string.helper"; -import { useCycle, useCycleFilter, useIssues, useMember, useProject } from "@/hooks/store"; -import { usePlatformOS } from "@/hooks/use-platform-os"; + ActiveCycleHeader, + ActiveCycleProductivity, + ActiveCycleProgress, + ActiveCycleStats, + UpcomingCyclesList, +} from "@/components/cycles"; +import { EmptyState } from "@/components/empty-state"; +// constants +import { EmptyStateType } from "@/constants/empty-state"; +// hooks +import { useCycle, useCycleFilter } from "@/hooks/store"; interface IActiveCycleDetails { workspaceSlug: string; @@ -52,41 +24,24 @@ interface IActiveCycleDetails { export const ActiveCycleRoot: React.FC = observer((props) => { // props const { workspaceSlug, projectId } = props; - // hooks - const { isMobile } = usePlatformOS(); // store hooks - const { - issues: { fetchActiveCycleIssues }, - } = useIssues(EIssuesStoreType.CYCLE); - const { - currentProjectActiveCycleId, - currentProjectUpcomingCycleIds, - fetchActiveCycle, - getActiveCycleById, - addCycleToFavorites, - removeCycleFromFavorites, - } = useCycle(); - const { currentProjectDetails } = useProject(); - const { getUserDetails } = useMember(); + const { fetchActiveCycle, currentProjectActiveCycleId, currentProjectUpcomingCycleIds, getActiveCycleById } = + useCycle(); // cycle filters hook const { updateDisplayFilters } = useCycleFilter(); // derived values const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null; - const cycleOwnerDetails = activeCycle ? getUserDetails(activeCycle.owned_by_id) : undefined; // fetch active cycle details const { isLoading } = useSWR( workspaceSlug && projectId ? `PROJECT_ACTIVE_CYCLE_${projectId}` : null, workspaceSlug && projectId ? () => fetchActiveCycle(workspaceSlug, projectId) : null ); - // fetch active cycle issues - const { data: activeCycleIssues } = useSWR( - workspaceSlug && projectId && currentProjectActiveCycleId - ? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" }) - : null, - workspaceSlug && projectId && currentProjectActiveCycleId - ? () => fetchActiveCycleIssues(workspaceSlug, projectId, currentProjectActiveCycleId) - : null - ); + + const handleEmptyStateAction = () => + updateDisplayFilters(projectId, { + active_tab: "all", + }); + // show loader if active cycle is loading if (!activeCycle && isLoading) return ( @@ -110,310 +65,28 @@ export const ActiveCycleRoot: React.FC = observer((props) = Create new cycles to find them here or check
{"'"}All{"'"} cycles tab to see all cycles or{" "} -

- + ); } - const endDate = getDate(activeCycle.end_date); - const startDate = getDate(activeCycle.start_date); - const daysLeft = findHowManyDaysLeft(activeCycle.end_date) ?? 0; - const cycleStatus = activeCycle.status.toLowerCase() as TCycleGroups; - - const groupedIssues: any = { - backlog: activeCycle.backlog_issues, - unstarted: activeCycle.unstarted_issues, - started: activeCycle.started_issues, - completed: activeCycle.completed_issues, - cancelled: activeCycle.cancelled_issues, - }; - - const handleAddToFavorites = (e: MouseEvent) => { - e.preventDefault(); - if (!workspaceSlug || !projectId) return; - - const addToFavoritePromise = addCycleToFavorites(workspaceSlug?.toString(), projectId.toString(), activeCycle.id); - - setPromiseToast(addToFavoritePromise, { - loading: "Adding cycle to favorites...", - success: { - title: "Success!", - message: () => "Cycle added to favorites.", - }, - error: { - title: "Error!", - message: () => "Couldn't add the cycle to favorites. Please try again.", - }, - }); - }; - - const handleRemoveFromFavorites = (e: MouseEvent) => { - e.preventDefault(); - if (!workspaceSlug || !projectId) return; - - const removeFromFavoritePromise = removeCycleFromFavorites( - workspaceSlug?.toString(), - projectId.toString(), - activeCycle.id - ); - - setPromiseToast(removeFromFavoritePromise, { - loading: "Removing cycle from favorites...", - success: { - title: "Success!", - message: () => "Cycle removed from favorites.", - }, - error: { - title: "Error!", - message: () => "Couldn't remove the cycle from favorites. Please try again.", - }, - }); - }; - - const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({ - id: index, - name: group.title, - value: - activeCycle.total_issues > 0 - ? ((activeCycle[group.key as keyof ICycle] as number) / activeCycle.total_issues) * 100 - : 0, - color: group.color, - })); - return ( -
-
-
-
-
-
- - - - - -

{truncateText(activeCycle.name, 70)}

-
-
- - - {`${daysLeft} ${daysLeft > 1 ? "days" : "day"} left`} - - {activeCycle.is_favorite ? ( - - ) : ( - - )} - -
- -
-
- - {renderFormattedDate(startDate)} -
- -
- - {renderFormattedDate(endDate)} -
-
- -
-
- - {cycleOwnerDetails?.display_name} -
- - {activeCycle.assignee_ids.length > 0 && ( -
- - {activeCycle.assignee_ids.map((assignee_id) => { - const member = getUserDetails(assignee_id); - return ; - })} - -
- )} -
- -
-
- - {activeCycle.total_issues} issues -
-
- - {activeCycle.completed_issues} issues -
-
- - - View cycle - -
-
-
-
-
-
-
- Progress - -
-
- {Object.keys(groupedIssues).map((group, index) => ( - - - {group} -
- } - completed={groupedIssues[group]} - total={activeCycle.total_issues} - /> - ))} -
-
-
-
- -
+ <> +
+ +
+ + +
-
-
-
High priority issues
-
- {activeCycleIssues ? ( - activeCycleIssues.length > 0 ? ( - activeCycleIssues.map((issue) => ( - -
- - - - - {currentProjectDetails?.identifier}-{issue.sequence_id} - - - - {truncateText(issue.name, 30)} - -
-
- {}} - projectId={projectId} - disabled - buttonVariant="background-with-text" - /> - {issue.target_date && ( - -
- - {renderFormattedDateWithoutYear(issue.target_date)} -
-
- )} -
- - )) - ) : ( -
- There are no high priority issues present in this cycle. -
- ) - ) : ( - - - - - - )} -
-
-
-
-
-
- - Ideal -
-
- - Current -
-
-
- - - - - Pending issues-{" "} - {activeCycle.total_issues - (activeCycle.completed_issues + activeCycle.cancelled_issues)} - -
-
-
- -
-
-
-
+ {currentProjectUpcomingCycleIds && } + ); }); diff --git a/web/components/cycles/active-cycle/upcoming-cycles-list.tsx b/web/components/cycles/active-cycle/upcoming-cycles-list.tsx index c2d8b2388..f4156f341 100644 --- a/web/components/cycles/active-cycle/upcoming-cycles-list.tsx +++ b/web/components/cycles/active-cycle/upcoming-cycles-list.tsx @@ -1,10 +1,16 @@ +import { FC } from "react"; import { observer } from "mobx-react"; -// hooks -import { UpcomingCycleListItem } from "@/components/cycles"; -import { useCycle } from "@/hooks/store"; // components +import { UpcomingCycleListItem } from "@/components/cycles"; +// hooks +import { useCycle } from "@/hooks/store"; -export const UpcomingCyclesList = observer(() => { +type Props = { + handleEmptyStateAction: () => void; +}; + +export const UpcomingCyclesList: FC = observer((props) => { + const { handleEmptyStateAction } = props; // store hooks const { currentProjectUpcomingCycleIds } = useCycle(); @@ -12,14 +18,30 @@ export const UpcomingCyclesList = observer(() => { return (
-
- Upcoming cycles -
-
- {currentProjectUpcomingCycleIds.map((cycleId) => ( - - ))} +
+ Next cycles
+ {currentProjectUpcomingCycleIds.length > 0 ? ( +
+ {currentProjectUpcomingCycleIds.map((cycleId) => ( + + ))} +
+ ) : ( +
+
+
No upcoming cycles
+

+ Create new cycles to find them here or check +
+ {"'"}All{"'"} cycles tab to see all cycles or{" "} + +

+
+
+ )}
); }); From 4eefc7f692e38a03630d7a72a93f80a5d23fa82c Mon Sep 17 00:00:00 2001 From: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:22:02 +0530 Subject: [PATCH 07/15] [WEB-784] chore: create workspace fields as required indication (#4003) * chore: create workspace fields as required indication * style: gap between asterisk & text --- web/components/workspace/create-workspace-form.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/web/components/workspace/create-workspace-form.tsx b/web/components/workspace/create-workspace-form.tsx index ad0c05096..d8a443d4d 100644 --- a/web/components/workspace/create-workspace-form.tsx +++ b/web/components/workspace/create-workspace-form.tsx @@ -121,7 +121,10 @@ export const CreateWorkspaceForm: FC = observer((props) => {
- + = observer((props) => { />
- +
{window && window.location.host}/ = observer((props) => { )}
- What size is your organization? + + What size is your organization?* +
Date: Wed, 20 Mar 2024 19:28:19 +0530 Subject: [PATCH 08/15] dev: add -p flag for the logs folder (#4007) --- apiserver/Dockerfile.api | 2 +- apiserver/Dockerfile.dev | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apiserver/Dockerfile.api b/apiserver/Dockerfile.api index c5113e059..34a50334a 100644 --- a/apiserver/Dockerfile.api +++ b/apiserver/Dockerfile.api @@ -48,7 +48,7 @@ USER root RUN apk --no-cache add "bash~=5.2" COPY ./bin ./bin/ -RUN mkdir /code/plane/logs +RUN mkdir -p /code/plane/logs RUN chmod +x ./bin/takeoff ./bin/worker ./bin/beat RUN chmod -R 777 /code RUN chown -R captain:plane /code diff --git a/apiserver/Dockerfile.dev b/apiserver/Dockerfile.dev index bd6684fd5..06f15231c 100644 --- a/apiserver/Dockerfile.dev +++ b/apiserver/Dockerfile.dev @@ -35,6 +35,7 @@ RUN addgroup -S plane && \ COPY . . +RUN mkdir -p /code/plane/logs RUN chown -R captain.plane /code RUN chmod -R +x /code/bin RUN chmod -R 777 /code From 6ab8588afb7cad15307f4a912e83f14c6b1d3db0 Mon Sep 17 00:00:00 2001 From: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:32:42 +0530 Subject: [PATCH 09/15] fix: create label inline overflow (#4006) --- web/components/labels/project-setting-label-list.tsx | 2 +- .../[workspaceSlug]/projects/[projectId]/settings/labels.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/components/labels/project-setting-label-list.tsx b/web/components/labels/project-setting-label-list.tsx index 9193fdd0b..bbc4023d1 100644 --- a/web/components/labels/project-setting-label-list.tsx +++ b/web/components/labels/project-setting-label-list.tsx @@ -96,7 +96,7 @@ export const ProjectSettingsLabelList: React.FC = observer(() => { Add label
-
+
{showLabelForm && (
{ return ( <> -
+
From 180f1d74e877260f3f87d17515e5bafd33ce2dd3 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Wed, 20 Mar 2024 19:59:31 +0530 Subject: [PATCH 10/15] dev: removed double issue count (#4008) --- apiserver/plane/api/views/module.py | 12 ++++++++++++ apiserver/plane/app/views/cycle/base.py | 5 +++++ apiserver/plane/app/views/module/base.py | 12 ++++++++++++ apiserver/plane/app/views/workspace/module.py | 6 ++++++ 4 files changed, 35 insertions(+) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 460722f99..88bb1b05e 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -67,6 +67,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ), ) .annotate( @@ -77,6 +78,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -87,6 +89,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -97,6 +100,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -107,6 +111,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -117,6 +122,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .order_by(self.kwargs.get("order_by", "-created_at")) @@ -486,6 +492,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ), ) .annotate( @@ -496,6 +503,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -506,6 +514,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -516,6 +525,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -526,6 +536,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -536,6 +547,7 @@ class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .order_by(self.kwargs.get("order_by", "-created_at")) diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py index b70db4c11..f2b493502 100644 --- a/apiserver/plane/app/views/cycle/base.py +++ b/apiserver/plane/app/views/cycle/base.py @@ -107,6 +107,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): issue_cycle__issue__archived_at__isnull=True, issue_cycle__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -117,6 +118,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): issue_cycle__issue__archived_at__isnull=True, issue_cycle__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -127,6 +129,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): issue_cycle__issue__archived_at__isnull=True, issue_cycle__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -137,6 +140,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): issue_cycle__issue__archived_at__isnull=True, issue_cycle__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -147,6 +151,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): issue_cycle__issue__archived_at__isnull=True, issue_cycle__issue__is_draft=False, ), + distinct=True, ) ) .annotate( diff --git a/apiserver/plane/app/views/module/base.py b/apiserver/plane/app/views/module/base.py index f6329c223..2e4b1024d 100644 --- a/apiserver/plane/app/views/module/base.py +++ b/apiserver/plane/app/views/module/base.py @@ -86,6 +86,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ), ) .annotate( @@ -96,6 +97,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -106,6 +108,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -116,6 +119,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -126,6 +130,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -136,6 +141,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -517,6 +523,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ), ) .annotate( @@ -527,6 +534,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -537,6 +545,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -547,6 +556,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -557,6 +567,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -567,6 +578,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( diff --git a/apiserver/plane/app/views/workspace/module.py b/apiserver/plane/app/views/workspace/module.py index fbd760271..8dd5e97f4 100644 --- a/apiserver/plane/app/views/workspace/module.py +++ b/apiserver/plane/app/views/workspace/module.py @@ -45,6 +45,7 @@ class WorkspaceModulesEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ), ) .annotate( @@ -55,6 +56,7 @@ class WorkspaceModulesEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -65,6 +67,7 @@ class WorkspaceModulesEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -75,6 +78,7 @@ class WorkspaceModulesEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -85,6 +89,7 @@ class WorkspaceModulesEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .annotate( @@ -95,6 +100,7 @@ class WorkspaceModulesEndpoint(BaseAPIView): issue_module__issue__archived_at__isnull=True, issue_module__issue__is_draft=False, ), + distinct=True, ) ) .order_by(self.kwargs.get("order_by", "-created_at")) From 054dd2bb7df38715f19b7b10a416ba609242060f Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:00:13 +0530 Subject: [PATCH 11/15] fix: module empty state (#4004) --- web/components/modules/modules-list-view.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/components/modules/modules-list-view.tsx b/web/components/modules/modules-list-view.tsx index a4cb7a652..ce8ce4e65 100644 --- a/web/components/modules/modules-list-view.tsx +++ b/web/components/modules/modules-list-view.tsx @@ -10,6 +10,7 @@ import { CycleModuleBoardLayout, CycleModuleListLayout, GanttLayoutLoader } from // assets // constants import { EmptyStateType } from "@/constants/empty-state"; +import { calculateTotalFilters } from "@/helpers/filter.helper"; import { useApplication, useEventTracker, useModule, useModuleFilter } from "@/hooks/store"; import AllFiltersImage from "public/empty-state/module/all-filters.svg"; import NameFilterImage from "public/empty-state/module/name-filter.svg"; @@ -22,10 +23,12 @@ export const ModulesListView: React.FC = observer(() => { const { commandPalette: commandPaletteStore } = useApplication(); const { setTrackElement } = useEventTracker(); const { getFilteredModuleIds, loader } = useModule(); - const { currentProjectDisplayFilters: displayFilters, searchQuery } = useModuleFilter(); + const { currentProjectDisplayFilters: displayFilters, searchQuery, currentProjectFilters } = useModuleFilter(); // derived values const filteredModuleIds = projectId ? getFilteredModuleIds(projectId.toString()) : undefined; + const totalFilters = calculateTotalFilters(currentProjectFilters ?? {}); + if (loader || !filteredModuleIds) return ( <> @@ -35,7 +38,7 @@ export const ModulesListView: React.FC = observer(() => { ); - if (filteredModuleIds.length === 0) + if (totalFilters > 0 || searchQuery.trim() !== "") return (
From 37c5ce54d558d79835ebf49b5c91ae829556623a Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:00:34 +0530 Subject: [PATCH 12/15] fix: peek overview sub issue operation (#4002) --- .../issues/sub-issues/issue-list-item.tsx | 16 ++++++++++++++-- web/components/issues/sub-issues/root.tsx | 3 +++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/web/components/issues/sub-issues/issue-list-item.tsx b/web/components/issues/sub-issues/issue-list-item.tsx index c3842e6b2..825f19796 100644 --- a/web/components/issues/sub-issues/issue-list-item.tsx +++ b/web/components/issues/sub-issues/issue-list-item.tsx @@ -45,6 +45,8 @@ export const IssueListItem: React.FC = observer((props) => { setPeekIssue, issue: { getIssueById }, subIssues: { subIssueHelpersByIssueId, setSubIssueHelpers }, + toggleCreateIssueModal, + toggleDeleteIssueModal, } = useIssueDetail(); const project = useProject(); const { getProjectStates } = useProjectState(); @@ -139,7 +141,12 @@ export const IssueListItem: React.FC = observer((props) => {
{disabled && ( - handleIssueCrudState("update", parentIssueId, { ...issue })}> + { + handleIssueCrudState("update", parentIssueId, { ...issue }); + toggleCreateIssueModal(true); + }} + >
Edit issue @@ -148,7 +155,12 @@ export const IssueListItem: React.FC = observer((props) => { )} {disabled && ( - handleIssueCrudState("delete", parentIssueId, issue)}> + { + handleIssueCrudState("delete", parentIssueId, issue); + toggleDeleteIssueModal(true); + }} + >
Delete issue diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 9c02fbcc4..b9c40f6e2 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -57,6 +57,7 @@ export const SubIssuesRoot: FC = observer((props) => { toggleCreateIssueModal, isSubIssuesModalOpen, toggleSubIssuesModal, + toggleDeleteIssueModal, } = useIssueDetail(); const { setTrackElement, captureIssueEvent } = useEventTracker(); // state @@ -496,6 +497,7 @@ export const SubIssuesRoot: FC = observer((props) => { isOpen={issueCrudState?.update?.toggle} onClose={() => { handleIssueCrudState("update", null, null); + toggleCreateIssueModal(false); }} data={issueCrudState?.update?.issue ?? undefined} onSubmit={async (_issue: TIssue) => { @@ -521,6 +523,7 @@ export const SubIssuesRoot: FC = observer((props) => { isOpen={issueCrudState?.delete?.toggle} handleClose={() => { handleIssueCrudState("delete", null, null); + toggleDeleteIssueModal(false); }} data={issueCrudState?.delete?.issue as TIssue} onSubmit={async () => From 3ff0f6187a92542844b8fbb570049788b756dc5a Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Wed, 20 Mar 2024 20:41:49 +0530 Subject: [PATCH 13/15] [WEB-762] chore: cycle, module modal improvement and cycle, module (#4000) * chore: cycle modal description improvement * chore: updated the description ui in cycles and modules --------- Co-authored-by: gurusainath --- web/components/cycles/form.tsx | 2 +- web/components/cycles/sidebar.tsx | 14 ++++++++------ web/components/modules/form.tsx | 2 +- web/components/modules/sidebar.tsx | 9 ++++++--- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web/components/cycles/form.tsx b/web/components/cycles/form.tsx index 27b972044..f8092f8d0 100644 --- a/web/components/cycles/form.tsx +++ b/web/components/cycles/form.tsx @@ -113,7 +113,7 @@ export const CycleForm: React.FC = (props) => { id="cycle_description" name="description" placeholder="Description..." - className="!h-24 w-full resize-none text-sm" + className="w-full text-sm resize-none min-h-24" hasError={Boolean(errors?.description)} value={value} onChange={onChange} diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index e142b95ec..ed6fe4b75 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -8,7 +8,7 @@ import { Disclosure, Transition } from "@headlessui/react"; // icons import { ICycle } from "@plane/types"; // ui -import { Avatar, CustomMenu, Loader, LayersIcon, TOAST_TYPE, setToast } from "@plane/ui"; +import { Avatar, CustomMenu, Loader, LayersIcon, TOAST_TYPE, setToast, TextArea } from "@plane/ui"; // components import { SidebarProgressStats } from "@/components/core"; import ProgressChart from "@/components/core/sidebar/progress-chart"; @@ -219,8 +219,8 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { ? "0 Issue" : `${cycleDetails.progress_snapshot.completed_issues}/${cycleDetails.progress_snapshot.total_issues}` : cycleDetails.total_issues === 0 - ? "0 Issue" - : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`; + ? "0 Issue" + : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`; const daysLeft = findHowManyDaysLeft(cycleDetails.end_date); @@ -290,9 +290,11 @@ export const CycleDetailsSidebar: React.FC = observer((props) => {
{cycleDetails.description && ( - - {cycleDetails.description} - +