Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-522] chore: enabled estimate point analytics for module and cycle #4763

Merged
merged 31 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b302add
chore: updated modal and form validations
gurusainath Jun 10, 2024
3996e58
Merge branch 'develop' of gurusainath:makeplane/plane into estimate-v…
gurusainath Jun 10, 2024
92c1cff
chore: module estimate analytics
NarayanBavisetti Jun 11, 2024
62b8638
Merge branch 'estimate-module-cycle-analytics' of gurusainath:makepla…
gurusainath Jun 11, 2024
866b0df
Merge branch 'develop' of github.com:makeplane/plane into estimate-mo…
NarayanBavisetti Jun 11, 2024
5e7cd78
Merge branch 'estimate-module-cycle-analytics' of github.com:makeplan…
NarayanBavisetti Jun 11, 2024
6ccdba2
chore: state analytics
NarayanBavisetti Jun 11, 2024
4c28f0f
chore: cycle estimate analytics
NarayanBavisetti Jun 11, 2024
7c2f4c2
Merge branch 'develop' of gurusainath:makeplane/plane into estimate-m…
gurusainath Jun 11, 2024
32d5ab8
Merge branch 'estimate-module-cycle-analytics' of gurusainath:makepla…
gurusainath Jun 11, 2024
183a184
chore: module points serializer
NarayanBavisetti Jun 11, 2024
b327873
Merge branch 'estimate-module-cycle-analytics' of gurusainath:makepla…
gurusainath Jun 11, 2024
9a556ad
chore: added fields in serializer
NarayanBavisetti Jun 11, 2024
d620e72
chore: module state estimate points
gurusainath Jun 11, 2024
498246c
dev: resolved merge conflicts
gurusainath Jun 11, 2024
e0f0f07
dev: updated module analytics
gurusainath Jun 11, 2024
08f086f
dev: updated hover description on the burndown
gurusainath Jun 11, 2024
9b90038
Merge branch 'develop' of gurusainath:makeplane/plane into estimate-m…
gurusainath Jun 11, 2024
257054d
dev: UI and module total percentage validation
gurusainath Jun 11, 2024
bfb1b09
chore: estimate points structure change
NarayanBavisetti Jun 11, 2024
551569a
Merge branch 'estimate-module-cycle-analytics' of github.com:makeplan…
NarayanBavisetti Jun 11, 2024
1c376f1
chore: module burndown
NarayanBavisetti Jun 11, 2024
e768041
chore: key values changed
NarayanBavisetti Jun 11, 2024
4df4f25
chore: cycle progress snapshot
NarayanBavisetti Jun 12, 2024
b774cb5
chore: updated module estimate points
gurusainath Jun 12, 2024
1065b38
chore: cycle detail endpoint
NarayanBavisetti Jun 12, 2024
b61efbd
chore: progress snapshot payload change
NarayanBavisetti Jun 12, 2024
f5f7fae
chore: resolved merge conflicts
gurusainath Jun 12, 2024
350ac3f
Merge branch 'estimate-module-cycle-analytics' of gurusainath:makepla…
gurusainath Jun 12, 2024
aa6ec67
chore: updated issue and point dropdown in active cycle
gurusainath Jun 12, 2024
2db70b3
chore: optimized grouped issues count in cycle and module
gurusainath Jun 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
chore: updated modal and form validations
  • Loading branch information
