[WEB-2879] chore sub issue analytics improvements (#6275)

* chore: epics type added to package

* chore: epic analytics helper function added

* chore: sub issue analytics mutation improvement
This commit is contained in:
Anmol Singh Bhatia 2024-12-24 20:52:03 +05:30 committed by GitHub
parent ff936887d2
commit fedcdf0c84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 104 additions and 18 deletions

16
packages/types/src/epics.d.ts vendored Normal file
View file

@ -0,0 +1,16 @@
export type TEpicAnalyticsGroup =
| "backlog_issues"
| "unstarted_issues"
| "started_issues"
| "completed_issues"
| "cancelled_issues"
| "overdue_issues";
export type TEpicAnalytics = {
backlog_issues: number;
unstarted_issues: number;
started_issues: number;
completed_issues: number;
cancelled_issues: number;
overdue_issues: number;
};

View file

@ -36,3 +36,4 @@ export * from "./workspace-draft-issues/base";
export * from "./command-palette";
export * from "./timezone";
export * from "./activity";
export * from "./epics";

View file

@ -0,0 +1,15 @@
import { TEpicAnalyticsGroup } from "@plane/types";
export const updateEpicAnalytics = () => {
const updateAnalytics = (
workspaceSlug: string,
projectId: string,
epicId: string,
data: {
incrementStateGroupCount?: TEpicAnalyticsGroup;
decrementStateGroupCount?: TEpicAnalyticsGroup;
}
) => {};
return { updateAnalytics };
};

View file

@ -1,13 +1,15 @@
"use client";
import { useMemo } from "react";
import { usePathname } from "next/navigation";
import { useParams, usePathname } from "next/navigation";
import { EIssueServiceType } from "@plane/constants";
import { TIssue, TIssueServiceType } from "@plane/types";
import { TOAST_TYPE, setToast } from "@plane/ui";
// helper
import { copyTextToClipboard } from "@/helpers/string.helper";
// hooks
import { useEventTracker, useIssueDetail } from "@/hooks/store";
import { useEventTracker, useIssueDetail, useProjectState } from "@/hooks/store";
// plane-web
import { updateEpicAnalytics } from "@/plane-web/helpers/epic-analytics";
// type
import { TSubIssueOperations } from "../../sub-issues";
@ -20,16 +22,26 @@ export type TRelationIssueOperations = {
export const useSubIssueOperations = (
issueServiceType: TIssueServiceType = EIssueServiceType.ISSUES
): TSubIssueOperations => {
// router
const { epicId: epicIdParam } = useParams();
const pathname = usePathname();
// store hooks
const {
issue: { getIssueById },
subIssues: { setSubIssueHelpers },
fetchSubIssues,
createSubIssues,
updateSubIssue,
deleteSubIssue,
} = useIssueDetail();
const { removeSubIssue } = useIssueDetail(issueServiceType);
const { getStateById } = useProjectState();
const { peekIssue: epicPeekIssue } = useIssueDetail(EIssueServiceType.EPICS);
// const { updateEpicAnalytics } = useIssueTypes();
const { updateAnalytics } = updateEpicAnalytics();
const { fetchSubIssues, removeSubIssue } = useIssueDetail(issueServiceType);
const { captureIssueEvent } = useEventTracker();
const pathname = usePathname();
// derived values
const epicId = epicIdParam || epicPeekIssue?.issueId;
const subIssueOperations: TSubIssueOperations = useMemo(
() => ({
@ -39,7 +51,7 @@ export const useSubIssueOperations = (
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Link Copied!",
message: "Issue link copied to clipboard.",
message: `${issueServiceType === EIssueServiceType.ISSUES ? "Issue" : "Epic"} link copied to clipboard`,
});
});
},
@ -50,7 +62,7 @@ export const useSubIssueOperations = (
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error fetching sub-issues",
message: `Error fetching ${issueServiceType === EIssueServiceType.ISSUES ? "sub-issues" : "issues"}`,
});
}
},
@ -60,13 +72,13 @@ export const useSubIssueOperations = (
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Sub-issues added successfully",
message: `${issueServiceType === EIssueServiceType.ISSUES ? "Sub-issues" : "Issues"} added successfully`,
});
} catch (error) {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Error adding sub-issue",
message: `Error adding ${issueServiceType === EIssueServiceType.ISSUES ? "sub-issues" : "issues"}`,
});
}
},
@ -82,6 +94,30 @@ export const useSubIssueOperations = (
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await updateSubIssue(workspaceSlug, projectId, parentIssueId, issueId, issueData, oldIssue, fromModal);
if (issueServiceType === EIssueServiceType.EPICS) {
const oldState = getStateById(oldIssue?.state_id)?.group;
if (oldState && oldIssue && issueData && epicId) {
// Check if parent_id is changed if yes then decrement the epic analytics count
if (issueData.parent_id && oldIssue?.parent_id && issueData.parent_id !== oldIssue?.parent_id) {
updateAnalytics(workspaceSlug, projectId, epicId.toString(), {
decrementStateGroupCount: `${oldState}_issues`,
});
}
// Check if state_id is changed if yes then decrement the old state group count and increment the new state group count
if (issueData.state_id) {
const newState = getStateById(issueData.state_id)?.group;
if (oldState && newState && oldState !== newState) {
updateAnalytics(workspaceSlug, projectId, epicId.toString(), {
decrementStateGroupCount: `${oldState}_issues`,
incrementStateGroupCount: `${newState}_issues`,
});
}
}
}
}
captureIssueEvent({
eventName: "Sub-issue updated",
payload: { ...oldIssue, ...issueData, state: "SUCCESS", element: "Issue detail page" },
@ -118,6 +154,16 @@ export const useSubIssueOperations = (
try {
setSubIssueHelpers(parentIssueId, "issue_loader", issueId);
await removeSubIssue(workspaceSlug, projectId, parentIssueId, issueId);
if (issueServiceType === EIssueServiceType.EPICS) {
const issueBeforeRemoval = getIssueById(issueId);
const oldState = getStateById(issueBeforeRemoval?.state_id)?.group;
if (epicId && oldState) {
updateAnalytics(workspaceSlug, projectId, epicId.toString(), {
decrementStateGroupCount: `${oldState}_issues`,
});
}
}
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",

View file

@ -3,6 +3,7 @@
import React from "react";
import { observer } from "mobx-react";
import { ChevronRight, X, Pencil, Trash, Link as LinkIcon, Loader } from "lucide-react";
import { EIssueServiceType } from "@plane/constants";
import { TIssue, TIssueServiceType } from "@plane/types";
// ui
import { ControlLink, CustomMenu, Tooltip } from "@plane/ui";
@ -50,14 +51,14 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
disabled,
handleIssueCrudState,
subIssueOperations,
issueServiceType = EIssueServiceType.ISSUES,
} = props;
const {
issue: { getIssueById },
subIssues: { subIssueHelpersByIssueId, setSubIssueHelpers },
toggleCreateIssueModal,
toggleDeleteIssueModal,
} = useIssueDetail();
} = useIssueDetail(issueServiceType);
const { toggleCreateIssueModal, toggleDeleteIssueModal } = useIssueDetail(issueServiceType);
const project = useProject();
const { getProjectStates } = useProjectState();
const { handleRedirection } = useIssuePeekOverviewRedirection();
@ -164,6 +165,7 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
issueId={issueId}
disabled={disabled}
subIssueOperations={subIssueOperations}
issueServiceType={issueServiceType}
/>
</div>
@ -209,7 +211,7 @@ export const IssueListItem: React.FC<ISubIssues> = observer((props) => {
>
<div className="flex items-center gap-2">
<X className="h-3.5 w-3.5" strokeWidth={2} />
<span>Remove parent issue</span>
<span>{`Remove ${issueServiceType === EIssueServiceType.ISSUES ? "parent" : ""} issue`}</span>
</div>
</CustomMenu.MenuItem>
)}

View file

@ -17,11 +17,11 @@ export interface IIssueProperty {
}
export const IssueProperty: React.FC<IIssueProperty> = (props) => {
const { workspaceSlug, parentIssueId, issueId, disabled, subIssueOperations } = props;
const { workspaceSlug, parentIssueId, issueId, disabled, subIssueOperations, issueServiceType } = props;
// hooks
const {
issue: { getIssueById },
} = useIssueDetail();
} = useIssueDetail(issueServiceType);
const issue = getIssueById(issueId);

View file

@ -13,7 +13,7 @@ import update from "lodash/update";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
// plane constants
import { EIssueLayoutTypes, ALL_ISSUES } from "@plane/constants";
import { EIssueLayoutTypes, ALL_ISSUES, EIssueServiceType } from "@plane/constants";
// types
import {
TIssue,
@ -203,7 +203,12 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
// API Abort controller
controller: AbortController;
constructor(_rootStore: IIssueRootStore, issueFilterStore: IBaseIssueFilterStore, isArchived = false) {
constructor(
_rootStore: IIssueRootStore,
issueFilterStore: IBaseIssueFilterStore,
isArchived = false,
serviceType = EIssueServiceType.ISSUES
) {
makeObservable(this, {
// observable
loader: observable,
@ -257,7 +262,7 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
this.isArchived = isArchived;
this.issueService = new IssueService();
this.issueService = new IssueService(serviceType);
this.issueArchiveService = new IssueArchiveService();
this.issueDraftService = new IssueDraftService();
this.moduleService = new ModuleService();

View file

@ -0,0 +1 @@
export * from "ce/helpers/epic-analytics";