chore: cycle and module sidebar analytics improvement (#3559)

* chore: cycle and module store update action updated

* chore: cycle and module issue store actions updated

* chore: cycle and module retrieve endpoints updated

* fix: app sidebar z index and priority icon fix

* chore: cycle and module sidebar and stats updated
This commit is contained in:
Anmol Singh Bhatia 2024-02-05 12:34:53 +05:30 committed by GitHub
parent efaba43494
commit 7d07afd59c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 70 additions and 68 deletions

View File

@ -242,13 +242,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.values("display_name", "assignee_id", "avatar") .values("display_name", "assignee_id", "avatar")
.annotate( .annotate(
total_issues=Count( total_issues=Count(
"assignee_id", "id",
filter=Q(archived_at__isnull=True, is_draft=False), filter=Q(archived_at__isnull=True, is_draft=False),
), ),
) )
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"assignee_id", "id",
filter=Q( filter=Q(
completed_at__isnull=False, completed_at__isnull=False,
archived_at__isnull=True, archived_at__isnull=True,
@ -258,7 +258,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
pending_issues=Count( pending_issues=Count(
"assignee_id", "id",
filter=Q( filter=Q(
completed_at__isnull=True, completed_at__isnull=True,
archived_at__isnull=True, archived_at__isnull=True,
@ -281,13 +281,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.values("label_name", "color", "label_id") .values("label_name", "color", "label_id")
.annotate( .annotate(
total_issues=Count( total_issues=Count(
"label_id", "id",
filter=Q(archived_at__isnull=True, is_draft=False), filter=Q(archived_at__isnull=True, is_draft=False),
) )
) )
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"label_id", "id",
filter=Q( filter=Q(
completed_at__isnull=False, completed_at__isnull=False,
archived_at__isnull=True, archived_at__isnull=True,
@ -297,7 +297,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
pending_issues=Count( pending_issues=Count(
"label_id", "id",
filter=Q( filter=Q(
completed_at__isnull=True, completed_at__isnull=True,
archived_at__isnull=True, archived_at__isnull=True,
@ -419,13 +419,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
total_issues=Count( total_issues=Count(
"assignee_id", "id",
filter=Q(archived_at__isnull=True, is_draft=False), filter=Q(archived_at__isnull=True, is_draft=False),
), ),
) )
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"assignee_id", "id",
filter=Q( filter=Q(
completed_at__isnull=False, completed_at__isnull=False,
archived_at__isnull=True, archived_at__isnull=True,
@ -435,7 +435,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
pending_issues=Count( pending_issues=Count(
"assignee_id", "id",
filter=Q( filter=Q(
completed_at__isnull=True, completed_at__isnull=True,
archived_at__isnull=True, archived_at__isnull=True,
@ -459,13 +459,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
.values("label_name", "color", "label_id") .values("label_name", "color", "label_id")
.annotate( .annotate(
total_issues=Count( total_issues=Count(
"label_id", "id",
filter=Q(archived_at__isnull=True, is_draft=False), filter=Q(archived_at__isnull=True, is_draft=False),
), ),
) )
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"label_id", "id",
filter=Q( filter=Q(
completed_at__isnull=False, completed_at__isnull=False,
archived_at__isnull=True, archived_at__isnull=True,
@ -475,7 +475,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
pending_issues=Count( pending_issues=Count(
"label_id", "id",
filter=Q( filter=Q(
completed_at__isnull=True, completed_at__isnull=True,
archived_at__isnull=True, archived_at__isnull=True,

View File

@ -197,7 +197,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
total_issues=Count( total_issues=Count(
"assignee_id", "id",
filter=Q( filter=Q(
archived_at__isnull=True, archived_at__isnull=True,
is_draft=False, is_draft=False,
@ -206,7 +206,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"assignee_id", "id",
filter=Q( filter=Q(
completed_at__isnull=False, completed_at__isnull=False,
archived_at__isnull=True, archived_at__isnull=True,
@ -216,7 +216,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
pending_issues=Count( pending_issues=Count(
"assignee_id", "id",
filter=Q( filter=Q(
completed_at__isnull=True, completed_at__isnull=True,
archived_at__isnull=True, archived_at__isnull=True,
@ -239,7 +239,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
.values("label_name", "color", "label_id") .values("label_name", "color", "label_id")
.annotate( .annotate(
total_issues=Count( total_issues=Count(
"label_id", "id",
filter=Q( filter=Q(
archived_at__isnull=True, archived_at__isnull=True,
is_draft=False, is_draft=False,
@ -248,7 +248,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
completed_issues=Count( completed_issues=Count(
"label_id", "id",
filter=Q( filter=Q(
completed_at__isnull=False, completed_at__isnull=False,
archived_at__isnull=True, archived_at__isnull=True,
@ -258,7 +258,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
) )
.annotate( .annotate(
pending_issues=Count( pending_issues=Count(
"label_id", "id",
filter=Q( filter=Q(
completed_at__isnull=True, completed_at__isnull=True,
archived_at__isnull=True, archived_at__isnull=True,

View File

@ -47,7 +47,6 @@ export const PriorityIcon: React.FC<IPriorityIcon> = (props) => {
> >
<Icon <Icon
size={size} size={size}
viewBox="0 0 23.5 24"
className={cn( className={cn(
{ {
"text-white": priority === "urgent", "text-white": priority === "urgent",

View File

@ -86,12 +86,15 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
{ {
id: "pending", id: "pending",
color: "#3F76FF", color: "#3F76FF",
data: chartData.map((item, index) => ({ data:
chartData.length > 0
? chartData.map((item, index) => ({
index, index,
x: item.currentDate, x: item.currentDate,
y: item.pending, y: item.pending,
color: "#3F76FF", color: "#3F76FF",
})), }))
: [],
enableArea: true, enableArea: true,
}, },
{ {
@ -121,7 +124,9 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
enableArea enableArea
colors={(datum) => datum.color ?? "#3F76FF"} colors={(datum) => datum.color ?? "#3F76FF"}
customYAxisTickValues={[0, totalIssues]} customYAxisTickValues={[0, totalIssues]}
gridXValues={chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : ""))} gridXValues={
chartData.length > 0 ? chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : "")) : undefined
}
enableSlices="x" enableSlices="x"
sliceTooltip={(datum) => ( sliceTooltip={(datum) => (
<div className="rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs"> <div className="rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">

View File

@ -319,13 +319,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus); const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
const issueCount = const issueCount =
cycleDetails.total_issues === 0 cycleDetails.total_issues === 0 ? "0 Issue" : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
? "0 Issue"
: cycleDetails.total_issues === cycleDetails.completed_issues
? cycleDetails.total_issues > 1
? `${cycleDetails.total_issues}`
: `${cycleDetails.total_issues}`
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
@ -575,7 +569,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
<Transition show={open}> <Transition show={open}>
<Disclosure.Panel> <Disclosure.Panel>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{isStartValid && isEndValid ? ( {cycleDetails.distribution?.completion_chart &&
cycleDetails.start_date &&
cycleDetails.end_date ? (
<div className="h-full w-full pt-4"> <div className="h-full w-full pt-4">
<div className="flex items-start gap-4 py-2 text-xs"> <div className="flex items-start gap-4 py-2 text-xs">
<div className="flex items-center gap-3 text-custom-text-100"> <div className="flex items-center gap-3 text-custom-text-100">
@ -589,16 +585,14 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
</div> </div>
</div> </div>
</div> </div>
{cycleDetails && cycleDetails.distribution && (
<div className="relative h-40 w-80"> <div className="relative h-40 w-80">
<ProgressChart <ProgressChart
distribution={cycleDetails.distribution?.completion_chart ?? {}} distribution={cycleDetails.distribution?.completion_chart}
startDate={cycleDetails.start_date ?? ""} startDate={cycleDetails.start_date}
endDate={cycleDetails.end_date ?? ""} endDate={cycleDetails.end_date}
totalIssues={cycleDetails.total_issues} totalIssues={cycleDetails.total_issues}
/> />
</div> </div>
)}
</div> </div>
) : ( ) : (
"" ""

View File

@ -262,13 +262,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status); const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status);
const issueCount = const issueCount =
moduleDetails.total_issues === 0 moduleDetails.total_issues === 0 ? "0 Issue" : `${moduleDetails.completed_issues}/${moduleDetails.total_issues}`;
? "0 Issue"
: moduleDetails.total_issues === moduleDetails.completed_issues
? moduleDetails.total_issues > 1
? `${moduleDetails.total_issues}`
: `${moduleDetails.total_issues}`
: `${moduleDetails.completed_issues}/${moduleDetails.total_issues}`;
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
@ -582,7 +576,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
<Transition show={open}> <Transition show={open}>
<Disclosure.Panel> <Disclosure.Panel>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
{isStartValid && isEndValid ? ( {moduleDetails.start_date && moduleDetails.target_date ? (
<div className=" h-full w-full pt-4"> <div className=" h-full w-full pt-4">
<div className="flex items-start gap-4 py-2 text-xs"> <div className="flex items-start gap-4 py-2 text-xs">
<div className="flex items-center gap-3 text-custom-text-100"> <div className="flex items-center gap-3 text-custom-text-100">
@ -598,9 +592,9 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
</div> </div>
<div className="relative h-40 w-80"> <div className="relative h-40 w-80">
<ProgressChart <ProgressChart
distribution={moduleDetails.distribution?.completion_chart} distribution={moduleDetails.distribution?.completion_chart ?? {}}
startDate={moduleDetails.start_date ?? ""} startDate={moduleDetails.start_date}
endDate={moduleDetails.target_date ?? ""} endDate={moduleDetails.target_date}
totalIssues={moduleDetails.total_issues} totalIssues={moduleDetails.total_issues}
/> />
</div> </div>

View File

@ -12,7 +12,7 @@ import { ProjectSidebarList } from "components/project";
import { useApplication } from "hooks/store"; import { useApplication } from "hooks/store";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
export interface IAppSidebar { } export interface IAppSidebar {}
export const AppSidebar: FC<IAppSidebar> = observer(() => { export const AppSidebar: FC<IAppSidebar> = observer(() => {
// store hooks // store hooks
@ -34,24 +34,23 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
} }
}; };
handleResize(); handleResize();
window.addEventListener('resize', handleResize); window.addEventListener("resize", handleResize);
return () => { return () => {
window.removeEventListener('resize', handleResize); window.removeEventListener("resize", handleResize);
}; };
}, [themStore]); }, [themStore]);
return ( return (
<div <div
className={`inset-y-0 z-30 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300 className={`inset-y-0 z-20 flex h-full flex-shrink-0 flex-grow-0 flex-col border-r border-custom-sidebar-border-200 bg-custom-sidebar-background-100 duration-300
fixed md:relative fixed md:relative
${themStore.sidebarCollapsed ? "-ml-[280px]" : ""} ${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}
sm:${themStore.sidebarCollapsed ? "-ml-[280px]" : ""} sm:${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}
md:ml-0 ${themStore.sidebarCollapsed ? 'w-[80px]' : 'w-[280px]'} md:ml-0 ${themStore.sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
lg:ml-0 ${themStore.sidebarCollapsed ? 'w-[80px]' : 'w-[280px]'} lg:ml-0 ${themStore.sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
`} > `}
<div >
ref={ref} <div ref={ref} className="flex h-full w-full flex-1 flex-col">
className="flex h-full w-full flex-1 flex-col">
<WorkspaceSidebarDropdown /> <WorkspaceSidebarDropdown />
<WorkspaceSidebarQuickAction /> <WorkspaceSidebarQuickAction />
<WorkspaceSidebarMenu /> <WorkspaceSidebarMenu />
@ -61,6 +60,3 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
</div> </div>
); );
}); });

View File

@ -304,6 +304,7 @@ export class CycleStore implements ICycleStore {
set(this.cycleMap, [cycleId], { ...this.cycleMap?.[cycleId], ...data }); set(this.cycleMap, [cycleId], { ...this.cycleMap?.[cycleId], ...data });
}); });
const response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data); const response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data);
this.fetchCycleDetails(workspaceSlug, projectId, cycleId);
return response; return response;
} catch (error) { } catch (error) {
console.log("Failed to patch cycle from cycle store"); console.log("Failed to patch cycle from cycle store");

View File

@ -152,6 +152,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
const params = this.rootIssueStore?.cycleIssuesFilter?.appliedFilters; const params = this.rootIssueStore?.cycleIssuesFilter?.appliedFilters;
const response = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params); const response = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
runInAction(() => { runInAction(() => {
set( set(
@ -182,6 +183,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
await this.addIssueToCycle(workspaceSlug, projectId, cycleId, [response.id]); await this.addIssueToCycle(workspaceSlug, projectId, cycleId, [response.id]);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
return response; return response;
} catch (error) { } catch (error) {
@ -218,6 +220,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
if (!cycleId) throw new Error("Cycle Id is required"); if (!cycleId) throw new Error("Cycle Id is required");
const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
const issueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === issueId); const issueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === issueId);
if (issueIndex >= 0) if (issueIndex >= 0)
@ -246,6 +249,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
}); });
const response = await this.createIssue(workspaceSlug, projectId, data, cycleId); const response = await this.createIssue(workspaceSlug, projectId, data, cycleId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
const quickAddIssueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === data.id); const quickAddIssueIndex = this.issues[cycleId].findIndex((_issueId) => _issueId === data.id);
if (quickAddIssueIndex >= 0) if (quickAddIssueIndex >= 0)
@ -273,6 +277,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
issueIds.forEach((issueId) => { issueIds.forEach((issueId) => {
this.rootStore.issues.updateIssue(issueId, { cycle_id: cycleId }); this.rootStore.issues.updateIssue(issueId, { cycle_id: cycleId });
}); });
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
return issueToCycle; return issueToCycle;
} catch (error) { } catch (error) {
@ -289,6 +294,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
this.rootStore.issues.updateIssue(issueId, { cycle_id: null }); this.rootStore.issues.updateIssue(issueId, { cycle_id: null });
const response = await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId); const response = await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
return response; return response;
} catch (error) { } catch (error) {

View File

@ -156,6 +156,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
const params = this.rootIssueStore?.moduleIssuesFilter?.appliedFilters; const params = this.rootIssueStore?.moduleIssuesFilter?.appliedFilters;
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params); const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
runInAction(() => { runInAction(() => {
set( set(
@ -187,6 +188,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data); const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id]); await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id]);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
return response; return response;
} catch (error) { } catch (error) {
@ -223,6 +225,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
if (!moduleId) throw new Error("Module Id is required"); if (!moduleId) throw new Error("Module Id is required");
const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId); const response = await this.rootIssueStore.projectIssues.removeIssue(workspaceSlug, projectId, issueId);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
const issueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === issueId); const issueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === issueId);
if (issueIndex >= 0) if (issueIndex >= 0)
@ -251,6 +254,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
}); });
const response = await this.createIssue(workspaceSlug, projectId, data, moduleId); const response = await this.createIssue(workspaceSlug, projectId, data, moduleId);
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
const quickAddIssueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === data.id); const quickAddIssueIndex = this.issues[moduleId].findIndex((_issueId) => _issueId === data.id);
if (quickAddIssueIndex >= 0) if (quickAddIssueIndex >= 0)
@ -284,6 +288,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
else return uniq(concat(issueModuleIds, [moduleId])); else return uniq(concat(issueModuleIds, [moduleId]));
}); });
}); });
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
return issueToModule; return issueToModule;
} catch (error) { } catch (error) {
@ -314,6 +319,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
moduleId, moduleId,
issueIds issueIds
); );
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
return response; return response;
} catch (error) { } catch (error) {

View File

@ -196,6 +196,7 @@ export class ModulesStore implements IModuleStore {
set(this.moduleMap, [moduleId], { ...originalModuleDetails, ...data }); set(this.moduleMap, [moduleId], { ...originalModuleDetails, ...data });
}); });
const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data); const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data);
this.fetchModuleDetails(workspaceSlug, projectId, moduleId);
return response; return response;
} catch (error) { } catch (error) {
console.error("Failed to update module in module store", error); console.error("Failed to update module in module store", error);