From 8061bf8e9de6fea085ce9b673affbca4f118fec6 Mon Sep 17 00:00:00 2001 From: Akshita Goyal <36129505+gakshita@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:09:23 +0530 Subject: [PATCH 1/6] [WEB-2301] fix: issue with state id in workspace projects (#914) * fix: state id fixed * fix: added loading for project states * fix: removed unwanted checks * fix: changed title to list --- web/core/layouts/auth-layout/workspace-wrapper.tsx | 2 +- web/ee/components/projects/header/layout-selection.tsx | 1 - web/ee/components/projects/layouts/board/utils.tsx | 2 +- web/ee/components/projects/layouts/project-layout-HOC.tsx | 6 ++++-- web/ee/constants/project/default-root.ts | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/web/core/layouts/auth-layout/workspace-wrapper.tsx b/web/core/layouts/auth-layout/workspace-wrapper.tsx index 0c111d0a00..070659bbee 100644 --- a/web/core/layouts/auth-layout/workspace-wrapper.tsx +++ b/web/core/layouts/auth-layout/workspace-wrapper.tsx @@ -70,7 +70,7 @@ export const WorkspaceAuthWrapper: FC = observer((props) ); // fetch project states useSWR( - workspaceSlug && currentWorkspace && isProjectStateEnabled ? `WORKSPACE_WORKLOGS_${workspaceSlug}` : null, + workspaceSlug && currentWorkspace && isProjectStateEnabled ? `WORKSPACE_PROJECT_STATES_${workspaceSlug}` : null, () => workspaceSlug && currentWorkspace && isProjectStateEnabled ? fetchProjectStates(workspaceSlug.toString()) : null, { revalidateOnFocus: false } diff --git a/web/ee/components/projects/header/layout-selection.tsx b/web/ee/components/projects/header/layout-selection.tsx index 8aa05cbbcf..74dbf381b4 100644 --- a/web/ee/components/projects/header/layout-selection.tsx +++ b/web/ee/components/projects/header/layout-selection.tsx @@ -25,7 +25,6 @@ export const ProjectLayoutSelection: FC = observer((pro // derived values const selectedLayout = filters?.layout || EProjectLayouts.TABLE; - console.log("selectedLayout", selectedLayout); return ( <> , prePopulatedPayload: { - state_id: states && states[0].id, + state_id: states && states.length > 0 && states[0].id, }, }; } diff --git a/web/ee/components/projects/layouts/project-layout-HOC.tsx b/web/ee/components/projects/layouts/project-layout-HOC.tsx index bbcd231f15..0541c5b749 100644 --- a/web/ee/components/projects/layouts/project-layout-HOC.tsx +++ b/web/ee/components/projects/layouts/project-layout-HOC.tsx @@ -1,10 +1,11 @@ +import isEmpty from "lodash/isEmpty"; import { observer } from "mobx-react"; import Image from "next/image"; import { EmptyState } from "@/components/empty-state"; import { GanttLayoutLoader, KanbanLayoutLoader, ListLayoutLoader, ProjectsLoader } from "@/components/ui"; import { EmptyStateType } from "@/constants/empty-state"; import { useCommandPalette, useEventTracker } from "@/hooks/store"; -import { useProjectFilter } from "@/plane-web/hooks/store"; +import { useProjectFilter, useWorkspaceProjectStates } from "@/plane-web/hooks/store"; import { EProjectLayouts } from "@/plane-web/types/workspace-project-filters"; import AllFiltersImage from "@/public/empty-state/project/all-filters.svg"; @@ -33,12 +34,13 @@ export const ProjectLayoutHOC = observer((props: Props) => { const { layout } = props; const { loading } = useProjectFilter(); const { getFilteredProjectsByLayout } = useProjectFilter(); + const { projectStates } = useWorkspaceProjectStates(); const { setTrackElement } = useEventTracker(); const { toggleCreateProjectModal } = useCommandPalette(); const filteredProjectIds = getFilteredProjectsByLayout(EProjectLayouts.GALLERY); - if (loading) { + if (loading || isEmpty(projectStates)) { return ; } if (!filteredProjectIds) { diff --git a/web/ee/constants/project/default-root.ts b/web/ee/constants/project/default-root.ts index 93d2e88667..38a3acb90a 100644 --- a/web/ee/constants/project/default-root.ts +++ b/web/ee/constants/project/default-root.ts @@ -48,7 +48,7 @@ export const PROJECT_LAYOUT_MAP: TProjectLayoutMap = { }, [EProjectLayouts.TABLE]: { key: EProjectLayouts.TABLE, - title: "Table Layout", + title: "List Layout", label: "Table", icon: List, selectivelyHide: false, From d249b2e6e8243b5be2a3995032615cf6b28a5530 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Tue, 27 Aug 2024 17:45:17 +0530 Subject: [PATCH 2/6] [WEB-2329] chore: updated UI for module and cycle detail overview (#5435) * chore: updated UI for module and cycle detail overview * chore: z-index issue in sheet --- .../(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx | 2 +- .../(detail)/[projectId]/modules/(detail)/[moduleId]/page.tsx | 2 +- web/core/components/cycles/analytics-sidebar/root.tsx | 1 - web/core/components/cycles/cycle-peek-overview.tsx | 1 - web/core/components/modules/analytics-sidebar/root.tsx | 1 - web/core/components/modules/module-peek-overview.tsx | 1 - 6 files changed, 2 insertions(+), 6 deletions(-) diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx index 61aeb3e01f..d9cfe99766 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/cycles/(detail)/[cycleId]/page.tsx @@ -71,7 +71,7 @@ const CycleDetailPage = observer(() => { {cycleId && !isSidebarCollapsed && (
{ {moduleId && !isSidebarCollapsed && (
void; isArchived?: boolean; - isPeekMode?: boolean; }; const defaultValues: Partial = { diff --git a/web/core/components/cycles/cycle-peek-overview.tsx b/web/core/components/cycles/cycle-peek-overview.tsx index 11846d7748..1bcfbcf034 100644 --- a/web/core/components/cycles/cycle-peek-overview.tsx +++ b/web/core/components/cycles/cycle-peek-overview.tsx @@ -51,7 +51,6 @@ export const CyclePeekOverview: React.FC = observer(({ projectId, workspa cycleId={peekCycle?.toString() ?? ""} handleClose={handleClose} isArchived={isArchived} - isPeekMode />
)} diff --git a/web/core/components/modules/analytics-sidebar/root.tsx b/web/core/components/modules/analytics-sidebar/root.tsx index 530ef89813..360c8495b8 100644 --- a/web/core/components/modules/analytics-sidebar/root.tsx +++ b/web/core/components/modules/analytics-sidebar/root.tsx @@ -63,7 +63,6 @@ type Props = { moduleId: string; handleClose: () => void; isArchived?: boolean; - isPeekMode?: boolean; }; // TODO: refactor this component diff --git a/web/core/components/modules/module-peek-overview.tsx b/web/core/components/modules/module-peek-overview.tsx index a03c8ecd3b..02fb857478 100644 --- a/web/core/components/modules/module-peek-overview.tsx +++ b/web/core/components/modules/module-peek-overview.tsx @@ -51,7 +51,6 @@ export const ModulePeekOverview: React.FC = observer(({ projectId, worksp moduleId={peekModule?.toString() ?? ""} handleClose={handleClose} isArchived={isArchived} - isPeekMode />
)} From 5b266788af3080062903d27700a136d356eac92d Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Tue, 27 Aug 2024 17:36:20 +0530 Subject: [PATCH 3/6] [WEB-2312] style: worklog ux copy update. (#955) --- web/ee/components/worklogs/workspace/empty.tsx | 6 +++--- web/ee/components/worklogs/workspace/upgrade.tsx | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/web/ee/components/worklogs/workspace/empty.tsx b/web/ee/components/worklogs/workspace/empty.tsx index 6c67b73c9f..e54eff0d1f 100644 --- a/web/ee/components/worklogs/workspace/empty.tsx +++ b/web/ee/components/worklogs/workspace/empty.tsx @@ -13,10 +13,10 @@ export const WorklogEmptyScreen: FC = () => { return (
-

No worklogs yet

+

See timesheets for any member in any project.

- Issue types distinguish different kinds of work in unique ways, helping you to identify, categorize, and - report on your team’s work more effectively. + When you log time via Tracked time in issue properties, you will see detailed timesheets here. Any member can + log time in any issue in any project in your workspace.

{ toggleProPlanModal(false)} />
-

Unlock worklogs with Plane pro

+

Get detailed time-tracking reports from your workspace

- Issue types distinguish different kinds of work in unique ways, helping you to identify, categorize, and - report on your team’s work more effectively. + Set date ranges for logged time by any member in any project in your workspace and get full CSVs in a click.

Date: Tue, 27 Aug 2024 17:27:59 +0530 Subject: [PATCH 4/6] [WEB-2312] chore: minor UI and UX copy improvements. (#5438) --- .../constants/project/settings/features.tsx | 19 +++++++++++-------- .../pages/editor/header/options-dropdown.tsx | 2 +- .../project/project-feature-update.tsx | 10 +++------- .../project/settings/features-list.tsx | 3 ++- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/web/ce/constants/project/settings/features.tsx b/web/ce/constants/project/settings/features.tsx index 140f3658c2..3fdccc9794 100644 --- a/web/ce/constants/project/settings/features.tsx +++ b/web/ce/constants/project/settings/features.tsx @@ -16,18 +16,20 @@ export type TFeatureList = { export type TProjectFeatures = { [key: string]: { title: string; + description: string; featureList: TFeatureList; }; }; export const PROJECT_FEATURES_LIST: TProjectFeatures = { project_features: { - title: "Features", + title: "Projects and issues", + description: "Toggle these on or off this project.", featureList: { cycles: { property: "cycle_view", title: "Cycles", - description: "Time-box issues and boost momentum, similar to sprints in scrum.", + description: "Timebox work as you see fit per project and change frequency from one period to the next.", icon: , isPro: false, isEnabled: true, @@ -35,7 +37,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { modules: { property: "module_view", title: "Modules", - description: "Group multiple issues together and track the progress.", + description: "Group work into sub-project-like set-ups with their own leads and assignees.", icon: , isPro: false, isEnabled: true, @@ -43,7 +45,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { views: { property: "issue_views_view", title: "Views", - description: "Apply filters to issues and save them to analyse and investigate work.", + description: "Save sorts, filters, and display options for later or share them.", icon: , isPro: false, isEnabled: true, @@ -51,7 +53,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { pages: { property: "page_view", title: "Pages", - description: "Document ideas, feature requirements, discussions within your project.", + description: "Write anything like you write anything.", icon: , isPro: false, isEnabled: true, @@ -59,7 +61,7 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { inbox: { property: "inbox_view", title: "Intake", - description: "Capture external inputs, move valid issues to workflow.", + description: "Consider and discuss issues before you add them to your project.", icon: , isPro: false, isEnabled: true, @@ -67,12 +69,13 @@ export const PROJECT_FEATURES_LIST: TProjectFeatures = { }, }, project_others: { - title: "Others", + title: "Work management", + description: "Available only on some plans as indicated by the label next to the feature below.", featureList: { is_time_tracking_enabled: { property: "is_time_tracking_enabled", title: "Time Tracking", - description: "Keep the work logs of the users in track ", + description: "Log time, see timesheets, and download full CSVs for your entire workspace.", icon: , isPro: true, isEnabled: false, diff --git a/web/core/components/pages/editor/header/options-dropdown.tsx b/web/core/components/pages/editor/header/options-dropdown.tsx index 14b352456a..113f02fcc6 100644 --- a/web/core/components/pages/editor/header/options-dropdown.tsx +++ b/web/core/components/pages/editor/header/options-dropdown.tsx @@ -166,7 +166,7 @@ export const PageOptionsDropdown: React.FC = observer((props) => { ]; return ( - + handleFullWidth(!isFullWidth)} diff --git a/web/core/components/project/project-feature-update.tsx b/web/core/components/project/project-feature-update.tsx index 5d08d85ef6..8b99027089 100644 --- a/web/core/components/project/project-feature-update.tsx +++ b/web/core/components/project/project-feature-update.tsx @@ -28,14 +28,10 @@ export const ProjectFeatureUpdate: FC = observer((props) => { return ( <> -
-

Toggle project features

-
- Turn on features which help you manage and run your project. -
+
+
- -
+
Congrats! Project {" "}

{currentProjectDetails.name}

created. diff --git a/web/core/components/project/settings/features-list.tsx b/web/core/components/project/settings/features-list.tsx index 796ad473a0..f8a71aabcf 100644 --- a/web/core/components/project/settings/features-list.tsx +++ b/web/core/components/project/settings/features-list.tsx @@ -64,8 +64,9 @@ export const ProjectFeaturesList: FC = observer((props) => { const feature = PROJECT_FEATURES_LIST[featureSectionKey]; return (
-
+

{feature.title}

+

{feature.description}

{Object.keys(feature.featureList).map((featureItemKey) => { const featureItem = feature.featureList[featureItemKey]; From 75b3f0f7d226d8a04ce1fbe961ecc52aa1bc42ac Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:46:53 +0530 Subject: [PATCH 5/6] redirect to issues page post deletion (#5437) --- .../issue-detail-quick-actions.tsx | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx b/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx index 54cce976fd..be6b56770b 100644 --- a/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx +++ b/web/core/components/issues/issue-detail/issue-detail-quick-actions.tsx @@ -78,18 +78,19 @@ export const IssueDetailQuickActions: FC = observer((props) => { const handleDeleteIssue = async () => { try { - if (issue?.archived_at) { - return removeArchivedIssue(workspaceSlug, projectId, issueId).then(() => { - router.push(`/${workspaceSlug}/projects/${projectId}/issues`); - captureIssueEvent({ - eventName: ISSUE_DELETED, - payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, - path: pathname, - }); + const deleteIssue = issue?.archived_at ? removeArchivedIssue : removeIssue; + const redirectionPath = issue?.archived_at + ? `/${workspaceSlug}/projects/${projectId}/archives/issues` + : `/${workspaceSlug}/projects/${projectId}/issues`; + + return deleteIssue(workspaceSlug, projectId, issueId).then(() => { + router.push(redirectionPath); + captureIssueEvent({ + eventName: ISSUE_DELETED, + payload: { id: issueId, state: "SUCCESS", element: "Issue detail page" }, + path: pathname, }); - } else { - return removeIssue(workspaceSlug, projectId, issueId); - } + }); } catch (error) { setToast({ title: "Error!", From 5c362dff11c5dfcae5909e61c1293d8e2b8783b4 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Tue, 27 Aug 2024 18:58:00 +0530 Subject: [PATCH 6/6] [MOBIL-141] chore: added project and user details in inbox (#947) * chore: added project and user details in inbox * chore: project detail * chore: cycle and module user properties queries and mutation * chore: typo in mutation * chore: created prjects in your work --- apiserver/plane/graphql/mutations/cycle.py | 40 ++++++++++++++++- apiserver/plane/graphql/mutations/module.py | 40 ++++++++++++++++- apiserver/plane/graphql/queries/cycle.py | 32 +++++++++++++- apiserver/plane/graphql/queries/issue.py | 13 +++--- apiserver/plane/graphql/queries/module.py | 32 +++++++++++++- .../plane/graphql/queries/notification.py | 1 - apiserver/plane/graphql/queries/project.py | 44 +++++++++++++++++++ apiserver/plane/graphql/queries/workspace.py | 3 +- apiserver/plane/graphql/schema.py | 20 +++++++-- apiserver/plane/graphql/types/cycle.py | 30 ++++++++++++- apiserver/plane/graphql/types/module.py | 30 ++++++++++++- apiserver/plane/graphql/types/notification.py | 14 ++---- apiserver/plane/graphql/types/users.py | 4 +- 13 files changed, 268 insertions(+), 35 deletions(-) diff --git a/apiserver/plane/graphql/mutations/cycle.py b/apiserver/plane/graphql/mutations/cycle.py index 71ee75368a..b41b94cd19 100644 --- a/apiserver/plane/graphql/mutations/cycle.py +++ b/apiserver/plane/graphql/mutations/cycle.py @@ -20,7 +20,13 @@ from plane.graphql.permissions.project import ( ProjectMemberPermission, ProjectBasePermission, ) -from plane.db.models import CycleIssue, Cycle, UserFavorite +from plane.db.models import ( + CycleIssue, + Cycle, + UserFavorite, + CycleUserProperties, +) +from plane.graphql.types.cycle import CycleUserPropertyType from plane.graphql.bgtasks.issue_activity_task import issue_activity @@ -160,7 +166,6 @@ class CycleIssueMutation: @strawberry.type class CycleFavoriteMutation: - @strawberry.mutation( extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] ) @@ -199,3 +204,34 @@ class CycleFavoriteMutation: await sync_to_async(cycle_favorite.delete)() return True + + +@strawberry.type +class CycleIssueUserPropertyMutation: + @strawberry.mutation( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def updateCycleUserProperties( + self, + info: Info, + slug: str, + project: strawberry.ID, + cycle: strawberry.ID, + filters: JSON, + display_filters: JSON, + display_properties: JSON, + ) -> CycleUserPropertyType: + cycle_issue_properties = await sync_to_async( + CycleUserProperties.objects.get + )( + workspace__slug=slug, + project_id=project, + cycle_id=cycle, + user=info.context.user, + ) + cycle_issue_properties.filters = filters + cycle_issue_properties.display_filters = display_filters + cycle_issue_properties.display_properties = display_properties + + await sync_to_async(cycle_issue_properties.save)() + return cycle_issue_properties diff --git a/apiserver/plane/graphql/mutations/module.py b/apiserver/plane/graphql/mutations/module.py index bb05373761..c53e88014a 100644 --- a/apiserver/plane/graphql/mutations/module.py +++ b/apiserver/plane/graphql/mutations/module.py @@ -18,7 +18,13 @@ from plane.graphql.permissions.project import ( ProjectMemberPermission, ProjectBasePermission, ) -from plane.db.models import Project, ModuleIssue, UserFavorite +from plane.db.models import ( + Project, + ModuleIssue, + UserFavorite, + ModuleUserProperties, +) +from plane.graphql.types.module import ModuleUserPropertyType from plane.graphql.bgtasks.issue_activity_task import issue_activity @@ -115,7 +121,6 @@ class ModuleIssueMutation: @strawberry.type class ModuleFavoriteMutation: - @strawberry.mutation( extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] ) @@ -154,3 +159,34 @@ class ModuleFavoriteMutation: await sync_to_async(module_favorite.delete)() return True + + +@strawberry.type +class ModuleIssueUserPropertyMutation: + @strawberry.mutation( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def updateModuleUserProperties( + self, + info: Info, + slug: str, + project: strawberry.ID, + module: strawberry.ID, + filters: JSON, + display_filters: JSON, + display_properties: JSON, + ) -> ModuleUserPropertyType: + module_issue_properties = await sync_to_async( + ModuleUserProperties.objects.get + )( + workspace__slug=slug, + project_id=project, + module_id=module, + user=info.context.user, + ) + module_issue_properties.filters = filters + module_issue_properties.display_filters = display_filters + module_issue_properties.display_properties = display_properties + + await sync_to_async(module_issue_properties.save)() + return module_issue_properties diff --git a/apiserver/plane/graphql/queries/cycle.py b/apiserver/plane/graphql/queries/cycle.py index a171778181..bc9e4b9d94 100644 --- a/apiserver/plane/graphql/queries/cycle.py +++ b/apiserver/plane/graphql/queries/cycle.py @@ -17,8 +17,8 @@ from strawberry.scalars import JSON from strawberry.permission import PermissionExtension # Module Imports -from plane.db.models import Cycle, Issue -from plane.graphql.types.cycle import CycleType +from plane.db.models import Cycle, Issue, CycleUserProperties +from plane.graphql.types.cycle import CycleType, CycleUserPropertyType from plane.graphql.types.issue import ( IssuesInformationType, IssuesInformationObjectType, @@ -84,6 +84,34 @@ class CycleQuery: return cycle +# cycle issue user properties +@strawberry.type +class CycleIssueUserPropertyQuery: + @strawberry.field( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def cycleIssueUserProperties( + self, + info: Info, + slug: str, + project: strawberry.ID, + cycle: strawberry.ID, + ) -> CycleUserPropertyType: + cycle_issue_property = await sync_to_async( + lambda: CycleUserProperties.objects.filter( + workspace__slug=slug, project_id=project, cycle_id=cycle + ) + .filter( + project__project_projectmember__member=info.context.user, + project__project_projectmember__is_active=True, + ) + .order_by("-created_at") + .first() + )() + + return cycle_issue_property + + # cycle issues information query @strawberry.type class CycleIssuesInformationQuery: diff --git a/apiserver/plane/graphql/queries/issue.py b/apiserver/plane/graphql/queries/issue.py index 8ead450bfa..5a956aacfd 100644 --- a/apiserver/plane/graphql/queries/issue.py +++ b/apiserver/plane/graphql/queries/issue.py @@ -364,16 +364,13 @@ class SubIssuesQuery: @strawberry.type class IssueTypesTypeQuery: @strawberry.field( - extensions=[PermissionExtension(permissions=[WorkspaceBasePermission()])] + extensions=[ + PermissionExtension(permissions=[WorkspaceBasePermission()]) + ] ) - async def issueTypes( - self, info: Info, slug: str - ) -> list[IssueTypesType]: + async def issueTypes(self, info: Info, slug: str) -> list[IssueTypesType]: issue_types = await sync_to_async(list)( - IssueType.objects.filter( - workspace__slug=slug - ) - .distinct() + IssueType.objects.filter(workspace__slug=slug).distinct() ) return issue_types diff --git a/apiserver/plane/graphql/queries/module.py b/apiserver/plane/graphql/queries/module.py index c3c5b13de0..1b7dabffef 100644 --- a/apiserver/plane/graphql/queries/module.py +++ b/apiserver/plane/graphql/queries/module.py @@ -13,8 +13,8 @@ from strawberry.scalars import JSON from strawberry.permission import PermissionExtension # Module Imports -from plane.db.models import Module, Issue -from plane.graphql.types.module import ModuleType +from plane.db.models import Module, Issue, ModuleUserProperties +from plane.graphql.types.module import ModuleType, ModuleUserPropertyType from plane.graphql.types.issue import ( IssuesInformationType, IssuesInformationObjectType, @@ -93,6 +93,34 @@ class ModuleQuery: return modules +# module issue user properties +@strawberry.type +class ModuleIssueUserPropertyQuery: + @strawberry.field( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def moduleIssueUserProperties( + self, + info: Info, + slug: str, + project: strawberry.ID, + module: strawberry.ID, + ) -> ModuleUserPropertyType: + module_issue_property = await sync_to_async( + lambda: ModuleUserProperties.objects.filter( + workspace__slug=slug, project_id=project, module_id=module + ) + .filter( + project__project_projectmember__member=info.context.user, + project__project_projectmember__is_active=True, + ) + .order_by("-created_at") + .first() + )() + + return module_issue_property + + # module issues information query @strawberry.type class ModuleIssuesInformationQuery: diff --git a/apiserver/plane/graphql/queries/notification.py b/apiserver/plane/graphql/queries/notification.py index 512cf9ed1c..0576caa55d 100644 --- a/apiserver/plane/graphql/queries/notification.py +++ b/apiserver/plane/graphql/queries/notification.py @@ -28,7 +28,6 @@ from typing import Optional, List @strawberry.type class NotificationQuery: - @strawberry.field( extensions=[ PermissionExtension(permissions=[WorkspaceBasePermission()]) diff --git a/apiserver/plane/graphql/queries/project.py b/apiserver/plane/graphql/queries/project.py index a141c1b2ca..35d323745f 100644 --- a/apiserver/plane/graphql/queries/project.py +++ b/apiserver/plane/graphql/queries/project.py @@ -71,6 +71,50 @@ class ProjectQuery: return paginate(results_object=project, cursor=cursor) + @strawberry.field( + extensions=[ + PermissionExtension(permissions=[WorkspaceBasePermission()]) + ] + ) + async def project( + self, + info: Info, + slug: str, + project: strawberry.ID, + ) -> Optional[ProjectType]: + def get_project() -> Optional[ProjectType]: + return ( + Project.objects.filter( + workspace__slug=slug, + pk=project, + archived_at__isnull=True, + ) + .annotate( + is_favorite=Exists( + UserFavorite.objects.filter( + user=info.context.user, + entity_identifier=OuterRef("pk"), + entity_type="project", + project_id=OuterRef("pk"), + ) + ) + ) + .annotate( + is_member=Exists( + ProjectMember.objects.filter( + member=info.context.user, + project_id=OuterRef("pk"), + workspace__slug=slug, + is_active=True, + ) + ) + ) + .first() + ) + + project = await sync_to_async(get_project)() + return project + @strawberry.type class ProjectMembersQuery: diff --git a/apiserver/plane/graphql/queries/workspace.py b/apiserver/plane/graphql/queries/workspace.py index 9856571d6f..f0d889e13a 100644 --- a/apiserver/plane/graphql/queries/workspace.py +++ b/apiserver/plane/graphql/queries/workspace.py @@ -158,7 +158,8 @@ class YourWorkQuery: projects = await sync_to_async(list)( Project.objects.filter(workspace__slug=slug) .filter( - Q( + Q(created_by=info.context.user) + & Q( project_projectmember__member=info.context.user, project_projectmember__is_active=True, ) diff --git a/apiserver/plane/graphql/schema.py b/apiserver/plane/graphql/schema.py index a4b1fc7078..5142afece2 100644 --- a/apiserver/plane/graphql/schema.py +++ b/apiserver/plane/graphql/schema.py @@ -30,11 +30,13 @@ from .queries.cycle import ( CycleQuery, CycleIssuesInformationQuery, CycleIssueQuery, + CycleIssueUserPropertyQuery, ) from .queries.module import ( ModuleQuery, ModuleIssuesInformationQuery, ModuleIssueQuery, + ModuleIssueUserPropertyQuery, ) from .queries.search import ProjectSearchQuery from .queries.attachment import IssueAttachmentQuery @@ -52,13 +54,21 @@ from .mutations.issue import ( IssueMutation, IssueUserPropertyMutation, IssueAttachmentMutation, - IssueSubscriptionMutation + IssueSubscriptionMutation, ) from .mutations.notification import NotificationMutation from .mutations.user import ProfileMutation from .mutations.page import PageFavoriteMutation -from .mutations.cycle import CycleIssueMutation, CycleFavoriteMutation -from .mutations.module import ModuleIssueMutation, ModuleFavoriteMutation +from .mutations.cycle import ( + CycleIssueMutation, + CycleFavoriteMutation, + CycleIssueUserPropertyMutation, +) +from .mutations.module import ( + ModuleIssueMutation, + ModuleFavoriteMutation, + ModuleIssueUserPropertyMutation, +) from .mutations.link import IssueLinkMutation from .mutations.favorite import UserFavoriteMutation @@ -102,6 +112,8 @@ class Query( IssueTypesTypeQuery, EstimatePointQuery, UserPageQuery, + CycleIssueUserPropertyQuery, + ModuleIssueUserPropertyQuery, ): pass @@ -127,6 +139,8 @@ class Mutation( CycleFavoriteMutation, ModuleFavoriteMutation, UserFavoriteMutation, + CycleIssueUserPropertyMutation, + ModuleIssueUserPropertyMutation, ): pass diff --git a/apiserver/plane/graphql/types/cycle.py b/apiserver/plane/graphql/types/cycle.py index d0b033d3d3..c5cd4b7691 100644 --- a/apiserver/plane/graphql/types/cycle.py +++ b/apiserver/plane/graphql/types/cycle.py @@ -13,7 +13,7 @@ from asgiref.sync import sync_to_async # Module Imports -from plane.db.models import Cycle, Issue +from plane.db.models import Cycle, Issue, CycleUserProperties from plane.graphql.types.users import UserType @@ -83,3 +83,31 @@ class CycleType: .count() )() return issue_assignees_count + + +@strawberry_django.type(CycleUserProperties) +class CycleUserPropertyType: + display_filters: JSON + display_properties: JSON + filters: JSON + id: strawberry.ID + user: strawberry.ID + workspace: strawberry.ID + project: strawberry.ID + cycle: strawberry.ID + + @strawberry.field + def user(self) -> int: + return self.user_id + + @strawberry.field + def workspace(self) -> int: + return self.workspace_id + + @strawberry.field + def project(self) -> int: + return self.project_id + + @strawberry.field + def cycle(self) -> int: + return self.cycle_id diff --git a/apiserver/plane/graphql/types/module.py b/apiserver/plane/graphql/types/module.py index 47ef874034..1ec6087449 100644 --- a/apiserver/plane/graphql/types/module.py +++ b/apiserver/plane/graphql/types/module.py @@ -9,7 +9,7 @@ from strawberry.types import Info from strawberry.scalars import JSON # Module Imports -from plane.db.models import Module, Issue +from plane.db.models import Module, Issue, ModuleUserProperties from plane.graphql.types.users import UserType # Third-party library imports @@ -85,3 +85,31 @@ class ModuleType: .count() )() return issue_assignees_count + + +@strawberry_django.type(ModuleUserProperties) +class ModuleUserPropertyType: + display_filters: JSON + display_properties: JSON + filters: JSON + id: strawberry.ID + user: strawberry.ID + workspace: strawberry.ID + project: strawberry.ID + module: strawberry.ID + + @strawberry.field + def user(self) -> int: + return self.user_id + + @strawberry.field + def workspace(self) -> int: + return self.workspace_id + + @strawberry.field + def project(self) -> int: + return self.project_id + + @strawberry.field + def module(self) -> int: + return self.module_id diff --git a/apiserver/plane/graphql/types/notification.py b/apiserver/plane/graphql/types/notification.py index 6a2c32370c..b25d94e034 100644 --- a/apiserver/plane/graphql/types/notification.py +++ b/apiserver/plane/graphql/types/notification.py @@ -9,6 +9,8 @@ from strawberry.scalars import JSON # Module imports from plane.db.models import Notification +from plane.graphql.types.users import UserType +from plane.graphql.types.project import ProjectType @strawberry_django.type(Notification) @@ -19,7 +21,7 @@ class NotificationType: message_html = Optional[str] message_stripped = Optional[str] sender: str - triggered_by: strawberry.ID + triggered_by: Optional[UserType] receiver: strawberry.ID read_at: Optional[datetime] snoozed_till: Optional[datetime] @@ -28,7 +30,7 @@ class NotificationType: entity_identifier: strawberry.ID data: JSON workspace: strawberry.ID - project: strawberry.ID + project: Optional[ProjectType] created_at: datetime updated_at: datetime @@ -36,14 +38,6 @@ class NotificationType: def workspace(self) -> int: return self.workspace_id - @strawberry.field - def project(self) -> int: - return self.project_id - - @strawberry.field - def triggered_by(self) -> int: - return self.triggered_by_id - @strawberry.field def receiver(self) -> int: return self.receiver_id diff --git a/apiserver/plane/graphql/types/users.py b/apiserver/plane/graphql/types/users.py index 6f92ff9081..064b05d5dc 100644 --- a/apiserver/plane/graphql/types/users.py +++ b/apiserver/plane/graphql/types/users.py @@ -109,7 +109,7 @@ class UserFavoriteEntityData: class UserFavoriteType: id: strawberry.ID entity_type: str - entity_identifier: str + entity_identifier: Optional[str] name: Optional[str] is_folder: bool sequence: float @@ -214,7 +214,7 @@ class UserRecentVisitType: @strawberry.field def project(self) -> int: return self.project_id - + @strawberry.field def user(self) -> int: return self.user_id