mirror of
https://github.com/makeplane/plane.git
synced 2026-07-14 06:25:58 +02:00
[WEB-2625] chore: workspace worklog and active cycle improvement (#1537)
* chore: worklog empty state updated * chore: added active cycles count * chore: workapce active cycle improvement --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
committed by
GitHub
parent
c05d8a32dc
commit
fb3c367211
@@ -66,6 +66,8 @@ class WorkSpaceMemberSerializer(DynamicBaseSerializer):
|
||||
|
||||
class WorkspaceMemberMeSerializer(BaseSerializer):
|
||||
draft_issue_count = serializers.IntegerField(read_only=True)
|
||||
active_cycles_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = WorkspaceMember
|
||||
fields = "__all__"
|
||||
|
||||
@@ -7,6 +7,7 @@ from django.db.models import (
|
||||
Subquery,
|
||||
IntegerField,
|
||||
)
|
||||
from django.utils import timezone
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models.functions import Cast
|
||||
|
||||
@@ -39,6 +40,7 @@ from plane.db.models import (
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
DraftIssue,
|
||||
Cycle,
|
||||
)
|
||||
from plane.utils.cache import cache_response, invalidate_cache
|
||||
from plane.payment.bgtasks.member_sync_task import member_sync_task
|
||||
@@ -317,11 +319,26 @@ class WorkspaceMemberUserEndpoint(BaseAPIView):
|
||||
DraftIssue.objects.filter(
|
||||
created_by=request.user,
|
||||
workspace_id=OuterRef("workspace_id"),
|
||||
project__project_projectmember__member=request.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
.values("workspace_id")
|
||||
.annotate(count=Count("id"))
|
||||
.values("count")
|
||||
)
|
||||
active_cycles_count = (
|
||||
Cycle.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__role__gt=5,
|
||||
project__project_projectmember__member=self.request.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
start_date__lte=timezone.now(),
|
||||
end_date__gte=timezone.now(),
|
||||
)
|
||||
.values("pk")
|
||||
.annotate(count=Count("id"))
|
||||
.values("count")
|
||||
)
|
||||
|
||||
workspace_member = (
|
||||
WorkspaceMember.objects.filter(
|
||||
@@ -332,6 +349,15 @@ class WorkspaceMemberUserEndpoint(BaseAPIView):
|
||||
Subquery(draft_issue_count, output_field=IntegerField()), 0
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
active_cycles_count=Coalesce(
|
||||
Subquery(
|
||||
active_cycles_count,
|
||||
output_field=IntegerField(),
|
||||
),
|
||||
0,
|
||||
)
|
||||
)
|
||||
.first()
|
||||
)
|
||||
serializer = WorkspaceMemberMeSerializer(workspace_member)
|
||||
|
||||
2
packages/types/src/workspace.d.ts
vendored
2
packages/types/src/workspace.d.ts
vendored
@@ -92,6 +92,7 @@ export interface IWorkspaceMemberMe {
|
||||
view_props: IWorkspaceViewProps;
|
||||
workspace: string;
|
||||
draft_issue_count: number;
|
||||
active_cycles_count: number;
|
||||
}
|
||||
|
||||
export interface ILastActiveWorkspaceDetails {
|
||||
@@ -222,4 +223,3 @@ export interface IWorkspaceProgressResponse {
|
||||
export interface IWorkspaceAnalyticsResponse {
|
||||
completion_chart: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,21 +24,23 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { UpgradeBadge } from "@/plane-web/components/workspace";
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
import { useWorkspaceSubscription } from "@/plane-web/hooks/store";
|
||||
|
||||
export const SidebarWorkspaceMenu = observer(() => {
|
||||
// state
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
// refs
|
||||
const actionSectionRef = useRef<HTMLDivElement | null>(null);
|
||||
// store hooks
|
||||
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// router params
|
||||
const { workspaceSlug } = useParams();
|
||||
// pathname
|
||||
const pathname = usePathname();
|
||||
// store hooks
|
||||
const { toggleSidebar, sidebarCollapsed } = useAppTheme();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { allowPermissions, workspaceUserInfo } = useUserPermissions();
|
||||
const { currentWorkspaceSubscribedPlanDetail } = useWorkspaceSubscription();
|
||||
// local storage
|
||||
const { setValue: toggleWorkspaceMenu, storedValue } = useLocalStorage<boolean>("is_workspace_menu_open", true);
|
||||
// derived values
|
||||
@@ -59,6 +61,10 @@ export const SidebarWorkspaceMenu = observer(() => {
|
||||
}, [sidebarCollapsed, toggleWorkspaceMenu]);
|
||||
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));
|
||||
|
||||
const shouldRenderWorkspaceActiveCycle =
|
||||
workspaceUserInfo[workspaceSlug.toString()]?.active_cycles_count > 0 &&
|
||||
currentWorkspaceSubscribedPlanDetail?.product !== "FREE";
|
||||
|
||||
return (
|
||||
<Disclosure as="div" defaultOpen>
|
||||
{!sidebarCollapsed && (
|
||||
@@ -152,8 +158,9 @@ export const SidebarWorkspaceMenu = observer(() => {
|
||||
})}
|
||||
static
|
||||
>
|
||||
{SIDEBAR_WORKSPACE_MENU_ITEMS.map(
|
||||
(link) =>
|
||||
{SIDEBAR_WORKSPACE_MENU_ITEMS.map((link) => {
|
||||
if (link.key === "active-cycles" && !shouldRenderWorkspaceActiveCycle) return null;
|
||||
return (
|
||||
allowPermissions(link.access, EUserPermissionsLevel.WORKSPACE, workspaceSlug.toString()) && (
|
||||
<Tooltip
|
||||
key={link.key}
|
||||
@@ -186,7 +193,8 @@ export const SidebarWorkspaceMenu = observer(() => {
|
||||
</Link>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</Disclosure.Panel>
|
||||
)}
|
||||
</Transition>
|
||||
|
||||
@@ -66,6 +66,7 @@ export interface ICycleStore {
|
||||
validateDate: (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => Promise<any>;
|
||||
setPlotType: (cycleId: string, plotType: TCyclePlotType) => void;
|
||||
setEstimateType: (cycleId: string, estimateType: TCycleEstimateType) => void;
|
||||
updateWorkspaceUserActiveCycleCount: (workspaceSlug: string, increment: number) => void;
|
||||
// fetch
|
||||
fetchWorkspaceCycles: (workspaceSlug: string) => Promise<ICycle[]>;
|
||||
fetchAllCycles: (workspaceSlug: string, projectId: string) => Promise<undefined | ICycle[]>;
|
||||
@@ -153,6 +154,7 @@ export class CycleStore implements ICycleStore {
|
||||
removeCycleFromFavorites: action,
|
||||
archiveCycle: action,
|
||||
restoreCycle: action,
|
||||
updateWorkspaceUserActiveCycleCount: action,
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
@@ -443,6 +445,13 @@ export class CycleStore implements ICycleStore {
|
||||
set(this.estimatedType, [cycleId], estimateType);
|
||||
};
|
||||
|
||||
updateWorkspaceUserActiveCycleCount(workspaceSlug: string, increment: number) {
|
||||
const workspaceUserInfo = this.rootStore.user.permission.workspaceUserInfo;
|
||||
const currentCount = workspaceUserInfo[workspaceSlug]?.active_cycles_count ?? 0;
|
||||
|
||||
set(workspaceUserInfo, [workspaceSlug, "active_cycles_count"], currentCount + increment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description fetch all cycles
|
||||
* @param workspaceSlug
|
||||
@@ -634,6 +643,10 @@ export class CycleStore implements ICycleStore {
|
||||
await this.cycleService.createCycle(workspaceSlug, projectId, data).then((response) => {
|
||||
runInAction(() => {
|
||||
set(this.cycleMap, [response.id], response);
|
||||
if (response.status?.toLowerCase() === "current") {
|
||||
// Update workspace active cycle count in workspaceUserInfo
|
||||
this.updateWorkspaceUserActiveCycleCount(workspaceSlug, 1);
|
||||
}
|
||||
});
|
||||
return response;
|
||||
})
|
||||
@@ -672,6 +685,10 @@ export class CycleStore implements ICycleStore {
|
||||
deleteCycle = async (workspaceSlug: string, projectId: string, cycleId: string) =>
|
||||
await this.cycleService.deleteCycle(workspaceSlug, projectId, cycleId).then(() => {
|
||||
runInAction(() => {
|
||||
if (this.cycleMap[cycleId].status?.toLowerCase() === "current") {
|
||||
// Update workspace active cycle count in workspaceUserInfo
|
||||
this.updateWorkspaceUserActiveCycleCount(workspaceSlug, -1);
|
||||
}
|
||||
delete this.cycleMap[cycleId];
|
||||
delete this.activeCycleIdMap[cycleId];
|
||||
if (this.rootStore.favorite.entityMap[cycleId]) this.rootStore.favorite.removeFavoriteFromStore(cycleId);
|
||||
|
||||
@@ -40,6 +40,7 @@ export const WorkspaceWorklogDownloadRoot: FC<TWorkspaceWorklogDownloadRoot> = o
|
||||
workspaceWorklogDownloadIds && workspaceWorklogDownloadIds.length > 0
|
||||
? EWorklogDownloadLoader.MUTATION_LOADER
|
||||
: EWorklogDownloadLoader.INIT_LOADER;
|
||||
const worklogDownloadIds = worklogDownloadIdsByWorkspaceId(workspaceId) || [];
|
||||
|
||||
// fetching workspace worklog downloads
|
||||
useSWR(workspaceSlug ? `WORKSPACE_WORKLOG_DOWNLOADS_${workspaceSlug}` : null, () =>
|
||||
@@ -50,6 +51,8 @@ export const WorkspaceWorklogDownloadRoot: FC<TWorkspaceWorklogDownloadRoot> = o
|
||||
|
||||
if (loader === EWorklogDownloadLoader.INIT_LOADER) return <WorklogDownloadLoader loader={loader} />;
|
||||
|
||||
if (worklogDownloadIds.length <= 0) return <></>;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="flex justify-between items-center">
|
||||
|
||||
@@ -194,6 +194,10 @@ export class CycleStore extends CeCycleStore implements ICycleStore {
|
||||
return await this.cycleService.createCycle(workspaceSlug, projectId, { ...data, version }).then((response) => {
|
||||
runInAction(() => {
|
||||
set(this.cycleMap, [response.id], response);
|
||||
if (response.status?.toLowerCase() === "current") {
|
||||
// Update workspace active cycle count in workspaceUserInfo
|
||||
this.updateWorkspaceUserActiveCycleCount(workspaceSlug, 1);
|
||||
}
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user