[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:
parent
ff936887d2
commit
fedcdf0c84
8 changed files with 104 additions and 18 deletions
16
packages/types/src/epics.d.ts
vendored
Normal file
16
packages/types/src/epics.d.ts
vendored
Normal 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;
|
||||
};
|
||||
1
packages/types/src/index.d.ts
vendored
1
packages/types/src/index.d.ts
vendored
|
|
@ -36,3 +36,4 @@ export * from "./workspace-draft-issues/base";
|
|||
export * from "./command-palette";
|
||||
export * from "./timezone";
|
||||
export * from "./activity";
|
||||
export * from "./epics";
|
||||
|
|
|
|||
15
web/ce/helpers/epic-analytics.ts
Normal file
15
web/ce/helpers/epic-analytics.ts
Normal 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 };
|
||||
};
|
||||
|
|
@ -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!",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
1
web/ee/helpers/epic-analytics.ts
Normal file
1
web/ee/helpers/epic-analytics.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "ce/helpers/epic-analytics";
|
||||
Loading…
Add table
Add a link
Reference in a new issue