[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:
Anmol Singh Bhatia
2024-10-18 14:59:05 +05:30
committed by GitHub
parent c05d8a32dc
commit fb3c367211
7 changed files with 69 additions and 9 deletions

View File

@@ -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__"

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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);

View File

@@ -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">

View File

@@ -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;
});