gurusainath committed Jun 10, 2024
commit b302addcd9967e8009b226c6dfb5b8768852d9ae
4 changes: 2 additions & 2 deletions web/components/core/modals/modal-core.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export enum EModalWidth {

type Props = {
children: React.ReactNode;
handleClose: () => void;
handleClose?: () => void;
isOpen: boolean;
position?: EModalPosition;
width?: EModalWidth;
Expand All @@ -27,7 +27,7 @@ export const ModalCore: React.FC<Props> = (props) => {

return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-20" onClose={handleClose}>
<Dialog as="div" className="relative z-20" onClose={() => handleClose && handleClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
Expand Down
94 changes: 56 additions & 38 deletions web/components/estimates/create/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
// states
const [estimateSystem, setEstimateSystem] = useState<TEstimateSystemKeys>(EEstimateSystem.POINTS);
const [estimatePoints, setEstimatePoints] = useState<TEstimatePointsObject[] | undefined>(undefined);
const [estimatePointCreate, setEstimatePointCreate] = useState<TEstimatePointsObject[] | undefined>(undefined);
const [estimatePointCreateError, setEstimatePointCreateError] = useState<number[]>([]);
const [buttonLoader, setButtonLoader] = useState(false);

const handleUpdatePoints = (newPoints: TEstimatePointsObject[] | undefined) => setEstimatePoints(newPoints);
Expand All @@ -40,41 +42,47 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
}, [isOpen]);

const handleCreateEstimate = async () => {
try {
if (!workspaceSlug || !projectId || !estimatePoints) return;
setButtonLoader(true);
const payload: IEstimateFormData = {
estimate: {
name: ESTIMATE_SYSTEMS[estimateSystem]?.name,
type: estimateSystem,
last_used: true,
},
estimate_points: estimatePoints,
};
await createEstimate(workspaceSlug, projectId, payload);
setEstimatePointCreateError([]);
if (estimatePointCreate === undefined || estimatePointCreate?.length === 0) {
try {
if (!workspaceSlug || !projectId || !estimatePoints) return;

setButtonLoader(false);
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Estimate created",
message: "A new estimate has been added in your project.",
});
handleClose();
} catch (error) {
setButtonLoader(false);
setToast({
type: TOAST_TYPE.ERROR,
title: "Estimate creation failed",
message: "We were unable to create the new estimate, please try again.",
});
setButtonLoader(true);
const payload: IEstimateFormData = {
estimate: {
name: ESTIMATE_SYSTEMS[estimateSystem]?.name,
type: estimateSystem,
last_used: true,
},
estimate_points: estimatePoints,
};
await createEstimate(workspaceSlug, projectId, payload);

setButtonLoader(false);
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Estimate created",
message: "A new estimate has been added in your project.",
});
handleClose();
} catch (error) {
setButtonLoader(false);
setToast({
type: TOAST_TYPE.ERROR,
title: "Estimate creation failed",
message: "We were unable to create the new estimate, please try again.",
});
}
} else {
setEstimatePointCreateError(estimatePointCreate.map((point) => point.key));
}
};

// derived values
const renderEstimateStepsCount = useMemo(() => (estimatePoints ? "2" : "1"), [estimatePoints]);

return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<div className="relative space-y-6 py-5">
{/* heading */}
<div className="relative flex justify-between items-center gap-2 px-5">
Expand All @@ -90,7 +98,7 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
<ChevronLeft className="w-4 h-4" />
</div>
)}
<div className="text-xl font-medium text-custom-text-100">New Estimate System</div>
<div className="text-xl font-medium text-custom-text-100">New estimate system</div>
</div>
<div className="text-xs text-gray-400">Step {renderEstimateStepsCount} of 2</div>
</div>
Expand All @@ -107,16 +115,26 @@ export const CreateEstimateModal: FC<TCreateEstimateModal> = observer((props) =>
/>
)}
{estimatePoints && (
<>
<EstimatePointCreateRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
estimateId={undefined}
estimateType={estimateSystem}
estimatePoints={estimatePoints}
setEstimatePoints={setEstimatePoints}
/>
</>
<EstimatePointCreateRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
estimateId={undefined}
estimateType={estimateSystem}
estimatePoints={estimatePoints}
setEstimatePoints={setEstimatePoints}
estimatePointCreate={estimatePointCreate}
setEstimatePointCreate={(value) => {
setEstimatePointCreateError([]);
setEstimatePointCreate(value);
}}
estimatePointCreateError={estimatePointCreateError}
/>
)}
{estimatePointCreateError.length > 0 && (
<div className="pt-5 text-sm text-red-500">
Estimate points can&apos;t be empty. Enter a value in each field or remove those you don&apos;t have
values for.
</div>
)}
</div>

