From b527ec582f968a02ccebd61144243ef50bf146b6 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:02:14 +0530 Subject: [PATCH 1/2] fix: mutation on transfer issues from cycle (#2979) * fix cycle issues mutation on transfering issues * fix transfer issues from cycle --- .../cycles/transfer-issues-modal.tsx | 12 ++++--- .../project-issues/cycle/issue.store.ts | 34 ++++++++++++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/web/components/cycles/transfer-issues-modal.tsx b/web/components/cycles/transfer-issues-modal.tsx index bcbd8efef..55555e221 100644 --- a/web/components/cycles/transfer-issues-modal.tsx +++ b/web/components/cycles/transfer-issues-modal.tsx @@ -2,10 +2,12 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; import useSWR from "swr"; import { Dialog, Transition } from "@headlessui/react"; +import { observer } from "mobx-react-lite"; // services import { CycleService } from "services/cycle.service"; // hooks import useToast from "hooks/use-toast"; +import { useMobxStore } from "lib/mobx/store-provider"; //icons import { ContrastIcon, TransferIcon } from "@plane/ui"; import { AlertCircle, Search, X } from "lucide-react"; @@ -23,17 +25,19 @@ type Props = { const cycleService = new CycleService(); -export const TransferIssuesModal: React.FC = ({ isOpen, handleClose }) => { +export const TransferIssuesModal: React.FC = observer(({ isOpen, handleClose }) => { const [query, setQuery] = useState(""); + const { cycleIssues: cycleIssueStore } = useMobxStore(); + const router = useRouter(); const { workspaceSlug, projectId, cycleId } = router.query; const { setToastAlert } = useToast(); const transferIssue = async (payload: any) => { - await cycleService - .transferIssues(workspaceSlug as string, projectId as string, cycleId as string, payload) + await cycleIssueStore + .transferIssuesFromCycle(workspaceSlug as string, projectId as string, cycleId as string, payload) .then(() => { setToastAlert({ type: "success", @@ -159,4 +163,4 @@ export const TransferIssuesModal: React.FC = ({ isOpen, handleClose }) => ); -}; +}); diff --git a/web/store/issues/project-issues/cycle/issue.store.ts b/web/store/issues/project-issues/cycle/issue.store.ts index 341013ce4..a951914a1 100644 --- a/web/store/issues/project-issues/cycle/issue.store.ts +++ b/web/store/issues/project-issues/cycle/issue.store.ts @@ -62,7 +62,14 @@ export interface ICycleIssuesStore { issueId: string, issueBridgeId: string ) => Promise; - + transferIssuesFromCycle: ( + workspaceSlug: string, + projectId: string, + cycleId: string, + payload: { + new_cycle_id: string; + } + ) => Promise; viewFlags: ViewFlags; } @@ -103,6 +110,7 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor quickAddIssue: action, addIssueToCycle: action, removeIssueFromCycle: action, + transferIssuesFromCycle: action, }); this.rootStore = _rootStore; @@ -348,4 +356,28 @@ export class CycleIssuesStore extends IssueBaseStore implements ICycleIssuesStor throw error; } }; + + transferIssuesFromCycle = async ( + workspaceSlug: string, + projectId: string, + cycleId: string, + payload: { + new_cycle_id: string; + } + ) => { + try { + const response = await this.cycleService.transferIssues( + workspaceSlug as string, + projectId as string, + cycleId as string, + payload + ); + await this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + + return response; + } catch (error) { + this.fetchIssues(workspaceSlug, projectId, "mutation", cycleId); + throw error; + } + }; } From 8a8eea38f92f3400432ce111622d6de0c9823a4d Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:03:23 +0530 Subject: [PATCH 2/2] fix: Permission levels for project settings (#2978) * fix add subgroup issue FED-1101 * fix subgroup by None assignee FED-1100 * fix grouping by asignee or labels FED-1096 * fix create view popup FED-1093 * fix subgroup exception in swimlanes * fix show sub issue filter FED-1102 * use Enums instead of numbers * fix Estimates setting permission for admin * disable access to project settings for viewers and guests * fix project unautorized flicker * add observer to estimates * add permissions to member list --- .../automation/auto-archive-automation.tsx | 7 ++-- .../automation/auto-close-automation.tsx | 7 ++-- web/components/inbox/actions-header.tsx | 3 +- web/components/inbox/main-content.tsx | 5 ++- .../issue-layouts/gantt/base-gantt-root.tsx | 3 +- .../issue-layouts/kanban/base-kanban-root.tsx | 3 +- .../issue-layouts/list/base-list-root.tsx | 3 +- .../spreadsheet/base-spreadsheet-root.tsx | 3 +- .../issue-peek-overview/issue-detail.tsx | 3 +- .../issues/issue-peek-overview/root.tsx | 3 +- web/components/issues/main-content.tsx | 5 ++- web/components/issues/sidebar.tsx | 35 ++++++++++--------- web/components/issues/sub-issues/root.tsx | 3 +- web/components/modules/sidebar.tsx | 9 ++--- web/components/pages/pages-list/list-item.tsx | 3 +- web/components/project/member-list-item.tsx | 9 ++--- .../project-settings-member-defaults.tsx | 3 +- .../project/send-project-invitation-modal.tsx | 5 +-- .../project/settings/features-list.tsx | 5 +-- web/helpers/user.helper.ts | 12 ++++--- .../settings-layout/project/layout.tsx | 18 ++++++++-- .../[projectId]/settings/automations.tsx | 3 +- .../[projectId]/settings/estimates.tsx | 21 ++++++++--- web/store/inbox/inbox_filters.store.ts | 5 +-- 24 files changed, 115 insertions(+), 61 deletions(-) diff --git a/web/components/automation/auto-archive-automation.tsx b/web/components/automation/auto-archive-automation.tsx index 1083073da..dff5450dd 100644 --- a/web/components/automation/auto-archive-automation.tsx +++ b/web/components/automation/auto-archive-automation.tsx @@ -11,6 +11,7 @@ import { ArchiveRestore } from "lucide-react"; import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; // types import { IProject } from "types"; +import { EUserWorkspaceRoles } from "constants/workspace"; type Props = { handleChange: (formData: Partial) => Promise; @@ -28,6 +29,8 @@ export const AutoArchiveAutomation: React.FC = observer((props) => { const projectDetails = projectStore.currentProjectDetails; const userRole = userStore.currentProjectRole; + const isAdmin = userRole === EUserWorkspaceRoles.ADMIN; + return ( <> = observer((props) => { projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 }) } size="sm" - disabled={userRole !== 20} + disabled={!isAdmin} /> @@ -74,7 +77,7 @@ export const AutoArchiveAutomation: React.FC = observer((props) => { }} input width="w-full" - disabled={userRole !== 20} + disabled={!isAdmin} > <> {PROJECT_AUTOMATION_MONTHS.map((month) => ( diff --git a/web/components/automation/auto-close-automation.tsx b/web/components/automation/auto-close-automation.tsx index 1f0ef1c31..30b1bdc0f 100644 --- a/web/components/automation/auto-close-automation.tsx +++ b/web/components/automation/auto-close-automation.tsx @@ -11,6 +11,7 @@ import { ArchiveX } from "lucide-react"; import { IProject } from "types"; // fetch keys import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; +import { EUserWorkspaceRoles } from "constants/workspace"; type Props = { handleChange: (formData: Partial) => Promise; @@ -53,6 +54,8 @@ export const AutoCloseAutomation: React.FC = observer((props) => { default_state: defaultState, }; + const isAdmin = userRole === EUserWorkspaceRoles.ADMIN; + return ( <> = observer((props) => { : handleChange({ close_in: 0, default_state: null }) } size="sm" - disabled={userRole !== 20} + disabled={!isAdmin} /> @@ -102,7 +105,7 @@ export const AutoCloseAutomation: React.FC = observer((props) => { }} input width="w-full" - disabled={userRole !== 20} + disabled={!isAdmin} > <> {PROJECT_AUTOMATION_MONTHS.map((month) => ( diff --git a/web/components/inbox/actions-header.tsx b/web/components/inbox/actions-header.tsx index be077df5c..83cdf1446 100644 --- a/web/components/inbox/actions-header.tsx +++ b/web/components/inbox/actions-header.tsx @@ -22,6 +22,7 @@ import { Button } from "@plane/ui"; import { CheckCircle2, ChevronDown, ChevronUp, Clock, FileStack, Inbox, Trash2, XCircle } from "lucide-react"; // types import type { TInboxStatus } from "types"; +import { EUserWorkspaceRoles } from "constants/workspace"; export const InboxActionsHeader = observer(() => { const [date, setDate] = useState(new Date()); @@ -71,7 +72,7 @@ export const InboxActionsHeader = observer(() => { }, [issue]); const issueStatus = issue?.issue_inbox[0].status; - const isAllowed = userRole === 15 || userRole === 20; + const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER; const today = new Date(); const tomorrow = new Date(today); diff --git a/web/components/inbox/main-content.tsx b/web/components/inbox/main-content.tsx index 02ead34cb..5d7863196 100644 --- a/web/components/inbox/main-content.tsx +++ b/web/components/inbox/main-content.tsx @@ -16,6 +16,7 @@ import { Loader } from "@plane/ui"; import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; // types import { IInboxIssue, IIssue } from "types"; +import { EUserWorkspaceRoles } from "constants/workspace"; const defaultValues: Partial = { name: "", @@ -144,6 +145,8 @@ export const InboxMainContent: React.FC = observer(() => { ); + const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER; + return ( <> {issueDetails ? ( @@ -222,7 +225,7 @@ export const InboxMainContent: React.FC = observer(() => { description_html: issueDetails.description_html, }} handleFormSubmit={submitChanges} - isAllowed={userRole === 15 || userRole === 20 || user?.id === issueDetails.created_by} + isAllowed={isAllowed || user?.id === issueDetails.created_by} /> diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index b2a75e2d3..bf7ed2b42 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -25,6 +25,7 @@ import { IViewIssuesStore, } from "store/issues"; import { TUnGroupedIssues } from "store/issues/types"; +import { EUserWorkspaceRoles } from "constants/workspace"; interface IBaseGanttRoot { issueFiltersStore: @@ -69,7 +70,7 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan ); }; - const isAllowed = currentProjectRole && currentProjectRole >= 15; + const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; return ( <> diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index e63b6e745..66d33ad16 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -31,6 +31,7 @@ import { KanBan } from "./default"; import { KanBanSwimLanes } from "./swimlanes"; import { EProjectStore } from "store/command-palette.store"; import { IssuePeekOverview } from "components/issues"; +import { EUserWorkspaceRoles } from "constants/workspace"; export interface IBaseKanBanLayout { issueStore: @@ -93,7 +94,7 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas } = useMobxStore(); const { currentProjectRole } = userStore; - const isEditingAllowed = [15, 20].includes(currentProjectRole || 0); + const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; const issues = issueStore?.getIssues || {}; const issueIds = issueStore?.getIssuesIds || []; diff --git a/web/components/issues/issue-layouts/list/base-list-root.tsx b/web/components/issues/issue-layouts/list/base-list-root.tsx index 0b20a0ffe..6789ea63c 100644 --- a/web/components/issues/issue-layouts/list/base-list-root.tsx +++ b/web/components/issues/issue-layouts/list/base-list-root.tsx @@ -25,6 +25,7 @@ import { IIssueResponse } from "store/issues/types"; import { EProjectStore } from "store/command-palette.store"; import { IssuePeekOverview } from "components/issues"; import { useRouter } from "next/router"; +import { EUserWorkspaceRoles } from "constants/workspace"; enum EIssueActions { UPDATE = "update", @@ -83,7 +84,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => { } = useMobxStore(); const { currentProjectRole } = userStore; - const isEditingAllowed = [15, 20].includes(currentProjectRole || 0); + const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; const issueIds = issueStore?.getIssuesIds || []; const issues = issueStore?.getIssues; diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx index db0aa37e1..e0b4c8cb5 100644 --- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx @@ -18,6 +18,7 @@ import { observer } from "mobx-react-lite"; import { EFilterType, TUnGroupedIssues } from "store/issues/types"; import { EIssueActions } from "../types"; import { IQuickActionProps } from "../list/list-view-types"; +import { EUserWorkspaceRoles } from "constants/workspace"; interface IBaseSpreadsheetRoot { issueFiltersStore: @@ -49,7 +50,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { } = useMobxStore(); const { currentProjectRole } = userStore; - const isEditingAllowed = [15, 20].includes(currentProjectRole || 0); + const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; const issuesResponse = issueStore.getIssues; const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues; diff --git a/web/components/issues/issue-peek-overview/issue-detail.tsx b/web/components/issues/issue-peek-overview/issue-detail.tsx index 60d20483e..fafdccb71 100644 --- a/web/components/issues/issue-peek-overview/issue-detail.tsx +++ b/web/components/issues/issue-peek-overview/issue-detail.tsx @@ -14,6 +14,7 @@ import { IIssue } from "types"; // services import { FileService } from "services/file.service"; import { useMobxStore } from "lib/mobx/store-provider"; +import { EUserWorkspaceRoles } from "constants/workspace"; const fileService = new FileService(); @@ -32,7 +33,7 @@ export const PeekOverviewIssueDetails: FC = (props) = // store const { user: userStore } = useMobxStore(); const { currentProjectRole } = userStore; - const isAllowed = [15, 20].includes(currentProjectRole || 0); + const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; // states const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [characterLimit, setCharacterLimit] = useState(false); diff --git a/web/components/issues/issue-peek-overview/root.tsx b/web/components/issues/issue-peek-overview/root.tsx index 5e2e0e3cc..09a5202b5 100644 --- a/web/components/issues/issue-peek-overview/root.tsx +++ b/web/components/issues/issue-peek-overview/root.tsx @@ -12,6 +12,7 @@ import { IssueView } from "./view"; import { copyUrlToClipboard } from "helpers/string.helper"; // types import { IIssue } from "types"; +import { EUserWorkspaceRoles } from "constants/workspace"; interface IIssuePeekOverview { workspaceSlug: string; @@ -118,7 +119,7 @@ export const IssuePeekOverview: FC = observer((props) => { } }; - const userRole = userStore.currentProjectRole ?? 5; + const userRole = userStore.currentProjectRole ?? EUserWorkspaceRoles.GUEST; return ( diff --git a/web/components/issues/main-content.tsx b/web/components/issues/main-content.tsx index b8e1ce871..7ea0e7cfe 100644 --- a/web/components/issues/main-content.tsx +++ b/web/components/issues/main-content.tsx @@ -26,6 +26,7 @@ import { MinusCircle } from "lucide-react"; import { IIssue, IIssueComment } from "types"; // fetch-keys import { PROJECT_ISSUES_ACTIVITY, SUB_ISSUES } from "constants/fetch-keys"; +import { EUserWorkspaceRoles } from "constants/workspace"; type Props = { issueDetails: IIssue; @@ -100,6 +101,8 @@ export const IssueMainContent: React.FC = observer((props) => { ); }; + const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER; + return ( <>
@@ -166,7 +169,7 @@ export const IssueMainContent: React.FC = observer((props) => { workspaceSlug={workspaceSlug as string} issue={issueDetails} handleFormSubmit={submitChanges} - isAllowed={userRole === 20 || userRole === 15 || !uneditable} + isAllowed={isAllowed || !uneditable} /> diff --git a/web/components/issues/sidebar.tsx b/web/components/issues/sidebar.tsx index c0bb2da18..d6681cb4a 100644 --- a/web/components/issues/sidebar.tsx +++ b/web/components/issues/sidebar.tsx @@ -40,6 +40,7 @@ import { copyTextToClipboard } from "helpers/string.helper"; import type { IIssue, IIssueLink, linkDetails } from "types"; // fetch-keys import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; +import { EUserWorkspaceRoles } from "constants/workspace"; type Props = { control: any; @@ -245,7 +246,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { setLinkModal(true); }; - const isNotAllowed = userRole === 5 || userRole === 10; + const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER; return ( <> @@ -295,7 +296,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { )} - {!isNotAllowed && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && ( + {isAllowed && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && (
@@ -586,7 +587,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { @@ -605,7 +606,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { issueDetails={issueDetail} labelList={issueDetail?.labels ?? []} submitChanges={submitChanges} - isNotAllowed={isNotAllowed} + isNotAllowed={!isAllowed} uneditable={uneditable ?? false} /> @@ -615,7 +616,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => {

Links

- {!isNotAllowed && ( + {isAllowed && (