forked from github/plane
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:
parent
efaba43494
commit
7d07afd59c
@ -242,13 +242,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
.values("display_name", "assignee_id", "avatar")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=False,
|
||||
archived_at__isnull=True,
|
||||
@ -258,7 +258,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
pending_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
@ -281,13 +281,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=False,
|
||||
archived_at__isnull=True,
|
||||
@ -297,7 +297,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
pending_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
@ -419,13 +419,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=False,
|
||||
archived_at__isnull=True,
|
||||
@ -435,7 +435,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
pending_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
@ -459,13 +459,13 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=False,
|
||||
archived_at__isnull=True,
|
||||
@ -475,7 +475,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
pending_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
|
@ -197,7 +197,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
@ -206,7 +206,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=False,
|
||||
archived_at__isnull=True,
|
||||
@ -216,7 +216,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
pending_issues=Count(
|
||||
"assignee_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
@ -239,7 +239,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
.values("label_name", "color", "label_id")
|
||||
.annotate(
|
||||
total_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
archived_at__isnull=True,
|
||||
is_draft=False,
|
||||
@ -248,7 +248,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
completed_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=False,
|
||||
archived_at__isnull=True,
|
||||
@ -258,7 +258,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
.annotate(
|
||||
pending_issues=Count(
|
||||
"label_id",
|
||||
"id",
|
||||
filter=Q(
|
||||
completed_at__isnull=True,
|
||||
archived_at__isnull=True,
|
||||
|
@ -47,7 +47,6 @@ export const PriorityIcon: React.FC<IPriorityIcon> = (props) => {
|
||||
>
|
||||
<Icon
|
||||
size={size}
|
||||
viewBox="0 0 23.5 24"
|
||||
className={cn(
|
||||
{
|
||||
"text-white": priority === "urgent",
|
||||
|
@ -86,12 +86,15 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
|
||||
{
|
||||
id: "pending",
|
||||
color: "#3F76FF",
|
||||
data: chartData.map((item, index) => ({
|
||||
index,
|
||||
x: item.currentDate,
|
||||
y: item.pending,
|
||||
color: "#3F76FF",
|
||||
})),
|
||||
data:
|
||||
chartData.length > 0
|
||||
? chartData.map((item, index) => ({
|
||||
index,
|
||||
x: item.currentDate,
|
||||
y: item.pending,
|
||||
color: "#3F76FF",
|
||||
}))
|
||||
: [],
|
||||
enableArea: true,
|
||||
},
|
||||
{
|
||||
@ -121,7 +124,9 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
|
||||
enableArea
|
||||
colors={(datum) => datum.color ?? "#3F76FF"}
|
||||
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"
|
||||
sliceTooltip={(datum) => (
|
||||
<div className="rounded-md border border-custom-border-200 bg-custom-background-80 p-2 text-xs">
|
||||
|
@ -319,13 +319,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
||||
|
||||
const issueCount =
|
||||
cycleDetails.total_issues === 0
|
||||
? "0 Issue"
|
||||
: cycleDetails.total_issues === cycleDetails.completed_issues
|
||||
? cycleDetails.total_issues > 1
|
||||
? `${cycleDetails.total_issues}`
|
||||
: `${cycleDetails.total_issues}`
|
||||
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||
cycleDetails.total_issues === 0 ? "0 Issue" : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
@ -575,7 +569,9 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
<Transition show={open}>
|
||||
<Disclosure.Panel>
|
||||
<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="flex items-start gap-4 py-2 text-xs">
|
||||
<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>
|
||||
{cycleDetails && cycleDetails.distribution && (
|
||||
<div className="relative h-40 w-80">
|
||||
<ProgressChart
|
||||
distribution={cycleDetails.distribution?.completion_chart ?? {}}
|
||||
startDate={cycleDetails.start_date ?? ""}
|
||||
endDate={cycleDetails.end_date ?? ""}
|
||||
totalIssues={cycleDetails.total_issues}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="relative h-40 w-80">
|
||||
<ProgressChart
|
||||
distribution={cycleDetails.distribution?.completion_chart}
|
||||
startDate={cycleDetails.start_date}
|
||||
endDate={cycleDetails.end_date}
|
||||
totalIssues={cycleDetails.total_issues}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
""
|
||||
|
@ -262,13 +262,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status);
|
||||
|
||||
const issueCount =
|
||||
moduleDetails.total_issues === 0
|
||||
? "0 Issue"
|
||||
: moduleDetails.total_issues === moduleDetails.completed_issues
|
||||
? moduleDetails.total_issues > 1
|
||||
? `${moduleDetails.total_issues}`
|
||||
: `${moduleDetails.total_issues}`
|
||||
: `${moduleDetails.completed_issues}/${moduleDetails.total_issues}`;
|
||||
moduleDetails.total_issues === 0 ? "0 Issue" : `${moduleDetails.completed_issues}/${moduleDetails.total_issues}`;
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
@ -582,7 +576,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
<Transition show={open}>
|
||||
<Disclosure.Panel>
|
||||
<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="flex items-start gap-4 py-2 text-xs">
|
||||
<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 className="relative h-40 w-80">
|
||||
<ProgressChart
|
||||
distribution={moduleDetails.distribution?.completion_chart}
|
||||
startDate={moduleDetails.start_date ?? ""}
|
||||
endDate={moduleDetails.target_date ?? ""}
|
||||
distribution={moduleDetails.distribution?.completion_chart ?? {}}
|
||||
startDate={moduleDetails.start_date}
|
||||
endDate={moduleDetails.target_date}
|
||||
totalIssues={moduleDetails.total_issues}
|
||||
/>
|
||||
</div>
|
||||
|
@ -12,7 +12,7 @@ import { ProjectSidebarList } from "components/project";
|
||||
import { useApplication } from "hooks/store";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
|
||||
export interface IAppSidebar { }
|
||||
export interface IAppSidebar {}
|
||||
|
||||
export const AppSidebar: FC<IAppSidebar> = observer(() => {
|
||||
// store hooks
|
||||
@ -34,24 +34,23 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
|
||||
}
|
||||
};
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [themStore]);
|
||||
|
||||
return (
|
||||
<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
|
||||
${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}
|
||||
sm:${themStore.sidebarCollapsed ? "-ml-[280px]" : ""}
|
||||
md:ml-0 ${themStore.sidebarCollapsed ? 'w-[80px]' : 'w-[280px]'}
|
||||
lg:ml-0 ${themStore.sidebarCollapsed ? 'w-[80px]' : 'w-[280px]'}
|
||||
`} >
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex h-full w-full flex-1 flex-col">
|
||||
md:ml-0 ${themStore.sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
|
||||
lg:ml-0 ${themStore.sidebarCollapsed ? "w-[80px]" : "w-[280px]"}
|
||||
`}
|
||||
>
|
||||
<div ref={ref} className="flex h-full w-full flex-1 flex-col">
|
||||
<WorkspaceSidebarDropdown />
|
||||
<WorkspaceSidebarQuickAction />
|
||||
<WorkspaceSidebarMenu />
|
||||
@ -61,6 +60,3 @@ export const AppSidebar: FC<IAppSidebar> = observer(() => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
@ -304,6 +304,7 @@ export class CycleStore implements ICycleStore {
|
||||
set(this.cycleMap, [cycleId], { ...this.cycleMap?.[cycleId], ...data });
|
||||
});
|
||||
const response = await this.cycleService.patchCycle(workspaceSlug, projectId, cycleId, data);
|
||||
this.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to patch cycle from cycle store");
|
||||
|
@ -152,6 +152,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
|
||||
const params = this.rootIssueStore?.cycleIssuesFilter?.appliedFilters;
|
||||
const response = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params);
|
||||
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
|
||||
runInAction(() => {
|
||||
set(
|
||||
@ -182,6 +183,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
|
||||
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
||||
await this.addIssueToCycle(workspaceSlug, projectId, cycleId, [response.id]);
|
||||
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
@ -218,6 +220,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
if (!cycleId) throw new Error("Cycle Id is required");
|
||||
|
||||
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);
|
||||
if (issueIndex >= 0)
|
||||
@ -246,6 +249,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
});
|
||||
|
||||
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);
|
||||
if (quickAddIssueIndex >= 0)
|
||||
@ -273,6 +277,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
issueIds.forEach((issueId) => {
|
||||
this.rootStore.issues.updateIssue(issueId, { cycle_id: cycleId });
|
||||
});
|
||||
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
|
||||
return issueToCycle;
|
||||
} catch (error) {
|
||||
@ -289,6 +294,7 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
this.rootStore.issues.updateIssue(issueId, { cycle_id: null });
|
||||
|
||||
const response = await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, cycleId, issueId);
|
||||
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
|
@ -156,6 +156,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
|
||||
const params = this.rootIssueStore?.moduleIssuesFilter?.appliedFilters;
|
||||
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
|
||||
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||
|
||||
runInAction(() => {
|
||||
set(
|
||||
@ -187,6 +188,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
|
||||
const response = await this.rootIssueStore.projectIssues.createIssue(workspaceSlug, projectId, data);
|
||||
await this.addIssuesToModule(workspaceSlug, projectId, moduleId, [response.id]);
|
||||
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
@ -223,6 +225,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
if (!moduleId) throw new Error("Module Id is required");
|
||||
|
||||
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);
|
||||
if (issueIndex >= 0)
|
||||
@ -251,6 +254,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
});
|
||||
|
||||
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);
|
||||
if (quickAddIssueIndex >= 0)
|
||||
@ -284,6 +288,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
else return uniq(concat(issueModuleIds, [moduleId]));
|
||||
});
|
||||
});
|
||||
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||
|
||||
return issueToModule;
|
||||
} catch (error) {
|
||||
@ -314,6 +319,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
moduleId,
|
||||
issueIds
|
||||
);
|
||||
this.rootIssueStore.rootStore.module.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
|
@ -196,6 +196,7 @@ export class ModulesStore implements IModuleStore {
|
||||
set(this.moduleMap, [moduleId], { ...originalModuleDetails, ...data });
|
||||
});
|
||||
const response = await this.moduleService.patchModule(workspaceSlug, projectId, moduleId, data);
|
||||
this.fetchModuleDetails(workspaceSlug, projectId, moduleId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Failed to update module in module store", error);
|
||||
|
Loading…
Reference in New Issue
Block a user