Expand Down
2 changes: 1 addition & 1 deletion web/components/estimates/delete/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const DeleteEstimateModal: FC<TDeleteEstimateModal> = observer((props) =>
};

return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<ModalCore isOpen={isOpen} position={EModalPosition.TOP} width={EModalWidth.XXL}>
<div className="relative space-y-6 py-5">
{/* heading */}
<div className="relative flex justify-between items-center gap-2 px-5">
Expand Down
48 changes: 30 additions & 18 deletions web/components/estimates/points/create-root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Dispatch, FC, SetStateAction, useCallback, useState } from "react";
import { Dispatch, FC, SetStateAction, useCallback } from "react";
import { observer } from "mobx-react";
import { Plus } from "lucide-react";
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types";
Expand All @@ -17,13 +17,24 @@ type TEstimatePointCreateRoot = {
estimateType: TEstimateSystemKeys;
estimatePoints: TEstimatePointsObject[];
setEstimatePoints: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>;
estimatePointCreate: TEstimatePointsObject[] | undefined;
setEstimatePointCreate: Dispatch<SetStateAction<TEstimatePointsObject[] | undefined>>;
estimatePointCreateError: number[];
};

export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((props) => {
// props
const { workspaceSlug, projectId, estimateId, estimateType, estimatePoints, setEstimatePoints } = props;
// states
const [estimatePointCreate, setEstimatePointCreate] = useState<TEstimatePointsObject[] | undefined>(undefined);
const {
workspaceSlug,
projectId,
estimateId,
estimateType,
estimatePoints,
setEstimatePoints,
estimatePointCreate,
setEstimatePointCreate,
estimatePointCreateError,
} = props;

const handleEstimatePoint = useCallback(
(mode: "add" | "remove" | "update", value: TEstimatePointsObject) => {
Expand Down Expand Up @@ -77,9 +88,19 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
setEstimatePoints(() => updatedEstimateKeysOrder);
};

const handleCreate = () => {
if (estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= maxEstimatesCount - 1) {
handleEstimatePointCreate("add", {
id: undefined,
key: estimatePoints.length + (estimatePointCreate?.length || 0) + 1,
value: "",
});
}
};

if (!workspaceSlug || !projectId) return <></>;
return (
<div className="space-y-3">
<div className="space-y-1">
<div className="text-sm font-medium text-custom-text-200 capitalize">{estimateType}</div>

<div>
Expand Down Expand Up @@ -118,21 +139,12 @@ export const EstimatePointCreateRoot: FC<TEstimatePointCreateRoot> = observer((p
handleEstimatePoint("add", { ...estimatePoint, value: estimatePointValue })
}
closeCallBack={() => handleEstimatePointCreate("remove", estimatePoint)}
handleCreateCallback={() => estimatePointCreate.length === 1 && handleCreate()}
isError={estimatePointCreateError.includes(estimatePoint.key) ? true : false}
/>
))}
{estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= maxEstimatesCount && (
<Button
variant="link-primary"
size="sm"
prependIcon={<Plus />}
onClick={() =>
handleEstimatePointCreate("add", {
id: undefined,
key: estimatePoints.length + (estimatePointCreate?.length || 0) + 1,
value: "",
})
}
>
{estimatePoints && estimatePoints.length + (estimatePointCreate?.length || 0) <= maxEstimatesCount - 1 && (
<Button variant="link-primary" size="sm" prependIcon={<Plus />} onClick={handleCreate}>
Add {estimateType}
</Button>
)}
Expand Down
74 changes: 44 additions & 30 deletions web/components/estimates/points/create.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { FC, MouseEvent, FocusEvent, useState } from "react";
import { FC, useState, FormEvent, useEffect } from "react";
import { observer } from "mobx-react";
import { Check, Info, X } from "lucide-react";
import { TEstimatePointsObject, TEstimateSystemKeys } from "@plane/types";
Expand All @@ -21,6 +21,8 @@ type TEstimatePointCreate = {
estimatePoints: TEstimatePointsObject[];
handleEstimatePointValue?: (estimateValue: string) => void;
closeCallBack: () => void;
handleCreateCallback: () => void;
isError: boolean;
};

export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) => {
Expand All @@ -32,6 +34,8 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
estimatePoints,
handleEstimatePointValue,
closeCallBack,
handleCreateCallback,
isError,
} = props;
// hooks
const { creteEstimatePoint } = useEstimate(estimateId);
Expand All @@ -40,6 +44,13 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
const [loader, setLoader] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);

useEffect(() => {
if (isError && error === undefined && estimateInputValue.length > 0) {
setError("Confirm this value first or discard it.");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isError]);

const handleSuccess = (value: string) => {
handleEstimatePointValue && handleEstimatePointValue(value);
setEstimateInputValue("");
Expand All @@ -51,7 +62,12 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
closeCallBack();
};

const handleCreate = async (event: MouseEvent<HTMLButtonElement> | FocusEvent<HTMLInputElement, Element>) => {
const handleEstimateInputValue = (value: string) => {
setError(undefined);
setEstimateInputValue(value);
};

const handleCreate = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();

if (!workspaceSlug || !projectId) return;
Expand Down Expand Up @@ -110,6 +126,9 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
}
} else {
handleSuccess(estimateInputValue);
if (handleCreateCallback) {
handleCreateCallback();
}
}
} else {
setLoader(false);
Expand All @@ -124,7 +143,7 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
};

return (
<form className="relative flex items-center gap-2 text-base">
<form onSubmit={handleCreate} className="relative flex items-center gap-2 text-base">
<div
className={cn(
"relative w-full border rounded flex items-center my-1",
Expand All @@ -134,42 +153,37 @@ export const EstimatePointCreate: FC<TEstimatePointCreate> = observer((props) =>
<input
type="text"
value={estimateInputValue}
onChange={(e) => setEstimateInputValue(e.target.value)}
onChange={(e) => handleEstimateInputValue(e.target.value)}
className="border-none focus:ring-0 focus:border-0 focus:outline-none p-2.5 w-full bg-transparent"
placeholder="Enter estimate point"
autoFocus
onBlur={(e) => !estimateId && handleCreate(e)}
/>
{error && (
<>
<Tooltip tooltipContent={error} position="bottom">
<div className="flex-shrink-0 w-3.5 h-3.5 overflow-hidden mr-3 relative flex justify-center items-center text-red-500">
<Info size={14} />
</div>
</Tooltip>
</>
<Tooltip tooltipContent={error} position="bottom">
<div className="flex-shrink-0 w-3.5 h-3.5 overflow-hidden mr-3 relative flex justify-center items-center text-red-500">
<Info size={14} />
</div>
</Tooltip>
)}
</div>

{estimateId && (
<>
<button
type="submit"
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer text-green-500"
disabled={loader}
onClick={handleCreate}
>
{loader ? <Spinner className="w-4 h-4" /> : <Check size={14} />}
</button>
<button
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
onClick={handleClose}
disabled={loader}
>
<X size={14} className="text-custom-text-200" />
</button>
</>
{estimateInputValue && estimateInputValue.length > 0 && (
<button
type="submit"
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer text-green-500"
disabled={loader}
>
{loader ? <Spinner className="w-4 h-4" /> : <Check size={14} />}
</button>
)}
<button
type="button"
className="rounded-sm w-6 h-6 flex-shrink-0 relative flex justify-center items-center hover:bg-custom-background-80 transition-colors cursor-pointer"
onClick={handleClose}
disabled={loader}
>
<X size={14} className="text-custom-text-200" />
</button>
</form>
);
});
Loading
Loading