From 74ca1876598c592662a781a5fd2fc9412cc6b322 Mon Sep 17 00:00:00 2001 From: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 7 Dec 2023 19:46:57 +0530 Subject: [PATCH] chore: workspace roles (#3024) * chore: workspace project roles for the current user * dev: workspace and project member * chore: store implementation for workspace user projects role * view changes for the project roles * connect workspace member's project roles to assigned --------- Co-authored-by: gurusainath Co-authored-by: rahulramesha Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> --- apiserver/plane/app/urls/project.py | 6 + apiserver/plane/app/views/__init__.py | 1 + apiserver/plane/app/views/project.py | 20 ++- apiserver/plane/license/api/views/instance.py | 33 +++- .../issue-layouts/kanban/base-kanban-root.tsx | 22 ++- .../issues/issue-layouts/kanban/block.tsx | 8 +- .../issue-layouts/kanban/blocks-list.tsx | 6 +- .../issues/issue-layouts/kanban/default.tsx | 24 +-- .../kanban/roots/profile-issues-root.tsx | 9 ++ .../issues/issue-layouts/kanban/swimlanes.tsx | 26 +-- .../issue-layouts/list/base-list-root.tsx | 10 +- .../issues/issue-layouts/list/block.tsx | 8 +- .../issues/issue-layouts/list/blocks-list.tsx | 6 +- .../issues/issue-layouts/list/default.tsx | 26 +-- .../list/roots/profile-issues-root.tsx | 20 ++- .../roots/all-issue-layout-root.tsx | 12 +- .../spreadsheet/base-spreadsheet-root.tsx | 16 +- .../spreadsheet/columns/columns-list.tsx | 28 ++-- .../columns/issue/issue-column.tsx | 6 +- .../issue/spreadsheet-issue-column.tsx | 10 +- .../spreadsheet/spreadsheet-column.tsx | 151 +++++++++--------- .../spreadsheet/spreadsheet-view.tsx | 8 +- web/layouts/auth-layout/workspace-wrapper.tsx | 7 +- web/services/workspace.service.ts | 9 ++ web/store/issues/profile/issue.store.ts | 6 +- web/store/workspace/workspace-member.store.ts | 39 ++++- web/types/users.d.ts | 4 + 27 files changed, 348 insertions(+), 173 deletions(-) diff --git a/apiserver/plane/app/urls/project.py b/apiserver/plane/app/urls/project.py index f1b4200ed..39456a830 100644 --- a/apiserver/plane/app/urls/project.py +++ b/apiserver/plane/app/urls/project.py @@ -13,6 +13,7 @@ from plane.app.views import ( UserProjectInvitationsViewset, ProjectPublicCoverImagesEndpoint, ProjectDeployBoardViewSet, + UserProjectRolesEndpoint, ) @@ -74,6 +75,11 @@ urlpatterns = [ ), name="user-project-invitations", ), + path( + "users/me/workspaces//project-roles/", + UserProjectRolesEndpoint.as_view(), + name="user-project-roles", + ), path( "workspaces//projects//join//", ProjectJoinEndpoint.as_view(), diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 0b5da33ea..c122dce9f 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -11,6 +11,7 @@ from .project import ( ProjectFavoritesViewSet, ProjectPublicCoverImagesEndpoint, ProjectDeployBoardViewSet, + UserProjectRolesEndpoint, ) from .user import ( UserEndpoint, diff --git a/apiserver/plane/app/views/project.py b/apiserver/plane/app/views/project.py index 5a6a9ddd3..2ed82e7e9 100644 --- a/apiserver/plane/app/views/project.py +++ b/apiserver/plane/app/views/project.py @@ -39,6 +39,7 @@ from plane.app.serializers import ( ) from plane.app.permissions import ( + WorkspaceUserPermission, ProjectBasePermission, ProjectMemberPermission, ) @@ -165,7 +166,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet): workspace__slug=slug, is_active=True, ).select_related("member"), - to_attr='members_list' + to_attr="members_list", ) ) .order_by("sort_order", "name") @@ -1049,3 +1050,20 @@ class ProjectDeployBoardViewSet(BaseViewSet): serializer = ProjectDeployBoardSerializer(project_deploy_board) return Response(serializer.data, status=status.HTTP_200_OK) + + +class UserProjectRolesEndpoint(BaseAPIView): + permission_classes = [ + WorkspaceUserPermission, + ] + + def get(self, request, slug): + project_members = ProjectMember.objects.filter( + workspace__slug=slug, + member_id=request.user.id, + ).values("project_id", "role") + + project_members = { + str(member["project_id"]): member["role"] for member in project_members + } + return Response(project_members, status=status.HTTP_200_OK) diff --git a/apiserver/plane/license/api/views/instance.py b/apiserver/plane/license/api/views/instance.py index 1e2d34bd0..0e40b897f 100644 --- a/apiserver/plane/license/api/views/instance.py +++ b/apiserver/plane/license/api/views/instance.py @@ -30,7 +30,7 @@ from plane.license.api.serializers import ( from plane.license.api.permissions import ( InstanceAdminPermission, ) -from plane.db.models import User +from plane.db.models import User, WorkspaceMember, ProjectMember from plane.license.utils.encryption import encrypt_data @@ -221,6 +221,37 @@ class InstanceAdminSignInEndpoint(BaseAPIView): is_password_autoset=False, ) + # if the current user is not using captain then add the current all users to workspace and projects + if user.email != "captain@plane.so": + # Add the current user also as a workspace member and project memeber to all the workspaces and projects + captain = User.objects.filter(email="captain@plane.so") + # Workspace members + workspace_members = WorkspaceMember.objects.filter(member=captain) + WorkspaceMember.objects.bulk_create( + [ + WorkspaceMember( + workspace=member.workspace_id, + member=user, + role=member.role, + ) + for member in workspace_members + ], + batch_size=100, + ) + # project members + project_members = ProjectMember.objects.filter(member=captain) + ProjectMember.objects.bulk_create( + [ + ProjectMember( + workspace=member.workspace_id, + member=user, + role=member.role, + ) + for member in project_members + ], + batch_size=100, + ) + # settings last active for the user user.is_active = True user.last_active = timezone.now() 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 b3a091cb3..4acdb907c 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -68,6 +68,7 @@ export interface IBaseKanBanLayout { issueWithIds: any ) => Promise; addIssuesToView?: (issueIds: string[]) => Promise; + canEditPropertiesBasedOnProject?: (projectId: string) => boolean; } type KanbanDragState = { @@ -88,6 +89,7 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas currentStore, handleDragDrop, addIssuesToView, + canEditPropertiesBasedOnProject, } = props; // router const router = useRouter(); @@ -105,7 +107,6 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas const { setToastAlert } = useToast(); const { currentProjectRole } = userStore; - const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; const issues = issueStore?.getIssues || {}; const issueIds = issueStore?.getIssuesIds || []; @@ -130,6 +131,15 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas const [dragState, setDragState] = useState({}); const [deleteIssueModal, setDeleteIssueModal] = useState(false); + const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; + + const canEditProperties = (projectId: string | undefined) => { + const isEditingAllowedBasedOnProject = + canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed; + + return enableInlineEditing && isEditingAllowedBasedOnProject; + }; + const onDragStart = (dragStart: DragStart) => { setDragState({ draggedIssueId: dragStart.draggableId.split("__")[0], @@ -285,7 +295,7 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas quickAddCallback={issueStore?.quickAddIssue} viewId={viewId} disableIssueCreation={!enableIssueCreation || !isEditingAllowed} - isReadOnly={!enableInlineEditing || !isEditingAllowed} + canEditProperties={canEditProperties} currentStore={currentStore} addIssuesToView={addIssuesToView} /> @@ -327,14 +337,10 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas isDragStarted={isDragStarted} disableIssueCreation={true} enableQuickIssueCreate={enableQuickAdd} - isReadOnly={!enableInlineEditing || !isEditingAllowed} currentStore={currentStore} quickAddCallback={issueStore?.quickAddIssue} - addIssuesToView={(issues) => { - console.log("kanban existingIds", issues); - - return Promise.resolve({} as IIssue); - }} + addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index e86235b36..4644552aa 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -20,7 +20,7 @@ interface IssueBlockProps { handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void; quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | null; - isReadOnly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; } interface IssueDetailsBlockProps { @@ -110,13 +110,15 @@ export const KanbanIssueBlock: React.FC = (props) => { handleIssues, quickActions, displayProperties, - isReadOnly, + canEditProperties, } = props; let draggableId = issue.id; if (columnId) draggableId = `${draggableId}__${columnId}`; if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`; + const canEditIssueProperties = canEditProperties(issue.project); + return ( <> @@ -143,7 +145,7 @@ export const KanbanIssueBlock: React.FC = (props) => { handleIssues={handleIssues} quickActions={quickActions} displayProperties={displayProperties} - isReadOnly={isReadOnly} + isReadOnly={!canEditIssueProperties} /> diff --git a/web/components/issues/issue-layouts/kanban/blocks-list.tsx b/web/components/issues/issue-layouts/kanban/blocks-list.tsx index 3e6728ca2..d72894fb0 100644 --- a/web/components/issues/issue-layouts/kanban/blocks-list.tsx +++ b/web/components/issues/issue-layouts/kanban/blocks-list.tsx @@ -19,7 +19,7 @@ interface IssueBlocksListProps { customActionButton?: React.ReactElement ) => React.ReactNode; displayProperties: IIssueDisplayProperties | null; - isReadOnly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; } export const KanbanIssueBlocksList: React.FC = (props) => { @@ -33,7 +33,7 @@ export const KanbanIssueBlocksList: React.FC = (props) => handleIssues, quickActions, displayProperties, - isReadOnly, + canEditProperties, } = props; return ( @@ -57,7 +57,7 @@ export const KanbanIssueBlocksList: React.FC = (props) => columnId={columnId} sub_group_id={sub_group_id} isDragDisabled={isDragDisabled} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} /> ); })} diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 80d4fdb70..a174e8eea 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -48,7 +48,7 @@ export interface IGroupByKanBan { disableIssueCreation?: boolean; currentStore?: EProjectStore; addIssuesToView?: (issueIds: string[]) => Promise; - isReadOnly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; } const GroupByKanBan: React.FC = observer((props) => { @@ -75,9 +75,9 @@ const GroupByKanBan: React.FC = observer((props) => { quickAddCallback, viewId, disableIssueCreation, - isReadOnly, currentStore, addIssuesToView, + canEditProperties, } = props; const verticalAlignPosition = (_list: any) => @@ -133,7 +133,7 @@ const GroupByKanBan: React.FC = observer((props) => { handleIssues={handleIssues} quickActions={quickActions} displayProperties={displayProperties} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} /> ) : ( isDragDisabled && ( @@ -216,7 +216,7 @@ export interface IKanBan { disableIssueCreation?: boolean; currentStore?: EProjectStore; addIssuesToView?: (issueIds: string[]) => Promise; - isReadOnly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; } export const KanBan: React.FC = observer((props) => { @@ -244,9 +244,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback, viewId, disableIssueCreation, - isReadOnly, currentStore, addIssuesToView, + canEditProperties, } = props; const { issueKanBanView: issueKanBanViewStore } = useMobxStore(); @@ -276,9 +276,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} - isReadOnly={isReadOnly} currentStore={currentStore} addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} @@ -305,9 +305,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} - isReadOnly={isReadOnly} currentStore={currentStore} addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} @@ -334,9 +334,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} - isReadOnly={isReadOnly} currentStore={currentStore} addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} @@ -363,9 +363,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} - isReadOnly={isReadOnly} currentStore={currentStore} addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} @@ -392,9 +392,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} - isReadOnly={isReadOnly} currentStore={currentStore} addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} @@ -421,9 +421,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} - isReadOnly={isReadOnly} currentStore={currentStore} addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} @@ -450,9 +450,9 @@ export const KanBan: React.FC = observer((props) => { quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} - isReadOnly={isReadOnly} currentStore={currentStore} addIssuesToView={addIssuesToView} + canEditProperties={canEditProperties} /> )} diff --git a/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx index affbab2d8..c1466140f 100644 --- a/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx @@ -10,6 +10,7 @@ import { IIssue } from "types"; import { EIssueActions } from "../../types"; import { BaseKanBanRoot } from "../base-kanban-root"; import { EProjectStore } from "store/command-palette.store"; +import { EUserWorkspaceRoles } from "constants/workspace"; export const ProfileIssuesKanBanLayout: React.FC = observer(() => { const router = useRouter(); @@ -18,6 +19,7 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => { const { workspaceProfileIssues: profileIssuesStore, workspaceProfileIssuesFilter: profileIssueFiltersStore, + workspaceMember: { currentWorkspaceUserProjectsRole }, issueKanBanView: issueKanBanViewStore, } = useMobxStore(); @@ -34,6 +36,12 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => { }, }; + const canEditPropertiesBasedOnProject = (projectId: string) => { + const currentProjectRole = currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId]; + + return !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; + }; + return ( { showLoader={true} QuickActions={ProjectIssueQuickActions} currentStore={EProjectStore.PROFILE} + canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject} /> ); }); diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index fa35f4722..4fdddc973 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -95,7 +95,7 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { disableIssueCreation?: boolean; currentStore?: EProjectStore; enableQuickIssueCreate: boolean; - isReadOnly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; quickAddCallback?: ( workspaceSlug: string, projectId: string, @@ -127,7 +127,7 @@ const SubGroupSwimlane: React.FC = observer((props) => { isDragStarted, disableIssueCreation, enableQuickIssueCreate, - isReadOnly, + canEditProperties, addIssuesToView, quickAddCallback, } = props; @@ -186,7 +186,7 @@ const SubGroupSwimlane: React.FC = observer((props) => { projects={projects} enableQuickIssueCreate={enableQuickIssueCreate} isDragStarted={isDragStarted} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} addIssuesToView={addIssuesToView} quickAddCallback={quickAddCallback} /> @@ -232,7 +232,7 @@ export interface IKanBanSwimLanes { data: IIssue, viewId?: string ) => Promise; - isReadOnly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; } export const KanBanSwimLanes: React.FC = observer((props) => { @@ -257,7 +257,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted, disableIssueCreation, enableQuickIssueCreate, - isReadOnly, + canEditProperties, currentStore, addIssuesToView, quickAddCallback, @@ -402,7 +402,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} @@ -431,7 +431,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} @@ -460,7 +460,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} @@ -489,7 +489,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} @@ -518,7 +518,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} @@ -547,7 +547,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} @@ -576,7 +576,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} @@ -605,7 +605,7 @@ export const KanBanSwimLanes: React.FC = observer((props) => { isDragStarted={isDragStarted} disableIssueCreation={disableIssueCreation} enableQuickIssueCreate={enableQuickIssueCreate} - isReadOnly={isReadOnly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} /> )} 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 62349d662..d6dd27be4 100644 --- a/web/components/issues/issue-layouts/list/base-list-root.tsx +++ b/web/components/issues/issue-layouts/list/base-list-root.tsx @@ -58,6 +58,7 @@ interface IBaseListRoot { viewId?: string; currentStore: EProjectStore; addIssuesToView?: (issueIds: string[]) => Promise; + canEditPropertiesBasedOnProject?: (projectId: string) => boolean; } export const BaseListRoot = observer((props: IBaseListRoot) => { @@ -70,6 +71,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => { viewId, currentStore, addIssuesToView, + canEditPropertiesBasedOnProject, } = props; // router const router = useRouter(); @@ -90,6 +92,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => { const issues = issueStore?.getIssues; const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {}; + const canEditProperties = (projectId: string | undefined) => { + const isEditingAllowedBasedOnProject = + canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed; + + return enableInlineEditing && isEditingAllowedBasedOnProject; + }; const displayFilters = issueFilterStore?.issueFilters?.displayFilters; const group_by = displayFilters?.group_by || null; @@ -147,7 +155,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => { viewId={viewId} quickAddCallback={issueStore?.quickAddIssue} enableIssueQuickAdd={!!enableQuickAdd} - isReadonly={!enableInlineEditing || !isEditingAllowed} + canEditProperties={canEditProperties} disableIssueCreation={!enableIssueCreation || !isEditingAllowed} currentStore={currentStore} addIssuesToView={addIssuesToView} diff --git a/web/components/issues/issue-layouts/list/block.tsx b/web/components/issues/issue-layouts/list/block.tsx index 0cad243ad..d7b0a9555 100644 --- a/web/components/issues/issue-layouts/list/block.tsx +++ b/web/components/issues/issue-layouts/list/block.tsx @@ -14,11 +14,11 @@ interface IssueBlockProps { handleIssues: (issue: IIssue, action: EIssueActions) => void; quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | undefined; - isReadonly?: boolean; + canEditProperties: (projectId: string | undefined) => boolean; } export const IssueBlock: React.FC = (props) => { - const { columnId, issue, handleIssues, quickActions, displayProperties, isReadonly } = props; + const { columnId, issue, handleIssues, quickActions, displayProperties, canEditProperties } = props; // router const router = useRouter(); const updateIssue = (group_by: string | null, issueToUpdate: IIssue) => { @@ -34,6 +34,8 @@ export const IssueBlock: React.FC = (props) => { }); }; + const canEditIssueProperties = canEditProperties(issue.project); + return ( <>
@@ -61,7 +63,7 @@ export const IssueBlock: React.FC = (props) => { diff --git a/web/components/issues/issue-layouts/list/blocks-list.tsx b/web/components/issues/issue-layouts/list/blocks-list.tsx index c0e5e6034..c51d37547 100644 --- a/web/components/issues/issue-layouts/list/blocks-list.tsx +++ b/web/components/issues/issue-layouts/list/blocks-list.tsx @@ -10,14 +10,14 @@ interface Props { columnId: string; issueIds: IGroupedIssues | TUnGroupedIssues | any; issues: IIssueResponse; - isReadonly?: boolean; + canEditProperties: (projectId: string | undefined) => boolean; handleIssues: (issue: IIssue, action: EIssueActions) => void; quickActions: (group_by: string | null, issue: IIssue) => React.ReactNode; displayProperties: IIssueDisplayProperties | undefined; } export const IssueBlocksList: FC = (props) => { - const { columnId, issueIds, issues, handleIssues, quickActions, displayProperties, isReadonly } = props; + const { columnId, issueIds, issues, handleIssues, quickActions, displayProperties, canEditProperties } = props; return (
@@ -31,7 +31,7 @@ export const IssueBlocksList: FC = (props) => { issue={issues[issueId]} handleIssues={handleIssues} quickActions={quickActions} - isReadonly={isReadonly} + canEditProperties={canEditProperties} displayProperties={displayProperties} /> ) diff --git a/web/components/issues/issue-layouts/list/default.tsx b/web/components/issues/issue-layouts/list/default.tsx index dacbea2b2..6cb9185d4 100644 --- a/web/components/issues/issue-layouts/list/default.tsx +++ b/web/components/issues/issue-layouts/list/default.tsx @@ -23,7 +23,7 @@ export interface IGroupByList { displayProperties: IIssueDisplayProperties | undefined; enableIssueQuickAdd: boolean; showEmptyGroup?: boolean; - isReadonly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; quickAddCallback?: ( workspaceSlug: string, projectId: string, @@ -50,7 +50,7 @@ const GroupByList: React.FC = (props) => { displayProperties, enableIssueQuickAdd, showEmptyGroup, - isReadonly, + canEditProperties, quickAddCallback, viewId, disableIssueCreation, @@ -105,7 +105,7 @@ const GroupByList: React.FC = (props) => { handleIssues={handleIssues} quickActions={quickActions} displayProperties={displayProperties} - isReadonly={isReadonly} + canEditProperties={canEditProperties} /> )} @@ -134,7 +134,7 @@ export interface IList { displayProperties: IIssueDisplayProperties | undefined; showEmptyGroup: boolean; enableIssueQuickAdd: boolean; - isReadonly: boolean; + canEditProperties: (projectId: string | undefined) => boolean; states: IState[] | null; labels: IIssueLabel[] | null; members: IUserLite[] | null; @@ -165,7 +165,7 @@ export const List: React.FC = (props) => { displayProperties, showEmptyGroup, enableIssueQuickAdd, - isReadonly, + canEditProperties, disableIssueCreation, states, stateGroups, @@ -193,7 +193,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} enableIssueQuickAdd={enableIssueQuickAdd} showEmptyGroup={showEmptyGroup} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} @@ -215,7 +215,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} showEmptyGroup={showEmptyGroup} enableIssueQuickAdd={enableIssueQuickAdd} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} @@ -237,7 +237,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} showEmptyGroup={showEmptyGroup} enableIssueQuickAdd={enableIssueQuickAdd} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} @@ -259,7 +259,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} showEmptyGroup={showEmptyGroup} enableIssueQuickAdd={enableIssueQuickAdd} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} @@ -281,7 +281,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} showEmptyGroup={showEmptyGroup} enableIssueQuickAdd={enableIssueQuickAdd} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} @@ -303,7 +303,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} showEmptyGroup={showEmptyGroup} enableIssueQuickAdd={enableIssueQuickAdd} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} @@ -325,7 +325,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} showEmptyGroup={showEmptyGroup} enableIssueQuickAdd={enableIssueQuickAdd} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} @@ -347,7 +347,7 @@ export const List: React.FC = (props) => { displayProperties={displayProperties} showEmptyGroup={showEmptyGroup} enableIssueQuickAdd={enableIssueQuickAdd} - isReadonly={isReadonly} + canEditProperties={canEditProperties} quickAddCallback={quickAddCallback} viewId={viewId} disableIssueCreation={disableIssueCreation} diff --git a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx index 933fef331..eedf7ae81 100644 --- a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx @@ -12,14 +12,18 @@ import { EIssueActions } from "../../types"; import { BaseListRoot } from "../base-list-root"; import { IProjectStore } from "store/project"; import { EProjectStore } from "store/command-palette.store"; +import { EUserWorkspaceRoles } from "constants/workspace"; export const ProfileIssuesListLayout: FC = observer(() => { const router = useRouter(); const { workspaceSlug, userId } = router.query as { workspaceSlug: string; userId: string }; // store - const { workspaceProfileIssuesFilter: profileIssueFiltersStore, workspaceProfileIssues: profileIssuesStore } = - useMobxStore(); + const { + workspaceProfileIssuesFilter: profileIssueFiltersStore, + workspaceProfileIssues: profileIssuesStore, + workspaceMember: { currentWorkspaceUserProjectsRole }, + } = useMobxStore(); const issueActions = { [EIssueActions.UPDATE]: async (group_by: string | null, issue: IIssue) => { @@ -36,6 +40,17 @@ export const ProfileIssuesListLayout: FC = observer(() => { const getProjects = (projectStore: IProjectStore) => projectStore.workspaceProjects; + const canEditPropertiesBasedOnProject = (projectId: string) => { + const currentProjectRole = currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId]; + + console.log( + projectId, + currentWorkspaceUserProjectsRole, + !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER + ); + return !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; + }; + return ( { issueActions={issueActions} getProjects={getProjects} currentStore={EProjectStore.PROFILE} + canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject} /> ); }); diff --git a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx index a45c8d867..84a8402d6 100644 --- a/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/all-issue-layout-root.tsx @@ -16,6 +16,7 @@ import { IIssueUnGroupedStructure } from "store/issue"; import { EIssueActions } from "../types"; import { EFilterType, TUnGroupedIssues } from "store/issues/types"; +import { EUserWorkspaceRoles } from "constants/workspace"; type Props = { type?: TStaticViewTypes | null; @@ -35,6 +36,7 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { globalViews: { fetchAllGlobalViews }, workspaceGlobalIssues: { loader, getIssues, getIssuesIds, fetchIssues, updateIssue, removeIssue }, workspaceGlobalIssuesFilter: { currentView, issueFilters, fetchFilters, updateFilters, setCurrentView }, + workspaceMember: { currentWorkspaceUserProjectsRole }, } = useMobxStore(); useSWR(workspaceSlug ? `WORKSPACE_GLOBAL_VIEWS${workspaceSlug}` : null, async () => { @@ -55,7 +57,13 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { } ); - const isEditingAllowed = false; + const canEditProperties = (projectId: string | undefined) => { + if (!projectId) return false; + + const currentProjectRole = currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId]; + + return !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; + }; const issuesResponse = getIssues; const issueIds = (getIssuesIds ?? []) as TUnGroupedIssues; @@ -123,7 +131,7 @@ export const AllIssueLayoutRoot: React.FC = observer((props) => { members={workspaceMembers?.map((m) => m.member)} labels={workspaceLabels || undefined} handleIssues={handleIssues} - disableUserActions={isEditingAllowed} + canEditProperties={canEditProperties} viewId={currentIssueView} />
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 a995f0716..090522cb0 100644 --- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx @@ -34,10 +34,11 @@ interface IBaseSpreadsheetRoot { [EIssueActions.UPDATE]?: (issue: IIssue) => void; [EIssueActions.REMOVE]?: (issue: IIssue) => void; }; + canEditPropertiesBasedOnProject?: (projectId: string) => boolean; } export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { - const { issueFiltersStore, issueStore, viewId, QuickActions, issueActions } = props; + const { issueFiltersStore, issueStore, viewId, QuickActions, issueActions, canEditPropertiesBasedOnProject } = props; const router = useRouter(); const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; @@ -49,9 +50,18 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { user: userStore, } = useMobxStore(); + const { enableInlineEditing, enableQuickAdd } = issueStore?.viewFlags || {}; + const { currentProjectRole } = userStore; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; + const canEditProperties = (projectId: string | undefined) => { + const isEditingAllowedBasedOnProject = + canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed; + + return enableInlineEditing && isEditingAllowedBasedOnProject; + }; + const issuesResponse = issueStore.getIssues; const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues; @@ -106,10 +116,10 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => { labels={projectLabels || undefined} states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined} handleIssues={handleIssues} - disableUserActions={!isEditingAllowed} + canEditProperties={canEditProperties} quickAddCallback={issueStore.quickAddIssue} viewId={viewId} - enableQuickCreateIssue + enableQuickCreateIssue={enableQuickAdd} /> ); }); diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx index 540dbc5ce..c71343cfc 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/columns-list.tsx @@ -8,7 +8,7 @@ import { IIssue, IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueLabe type Props = { displayFilters: IIssueDisplayFilterOptions; displayProperties: IIssueDisplayProperties; - disableUserActions: boolean; + canEditProperties: (projectId: string | undefined) => boolean; expandedIssues: string[]; handleDisplayFilterUpdate: (data: Partial) => void; handleUpdateIssue: (issue: IIssue, data: Partial) => void; @@ -20,7 +20,7 @@ type Props = { export const SpreadsheetColumnsList: React.FC = observer((props) => { const { - disableUserActions, + canEditProperties, displayFilters, displayProperties, expandedIssues, @@ -43,7 +43,7 @@ export const SpreadsheetColumnsList: React.FC = observer((props) => { {displayProperties.state && ( = observer((props) => { {displayProperties.priority && ( = observer((props) => { {displayProperties.assignee && ( = observer((props) => { {displayProperties.labels && ( = observer((props) => { {displayProperties.start_date && ( = observer((props) => { {displayProperties.due_date && ( = observer((props) => { {displayProperties.estimate && isEstimateEnabled && ( = observer((props) => { {displayProperties.created_on && ( = observer((props) => { {displayProperties.updated_on && ( = observer((props) => { {displayProperties.link && ( = observer((props) => { {displayProperties.attachment_count && ( = observer((props) => { {displayProperties.sub_issue_count && ( void; properties: IIssueDisplayProperties; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; - disableUserActions: boolean; + canEditProperties: (projectId: string | undefined) => boolean; nestingLevel: number; }; @@ -24,7 +24,7 @@ export const IssueColumn: React.FC = ({ handleToggleExpand, properties, quickActions, - disableUserActions, + canEditProperties, nestingLevel, }) => { // router @@ -76,7 +76,7 @@ export const IssueColumn: React.FC = ({ {issue.project_detail?.identifier}-{issue.sequence_id} - {!disableUserActions && ( + {canEditProperties(issue.project) && ( diff --git a/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx b/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx index d368b4f1c..738880d65 100644 --- a/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/columns/issue/spreadsheet-issue-column.tsx @@ -12,8 +12,8 @@ type Props = { expandedIssues: string[]; setExpandedIssues: React.Dispatch>; properties: IIssueDisplayProperties; - quickActions: (issue: IIssue,customActionButton?: React.ReactElement) => React.ReactNode; - disableUserActions: boolean; + quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; + canEditProperties: (projectId: string | undefined) => boolean; nestingLevel?: number; }; @@ -23,7 +23,7 @@ export const SpreadsheetIssuesColumn: React.FC = ({ setExpandedIssues, properties, quickActions, - disableUserActions, + canEditProperties, nestingLevel = 0, }) => { const handleToggleExpand = (issueId: string) => { @@ -49,7 +49,7 @@ export const SpreadsheetIssuesColumn: React.FC = ({ expanded={isExpanded} handleToggleExpand={handleToggleExpand} properties={properties} - disableUserActions={disableUserActions} + canEditProperties={canEditProperties} nestingLevel={nestingLevel} quickActions={quickActions} /> @@ -66,7 +66,7 @@ export const SpreadsheetIssuesColumn: React.FC = ({ setExpandedIssues={setExpandedIssues} properties={properties} quickActions={quickActions} - disableUserActions={disableUserActions} + canEditProperties={canEditProperties} nestingLevel={nestingLevel + 1} /> ))} diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx index ca093eb73..c59b65d9c 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-column.tsx @@ -32,7 +32,7 @@ import { IIssue, IIssueDisplayFilterOptions, IIssueLabel, IState, IUserLite, TIs import { SPREADSHEET_PROPERTY_DETAILS } from "constants/spreadsheet"; type Props = { - disableUserActions: boolean; + canEditProperties: (projectId: string | undefined) => boolean; displayFilters: IIssueDisplayFilterOptions; expandedIssues: string[]; handleDisplayFilterUpdate: (data: Partial) => void; @@ -46,7 +46,7 @@ type Props = { export const SpreadsheetColumn: React.FC = (props) => { const { - disableUserActions, + canEditProperties, displayFilters, expandedIssues, handleDisplayFilterUpdate, @@ -160,78 +160,81 @@ export const SpreadsheetColumn: React.FC = (props) => {
- {issues?.map((issue) => ( -
- {property === "state" ? ( - ) => handleUpdateIssue(issue, data)} - states={states} - /> - ) : property === "priority" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "estimate" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "assignee" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "labels" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "start_date" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "due_date" ? ( - ) => handleUpdateIssue(issue, data)} - /> - ) : property === "created_on" ? ( - - ) : property === "updated_on" ? ( - - ) : property === "link" ? ( - - ) : property === "attachment_count" ? ( - - ) : property === "sub_issue_count" ? ( - - ) : null} -
- ))} + {issues?.map((issue) => { + const disableUserActions = !canEditProperties(issue.project); + return ( +
+ {property === "state" ? ( + ) => handleUpdateIssue(issue, data)} + states={states} + /> + ) : property === "priority" ? ( + ) => handleUpdateIssue(issue, data)} + /> + ) : property === "estimate" ? ( + ) => handleUpdateIssue(issue, data)} + /> + ) : property === "assignee" ? ( + ) => handleUpdateIssue(issue, data)} + /> + ) : property === "labels" ? ( + ) => handleUpdateIssue(issue, data)} + /> + ) : property === "start_date" ? ( + ) => handleUpdateIssue(issue, data)} + /> + ) : property === "due_date" ? ( + ) => handleUpdateIssue(issue, data)} + /> + ) : property === "created_on" ? ( + + ) : property === "updated_on" ? ( + + ) : property === "link" ? ( + + ) : property === "attachment_count" ? ( + + ) : property === "sub_issue_count" ? ( + + ) : null} +
+ ); + })}
); diff --git a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx index 8eb98d512..b2155cdca 100644 --- a/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/spreadsheet-view.tsx @@ -31,7 +31,7 @@ type Props = { viewId?: string ) => Promise; viewId?: string; - disableUserActions: boolean; + canEditProperties: (projectId: string | undefined) => boolean; enableQuickCreateIssue?: boolean; }; @@ -48,7 +48,7 @@ export const SpreadsheetView: React.FC = observer((props) => { handleIssues, quickAddCallback, viewId, - disableUserActions, + canEditProperties, enableQuickCreateIssue, } = props; // states @@ -114,7 +114,7 @@ export const SpreadsheetView: React.FC = observer((props) => { setExpandedIssues={setExpandedIssues} properties={displayProperties} quickActions={quickActions} - disableUserActions={disableUserActions} + canEditProperties={canEditProperties} /> ) : null )} @@ -124,7 +124,7 @@ export const SpreadsheetView: React.FC = observer((props) => { handleIssues({ ...issue, ...data }, EIssueActions.UPDATE)} diff --git a/web/layouts/auth-layout/workspace-wrapper.tsx b/web/layouts/auth-layout/workspace-wrapper.tsx index 622e9c23e..9a3bc09f0 100644 --- a/web/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/layouts/auth-layout/workspace-wrapper.tsx @@ -19,7 +19,7 @@ export const WorkspaceAuthWrapper: FC = observer((props) user: { currentWorkspaceMemberInfo, hasPermissionToCurrentWorkspace, fetchUserWorkspaceInfo }, project: { fetchProjects }, workspace: { fetchWorkspaceLabels }, - workspaceMember: { fetchWorkspaceMembers }, + workspaceMember: { fetchWorkspaceMembers, fetchWorkspaceUserProjectsRole }, } = useMobxStore(); // router @@ -45,6 +45,11 @@ export const WorkspaceAuthWrapper: FC = observer((props) workspaceSlug ? `WORKSPACE_LABELS_${workspaceSlug}` : null, workspaceSlug ? () => fetchWorkspaceLabels(workspaceSlug.toString()) : null ); + // fetch workspace user projects role + useSWR( + workspaceSlug ? `WORKSPACE_PROJECTS_ROLE_${workspaceSlug}` : null, + workspaceSlug ? () => fetchWorkspaceUserProjectsRole(workspaceSlug.toString()) : null + ); // while data is being loaded if (!currentWorkspaceMemberInfo && hasPermissionToCurrentWorkspace === undefined) { diff --git a/web/services/workspace.service.ts b/web/services/workspace.service.ts index 42cfef450..52d47f8b5 100644 --- a/web/services/workspace.service.ts +++ b/web/services/workspace.service.ts @@ -13,6 +13,7 @@ import { IProductUpdateResponse, IWorkspaceBulkInviteFormData, IWorkspaceViewProps, + IUserProjectsRole, } from "types"; import { IWorkspaceView } from "types/workspace-views"; // store @@ -267,4 +268,12 @@ export class WorkspaceService extends APIService { throw error?.response?.data; }); } + + async getWorkspaceUserProjectsRole(workspaceSlug: string): Promise { + return this.get(`/api/users/me/workspaces/${workspaceSlug}/project-roles/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } diff --git a/web/store/issues/profile/issue.store.ts b/web/store/issues/profile/issue.store.ts index 1ef0b34c8..15d95c843 100644 --- a/web/store/issues/profile/issue.store.ts +++ b/web/store/issues/profile/issue.store.ts @@ -136,7 +136,7 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues return { enableQuickAdd: false, enableIssueCreation: false, - enableInlineEditing: false, + enableInlineEditing: true, }; } @@ -233,8 +233,8 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues let _issues = { ...this.issues }; if (!_issues) _issues = {}; if (!_issues[userId]) _issues[userId] = { assigned: {}, created: {}, subscribed: {} }; - _issues[userId][this.currentUserIssueTab][userId] = { - ..._issues[userId][this.currentUserIssueTab][userId], + _issues[userId][this.currentUserIssueTab][issueId] = { + ..._issues[userId][this.currentUserIssueTab][issueId], ...data, }; diff --git a/web/store/workspace/workspace-member.store.ts b/web/store/workspace/workspace-member.store.ts index def30ea59..e699cb467 100644 --- a/web/store/workspace/workspace-member.store.ts +++ b/web/store/workspace/workspace-member.store.ts @@ -1,7 +1,7 @@ import { action, computed, observable, makeObservable, runInAction } from "mobx"; import { RootStore } from "../root"; // types -import { IWorkspaceMember, IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData } from "types"; +import { IWorkspaceMember, IWorkspaceMemberInvitation, IWorkspaceBulkInviteFormData, IUserProjectsRole } from "types"; // services import { WorkspaceService } from "services/workspace.service"; @@ -13,7 +13,9 @@ export interface IWorkspaceMemberStore { // observables members: { [workspaceSlug: string]: IWorkspaceMember[] }; // workspaceSlug: members[] memberInvitations: { [workspaceSlug: string]: IWorkspaceMemberInvitation[] }; + workspaceUserProjectsRole: { [workspaceSlug: string]: IUserProjectsRole } | undefined; // actions + fetchWorkspaceUserProjectsRole: (workspaceSlug: string) => Promise; fetchWorkspaceMembers: (workspaceSlug: string) => Promise; fetchWorkspaceMemberInvitations: (workspaceSlug: string) => Promise; updateMember: (workspaceSlug: string, memberId: string, data: Partial) => Promise; @@ -29,6 +31,7 @@ export interface IWorkspaceMemberStore { workspaceMembers: IWorkspaceMember[] | null; workspaceMemberInvitations: IWorkspaceMemberInvitation[] | null; workspaceMembersWithInvitations: any[] | null; + currentWorkspaceUserProjectsRole: IUserProjectsRole | undefined; } export class WorkspaceMemberStore implements IWorkspaceMemberStore { @@ -38,6 +41,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore { // observables members: { [workspaceSlug: string]: IWorkspaceMember[] } = {}; memberInvitations: { [workspaceSlug: string]: IWorkspaceMemberInvitation[] } = {}; + workspaceUserProjectsRole: { [workspaceSlug: string]: IUserProjectsRole } | undefined = undefined; // services workspaceService; // root store @@ -52,7 +56,9 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore { // observables members: observable.ref, memberInvitations: observable.ref, + workspaceUserProjectsRole: observable.ref, // actions + fetchWorkspaceUserProjectsRole: action, fetchWorkspaceMembers: action, fetchWorkspaceMemberInvitations: action, updateMember: action, @@ -64,6 +70,7 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore { workspaceMembers: computed, workspaceMemberInvitations: computed, workspaceMembersWithInvitations: computed, + currentWorkspaceUserProjectsRole: computed, }); this.rootStore = _rootStore; @@ -126,6 +133,36 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore { ]; } + /** + * computed value provides the workspace user projects role + */ + get currentWorkspaceUserProjectsRole() { + if (!this.rootStore.workspace.workspaceSlug) return undefined; + + return this.workspaceUserProjectsRole?.[this.rootStore.workspace.workspaceSlug]; + } + + /** + * fetch workspace user projects role using workspace slug + * @param workspaceSlug + */ + fetchWorkspaceUserProjectsRole = async (workspaceSlug: string) => { + try { + const _workspaceUserProjectsRole = { ...this.workspaceUserProjectsRole }; + if (!_workspaceUserProjectsRole[workspaceSlug]) _workspaceUserProjectsRole[workspaceSlug] = {}; + + const response = await this.workspaceService.getWorkspaceUserProjectsRole(workspaceSlug); + _workspaceUserProjectsRole[workspaceSlug] = response; + + runInAction(() => { + this.workspaceUserProjectsRole = _workspaceUserProjectsRole; + }); + return response; + } catch (error) { + throw error; + } + }; + /** * fetch workspace members using workspace slug * @param workspaceSlug diff --git a/web/types/users.d.ts b/web/types/users.d.ts index 0d81ba2a7..301c1d7c0 100644 --- a/web/types/users.d.ts +++ b/web/types/users.d.ts @@ -162,6 +162,10 @@ export interface IUserProfileProjectSegregation { }; } +export interface IUserProjectsRole { + [project_id: string]: number; +} + // export interface ICurrentUser { // id: readonly string; // avatar: string;