From dfa3a7b78dcccb91ce60554b2577baccc57547e1 Mon Sep 17 00:00:00 2001 From: Kunal Vishwakarma <116634168+kunalv17@users.noreply.github.com> Date: Tue, 11 Apr 2023 17:54:01 +0530 Subject: [PATCH] feat: estimates (#783) * chore: use estimate points hook created * chore: user auth layer * fix: build error * chore: estimate crud and validation * fix: build errors --------- Co-authored-by: Aaryan Khandelwal --- .../auth-screens/workspace/not-a-member.tsx | 4 +- .../components/core/issues-view-filter.tsx | 35 +++-- .../core/list-view/single-issue.tsx | 9 ++ .../create-update-estimate-modal.tsx | 12 +- .../estimates/estimate-points-modal.tsx | 148 +++++------------- .../components/estimates/single-estimate.tsx | 42 ++--- apps/app/components/issues/form.tsx | 2 +- .../app/components/issues/select/estimate.tsx | 53 ++----- .../issues/sidebar-select/estimate.tsx | 45 ++++-- .../issues/view-select/estimate.tsx | 23 ++- .../issues/view-select/priority.tsx | 2 +- apps/app/hooks/use-estimate-option.tsx | 6 +- .../project-authorization-wrapper.tsx | 17 ++ .../[projectId]/settings/estimates.tsx | 51 ++++-- apps/app/pages/onboarding.tsx | 2 +- apps/app/services/estimates.service.ts | 26 +-- apps/app/services/track-event.service.ts | 1 + apps/app/types/projects.d.ts | 2 +- 18 files changed, 225 insertions(+), 255 deletions(-) diff --git a/apps/app/components/auth-screens/workspace/not-a-member.tsx b/apps/app/components/auth-screens/workspace/not-a-member.tsx index f630b484c..542727593 100644 --- a/apps/app/components/auth-screens/workspace/not-a-member.tsx +++ b/apps/app/components/auth-screens/workspace/not-a-member.tsx @@ -28,9 +28,7 @@ export const NotAWorkspaceMember = () => {
- router.back()}> - Check pending invites - + Check pending invites diff --git a/apps/app/components/core/issues-view-filter.tsx b/apps/app/components/core/issues-view-filter.tsx index bf35baa2a..4b4d15e09 100644 --- a/apps/app/components/core/issues-view-filter.tsx +++ b/apps/app/components/core/issues-view-filter.tsx @@ -20,6 +20,7 @@ import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; import { Properties } from "types"; // constants import { GROUP_BY_OPTIONS, ORDER_BY_OPTIONS, FILTER_ISSUE_OPTIONS } from "constants/issue"; +import useEstimateOption from "hooks/use-estimate-option"; export const IssuesFilterView: React.FC = () => { const router = useRouter(); @@ -45,6 +46,8 @@ export const IssuesFilterView: React.FC = () => { projectId as string ); + const { isEstimateActive } = useEstimateOption(); + return (
@@ -233,20 +236,24 @@ export const IssuesFilterView: React.FC = () => {

Display Properties

- {Object.keys(properties).map((key) => ( - - ))} + {Object.keys(properties).map((key) => { + if (key === "estimate" && !isEstimateActive) return null; + + return ( + + ); + })}
diff --git a/apps/app/components/core/list-view/single-issue.tsx b/apps/app/components/core/list-view/single-issue.tsx index f5d12c386..2d1a7932a 100644 --- a/apps/app/components/core/list-view/single-issue.tsx +++ b/apps/app/components/core/list-view/single-issue.tsx @@ -13,6 +13,7 @@ import useToast from "hooks/use-toast"; import { ViewAssigneeSelect, ViewDueDateSelect, + ViewEstimateSelect, ViewPrioritySelect, ViewStateSelect, } from "components/issues/view-select"; @@ -273,6 +274,14 @@ export const SingleListIssue: React.FC = ({ isNotAllowed={isNotAllowed} /> )} + {properties.estimate && ( + + )} {type && !isNotAllowed && ( diff --git a/apps/app/components/estimates/create-update-estimate-modal.tsx b/apps/app/components/estimates/create-update-estimate-modal.tsx index da127a455..2f2ac2c71 100644 --- a/apps/app/components/estimates/create-update-estimate-modal.tsx +++ b/apps/app/components/estimates/create-update-estimate-modal.tsx @@ -21,10 +21,9 @@ import { IEstimate } from "types"; import { ESTIMATES_LIST } from "constants/fetch-keys"; type Props = { + isOpen: boolean; handleClose: () => void; data?: IEstimate; - isOpen: boolean; - isCreate: boolean; }; const defaultValues: Partial = { @@ -32,7 +31,7 @@ const defaultValues: Partial = { description: "", }; -export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, isOpen, isCreate }) => { +export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, isOpen }) => { const { register, formState: { errors, isSubmitting }, @@ -109,12 +108,11 @@ export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, }; useEffect(() => { - if (!data && isCreate) return; reset({ ...defaultValues, ...data, }); - }, [data, reset, isCreate]); + }, [data, reset]); return ( <> @@ -148,7 +146,9 @@ export const CreateUpdateEstimateModal: React.FC = ({ handleClose, data, onSubmit={data ? handleSubmit(updateEstimate) : handleSubmit(createEstimate)} >
-
Create Estimate
+
+ {data ? "Update" : "Create"} Estimate +
= ({ isOpen, data, estimate, onClose }) => { @@ -56,7 +53,7 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o const { register, - formState: { errors, isSubmitting }, + formState: { isSubmitting }, handleSubmit, reset, } = useForm({ defaultValues }); @@ -95,14 +92,6 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o key: 5, value: formData.value6, }, - { - key: 6, - value: formData.value7, - }, - { - key: 7, - value: formData.value8, - }, ], }; @@ -126,49 +115,20 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o }; const updateEstimatePoints = async (formData: FormValues) => { - if (!workspaceSlug || !projectId) return; + if (!workspaceSlug || !projectId || !data || data.length === 0) return; + const payload = { - estimate_points: [ - { - key: 0, - value: formData.value1, - }, - { - key: 1, - value: formData.value2, - }, - { - key: 2, - value: formData.value3, - }, - { - key: 3, - value: formData.value4, - }, - { - key: 4, - value: formData.value5, - }, - { - key: 5, - value: formData.value6, - }, - { - key: 6, - value: formData.value7, - }, - { - key: 7, - value: formData.value8, - }, - ], + estimate_points: data.map((d, index) => ({ + id: d.id, + value: (formData as any)[`value${index + 1}`], + })), }; + await estimatesService - .updateEstimatesPoints( + .patchEstimatePoints( workspaceSlug as string, projectId as string, estimate?.id as string, - data?.[0]?.id as string, payload ) .then(() => { @@ -183,15 +143,27 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o }); }; + const onSubmit = async (formData: FormValues) => { + if (data && data.length !== 0) await updateEstimatePoints(formData); + else await createEstimatePoints(formData); + + if (estimate) mutate(ESTIMATE_POINTS_LIST(estimate.id)); + }; + useEffect(() => { - if(!data) return + if (!data || data.length < 6) return; + reset({ ...defaultValues, - ...data, + value1: data[0].value, + value2: data[1].value, + value3: data[2].value, + value4: data[3].value, + value5: data[4].value, + value6: data[5].value, }); }, [data, reset]); - return ( handleClose()}> @@ -219,18 +191,16 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > -
+
-

Create Estimate Points

-
+

+ {data ? "Update" : "Create"} Estimate Points +

+
- V0 + 1 = ({ isOpen, data, estimate, o required: "value is required", maxLength: { value: 10, - message: "value should be less than 10 characters", + message: "Name should be less than 10 characters", }, }} /> @@ -252,7 +222,7 @@ export const EstimatePointsModal: React.FC = ({ isOpen, data, estimate, o
- V1 + 2 = ({ isOpen, data, estimate, o
- V2 + 3 = ({ isOpen, data, estimate, o
- V3 + 4 = ({ isOpen, data, estimate, o
- V4 + 5 = ({ isOpen, data, estimate, o
- V5 + 6 = ({ isOpen, data, estimate, o
-
- - V6 - - - - -
-
- - V7 - - - - -
diff --git a/apps/app/components/estimates/single-estimate.tsx b/apps/app/components/estimates/single-estimate.tsx index 4741dc3f0..f65002937 100644 --- a/apps/app/components/estimates/single-estimate.tsx +++ b/apps/app/components/estimates/single-estimate.tsx @@ -25,6 +25,7 @@ import { import { IEstimate, IProject } from "types"; // fetch-keys import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys"; +import { orderArrayBy } from "helpers/array.helper"; type Props = { estimate: IEstimate; @@ -88,6 +89,7 @@ export const SingleEstimate: React.FC = ({ isOpen={isEstimatePointsModalOpen} estimate={estimate} onClose={() => setIsEstimatePointsModalOpen(false)} + data={estimatePoints ? orderArrayBy(estimatePoints, "key") : undefined} />
@@ -105,7 +107,7 @@ export const SingleEstimate: React.FC = ({

- {projectDetails?.estimate && projectDetails?.estimate !== estimate.id && ( + {projectDetails?.estimate !== estimate.id && (
@@ -116,7 +118,9 @@ export const SingleEstimate: React.FC = ({ setIsEstimatePointsModalOpen(true)}>
- {estimatePoints?.length === 8 ? "Update points" : "Create points"} + + {estimatePoints && estimatePoints?.length > 0 ? "Edit points" : "Create points"} +
= ({ Edit estimate
- { - handleEstimateDelete(estimate.id); - }} - > -
- - Delete estimate -
-
+ {projectDetails?.estimate !== estimate.id && ( + { + handleEstimateDelete(estimate.id); + }} + > +
+ + Delete estimate +
+
+ )}
{estimatePoints && estimatePoints.length > 0 ? ( -
- {estimatePoints.length > 0 && "Estimate points ("} - {estimatePoints.map((point, i) => ( +
+ Estimate points( + {estimatePoints.map((point, index) => (
{point.value} - {i !== estimatePoints.length - 1 && ","}{" "} + {index !== estimatePoints.length - 1 && ","}{" "}
))} - {estimatePoints.length > 0 && ")"} + )
) : (
-

No estimate points

+

No estimate points

)}
diff --git a/apps/app/components/issues/form.tsx b/apps/app/components/issues/form.tsx index 8e42754d4..9051ed2a5 100644 --- a/apps/app/components/issues/form.tsx +++ b/apps/app/components/issues/form.tsx @@ -405,7 +405,7 @@ export const IssueForm: FC = ({ control={control} name="estimate_point" render={({ field: { value, onChange } }) => ( - + )} />
diff --git a/apps/app/components/issues/select/estimate.tsx b/apps/app/components/issues/select/estimate.tsx index 6f63b2330..f7b171dae 100644 --- a/apps/app/components/issues/select/estimate.tsx +++ b/apps/app/components/issues/select/estimate.tsx @@ -1,72 +1,37 @@ import React from "react"; -import { useRouter } from "next/router"; - -import useSWR from "swr"; - -// services -import projectService from "services/project.service"; -import estimatesService from "services/estimates.service"; // ui import { CustomSelect } from "components/ui"; // icons -import { PlayIcon, ChevronDownIcon } from "@heroicons/react/24/outline"; +import { PlayIcon } from "@heroicons/react/24/outline"; // fetch-keys -import { ESTIMATE_POINTS_LIST, PROJECT_DETAILS } from "constants/fetch-keys"; +import useEstimateOption from "hooks/use-estimate-option"; type Props = { value: number; onChange: (value: number) => void; - chevron: boolean; }; -export const IssueEstimateSelect: React.FC = ({ value, onChange, chevron }) => { - const router = useRouter(); - const { workspaceSlug, projectId } = router.query; +export const IssueEstimateSelect: React.FC = ({ value, onChange }) => { + const { isEstimateActive, estimatePoints } = useEstimateOption(); - const { data: projectDetails } = useSWR( - workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, - workspaceSlug && projectId - ? () => projectService.getProject(workspaceSlug as string, projectId as string) - : null - ); - - const { data: estimatePoints } = useSWR( - workspaceSlug && projectId && projectDetails && projectDetails.estimate - ? ESTIMATE_POINTS_LIST(projectDetails.estimate) - : null, - workspaceSlug && projectId && projectDetails && projectDetails.estimate - ? () => - estimatesService.getEstimatesPointsList( - workspaceSlug as string, - projectId as string, - projectDetails.estimate - ) - : null - ); + if (!isEstimateActive) return null; return ( - - - +
+ {estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate points"} - {chevron && ( - - - - )}
} onChange={onChange} position="right" - width="w-full min-w-[111px]" - noChevron={!chevron} + width="w-full min-w-[6rem]" + noChevron > {estimatePoints && estimatePoints.map((point) => ( diff --git a/apps/app/components/issues/sidebar-select/estimate.tsx b/apps/app/components/issues/sidebar-select/estimate.tsx index 9b24a529d..2491c0978 100644 --- a/apps/app/components/issues/sidebar-select/estimate.tsx +++ b/apps/app/components/issues/sidebar-select/estimate.tsx @@ -1,13 +1,12 @@ import React from "react"; // ui -import { IssueEstimateSelect } from "components/issues/select"; - +import { CustomSelect } from "components/ui"; // icons -import { BanknotesIcon } from "@heroicons/react/24/outline"; - +import { BanknotesIcon, PlayIcon } from "@heroicons/react/24/outline"; // types import { UserAuth } from "types"; +import useEstimateOption from "hooks/use-estimate-option"; // constants type Props = { @@ -16,20 +15,48 @@ type Props = { userAuth: UserAuth; }; - - export const SidebarEstimateSelect: React.FC = ({ value, onChange, userAuth }) => { const isNotAllowed = userAuth.isGuest || userAuth.isViewer; + const { isEstimateActive, estimatePoints } = useEstimateOption(); + + if (!isEstimateActive) return null; + return (
- +

Estimate

- + + + + {estimatePoints?.find((e) => e.key === value)?.value ?? "Estimate points"} + +
+ } + onChange={onChange} + position="right" + width="w-full" + disabled={isNotAllowed} + > + {estimatePoints && + estimatePoints.map((point) => ( + + <> + + + + {point.value} + + + ))} +
); -}; \ No newline at end of file +}; diff --git a/apps/app/components/issues/view-select/estimate.tsx b/apps/app/components/issues/view-select/estimate.tsx index bdc916cff..5062950f3 100644 --- a/apps/app/components/issues/view-select/estimate.tsx +++ b/apps/app/components/issues/view-select/estimate.tsx @@ -11,6 +11,7 @@ import { PRIORITIES } from "constants/project"; // services import trackEventServices from "services/track-event.service"; import useEstimateOption from "hooks/use-estimate-option"; +import { PlayIcon } from "@heroicons/react/24/outline"; type Props = { issue: IIssue; @@ -27,15 +28,17 @@ export const ViewEstimateSelect: React.FC = ({ selfPositioned = false, isNotAllowed, }) => { - const { isEstimateActive, estimatePoints, estimateValue } = useEstimateOption( - issue.estimate_point - ); + const { isEstimateActive, estimatePoints } = useEstimateOption(issue.estimate_point); + + const estimateValue = estimatePoints?.find((e) => e.key === issue.estimate_point)?.value; + + if (!isEstimateActive) return null; return ( { - partialUpdateIssue({ priority: data, state: issue.state, target_date: issue.target_date }); + value={issue.estimate_point} + onChange={(val: number) => { + partialUpdateIssue({ estimate_point: val }); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug: issue.workspace_detail.slug, @@ -45,12 +48,15 @@ export const ViewEstimateSelect: React.FC = ({ projectName: issue.project_detail.name, issueId: issue.id, }, - "ISSUE_PROPERTY_UPDATE_PRIORITY" + "ISSUE_PROPERTY_UPDATE_ESTIMATE" ); }} label={ - <>{estimateValue} +
+ + {estimateValue} +
} maxHeight="md" @@ -58,6 +64,7 @@ export const ViewEstimateSelect: React.FC = ({ disabled={isNotAllowed} position={position} selfPositioned={selfPositioned} + width="w-full min-w-[6rem]" > {estimatePoints?.map((estimate) => ( diff --git a/apps/app/components/issues/view-select/priority.tsx b/apps/app/components/issues/view-select/priority.tsx index 47847f658..105978a04 100644 --- a/apps/app/components/issues/view-select/priority.tsx +++ b/apps/app/components/issues/view-select/priority.tsx @@ -29,7 +29,7 @@ export const ViewPrioritySelect: React.FC = ({ { - partialUpdateIssue({ priority: data, state: issue.state, target_date: issue.target_date }); + partialUpdateIssue({ priority: data }); trackEventServices.trackIssuePartialPropertyUpdateEvent( { workspaceSlug: issue.workspace_detail.slug, diff --git a/apps/app/hooks/use-estimate-option.tsx b/apps/app/hooks/use-estimate-option.tsx index 609dd0dfc..664b1af87 100644 --- a/apps/app/hooks/use-estimate-option.tsx +++ b/apps/app/hooks/use-estimate-option.tsx @@ -8,6 +8,8 @@ import useSWR from "swr"; import estimatesService from "services/estimates.service"; // hooks import useProjectDetails from "hooks/use-project-details"; +// helpers +import { orderArrayBy } from "helpers/array.helper"; // fetch-keys import { ESTIMATE_POINTS_LIST } from "constants/fetch-keys"; @@ -26,7 +28,7 @@ const useEstimateOption = (estimateKey?: number) => { estimatesService.getEstimatesPointsList( workspaceSlug as string, projectId as string, - projectDetails.estimate + projectDetails.estimate as string ) : null ); @@ -41,7 +43,7 @@ const useEstimateOption = (estimateKey?: number) => { return { isEstimateActive: projectDetails?.estimate ? true : false, - estimatePoints, + estimatePoints: orderArrayBy(estimatePoints ?? [], "key"), estimateValue, }; }; diff --git a/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx b/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx index b873e07fc..2b12133c7 100644 --- a/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx +++ b/apps/app/layouts/auth-layout/project-authorization-wrapper.tsx @@ -10,6 +10,7 @@ import Container from "layouts/container"; import AppHeader from "layouts/app-layout/app-header"; import AppSidebar from "layouts/app-layout/app-sidebar"; import SettingsNavbar from "layouts/settings-navbar"; +import { WorkspaceAuthorizationLayout } from "./workspace-authorization-wrapper"; // components import { NotAuthorizedView, JoinProject } from "components/auth-screens"; import { CommandPalette } from "components/command-palette"; @@ -89,6 +90,22 @@ const ProjectAuthorizationWrapped: React.FC = ({
+ ) : error?.status === 401 || error?.status === 403 ? ( + + ) : error?.status === 404 ? ( +
+
+

No such project exist. Create one?

+ { + const e = new KeyboardEvent("keydown", { key: "p" }); + document.dispatchEvent(e); + }} + > + Create project + +
+
) : settingsLayout && (memberType?.isGuest || memberType?.isViewer) ? ( { const [estimateFormOpen, setEstimateFormOpen] = useState(false); - const [isUpdating, setIsUpdating] = useState(false); const [estimateToUpdate, setEstimateToUpdate] = useState(); const router = useRouter(); @@ -38,8 +38,6 @@ const EstimatesSettings: NextPage = () => { const { projectDetails } = useProjectDetails(); - const scrollToRef = useRef(null); - const { data: estimatesList } = useSWR( workspaceSlug && projectId ? ESTIMATES_LIST(projectId as string) : null, workspaceSlug && projectId @@ -48,7 +46,6 @@ const EstimatesSettings: NextPage = () => { ); const editEstimate = (estimate: IEstimate) => { - setIsUpdating(true); setEstimateToUpdate(estimate); setEstimateFormOpen(true); }; @@ -73,6 +70,30 @@ const EstimatesSettings: NextPage = () => { }); }; + const disableEstimates = () => { + if (!workspaceSlug || !projectId) return; + + mutate( + PROJECT_DETAILS(projectId as string), + (prevData) => { + if (!prevData) return prevData; + + return { ...prevData, estimate: null }; + }, + false + ); + + projectService + .updateProject(workspaceSlug as string, projectId as string, { estimate: null }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Estimate could not be disabled. Please try again", + }) + ); + }; + return ( <> { } > { @@ -95,14 +115,12 @@ const EstimatesSettings: NextPage = () => { setEstimateToUpdate(undefined); }} /> -
-
-

Estimates

-
+
+

Estimates

-
+
{ setEstimateToUpdate(undefined); setEstimateFormOpen(true); @@ -111,6 +129,9 @@ const EstimatesSettings: NextPage = () => { Create New Estimate + {projectDetails?.estimate && ( + Disable Estimates + )}
diff --git a/apps/app/pages/onboarding.tsx b/apps/app/pages/onboarding.tsx index 86f6846cc..55c6541ee 100644 --- a/apps/app/pages/onboarding.tsx +++ b/apps/app/pages/onboarding.tsx @@ -22,9 +22,9 @@ import { ONBOARDING_CARDS } from "constants/workspace"; import Logo from "public/onboarding/logo.svg"; // types import type { NextPage } from "next"; +import { ICurrentUserResponse } from "types"; // fetch-keys import { CURRENT_USER } from "constants/fetch-keys"; -import { ICurrentUserResponse } from "types"; const Onboarding: NextPage = () => { const [step, setStep] = useState(1); diff --git a/apps/app/services/estimates.service.ts b/apps/app/services/estimates.service.ts index 0b5d4dd6b..d03a7f1f2 100644 --- a/apps/app/services/estimates.service.ts +++ b/apps/app/services/estimates.service.ts @@ -1,6 +1,6 @@ // services import APIService from "services/api.service"; - +// types import type { IEstimate, IEstimatePoint } from "types"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; @@ -78,7 +78,7 @@ class ProjectEstimateServices extends APIService { } ): Promise { return this.post( - `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/bulk-create-estimate-points/`, + `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/bulk-estimate-points/`, data ) .then((response) => response?.data) @@ -87,7 +87,7 @@ class ProjectEstimateServices extends APIService { }); } - async getEstimatesPoints( + async getEstimatesPointDetails( workspaceSlug: string, projectId: string, estimateId: string, @@ -116,15 +116,14 @@ class ProjectEstimateServices extends APIService { }); } - async updateEstimatesPoints( + async patchEstimatePoints( workspaceSlug: string, projectId: string, estimateId: string, - estimatePointId: string, data: any ): Promise { return this.patch( - `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimate/${estimateId}/estimate-points/${estimatePointId}`, + `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimates/${estimateId}/bulk-estimate-points/`, data ) .then((response) => response?.data) @@ -132,21 +131,6 @@ class ProjectEstimateServices extends APIService { throw error?.response?.data; }); } - - async deleteEstimatesPoints( - workspaceSlug: string, - projectId: string, - estimateId: string, - estimatePointId: string - ): Promise { - return this.delete( - `/api/workspaces/${workspaceSlug}/projects/${projectId}/estimate/${estimateId}/estimate-points/${estimatePointId}` - ) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - } } export default new ProjectEstimateServices(); diff --git a/apps/app/services/track-event.service.ts b/apps/app/services/track-event.service.ts index eddb6e8af..a8b77150c 100644 --- a/apps/app/services/track-event.service.ts +++ b/apps/app/services/track-event.service.ts @@ -192,6 +192,7 @@ class TrackEventServices extends APIService { | "ISSUE_PROPERTY_UPDATE_STATE" | "ISSUE_PROPERTY_UPDATE_ASSIGNEE" | "ISSUE_PROPERTY_UPDATE_DUE_DATE" + | "ISSUE_PROPERTY_UPDATE_ESTIMATE" ): Promise { if (!trackEvent) return; return this.request({ diff --git a/apps/app/types/projects.d.ts b/apps/app/types/projects.d.ts index daf3a24c2..792403a6f 100644 --- a/apps/app/types/projects.d.ts +++ b/apps/app/types/projects.d.ts @@ -18,7 +18,7 @@ export interface IProject { page_view: boolean; default_assignee: IUser | string | null; description: string; - estimate: string; + estimate: string | null; icon: string; id: string; identifier: string;