plane/web/store/estimates/project-estimate.store.ts
sriram veeraghanta 59fdd611e4
feat: estimates revamp and space app refactor (#4742)
* Move code from EE to CE repo

* chore: folder structure updates

* Move sortabla and radio input to packages/ui

* chore: updated empty and loading screens

* chore: delete an estimate point

* chore: estimate point response change

* chore: updated create estimate and handled the build error

* chore: migration fixes

* chore: updated create estimate

* chore: create estimate workflow update

* chore: editing and deleting the existing estimate updates

* chore: updating the new estinates in update modal

* chore: ui changed

* chore: response changes of get and post

* chore: new field added in estimates

* chore: individual endpoint for estimate points

* chore: typo changes

* chore: create estimate point

* chore: integrated new endpoints

* chore: update key value pair

* chore: update sorting in the estimates

* Add custom option in the estimate templates

* chore: handled current project active estimate

* chore: handle estimate update worklfow

* chore: handled estimates switch

* chore: handled estimate edit

* chore: handled close button in estimate edit

* chore: updated ceate estimare workflow

* chore: updated switch estimate

* chore: UI and typos

* chore: resolved build error

* chore: updated delete dropdown and handled the repeated values while creating and updating the estimate point

* chore: handled inline errors in the estimate switch

* chore: handled active and availability vadilation

* chore: handled create and update components in projecr estimates

* chore: added migration

* Add category specific values for custom template

* chore: estimate dropdown handled in issues

* chore: estimate alerts

* chore: updated alerts

* Extract the list row actions

* fix: updated and handled the estimate points

* fix: upgrader ee banner

* Fix issues with sortable

* Fix sortable spacing issue in create estimate modal

* fix: updated the issue create sorting

* chore: removed radio button from ui and updated in the estimates

* chore: resolved import error in packaged ui

* chore: handled props in create modal

* chore: removed ee files

* chore: changed default analytics

* chore: removed the migration file

* chore: estimate point value in graph

* chore: estimate point key change

* chore: squashed migration (#4634)

* chore: squashed migration

* chore: removed instance migraion

* chore: key changes

* chore: issue activity back migration

* dev: replaced estimate key with estimate id and replaced estimate type from number to string in issue

* chore: estimate point value field

* chore: estimate point activity

* chore: removed the unused function

* chore: resolved merge conflicts

* chore: deploy board keys changed

* chore: yarn lock file change

* chore: resolved frontend build

---------

Co-authored-by: guru_sainath <gurusainath007@gmail.com>

* [WEB-1516] refactor: space app routing and layouts (#4705)

* dev: change layout

* chore: replace workspace slug and project id with anchor

* chore: migration fixes

* chore: update filtering logic

* chore: endpoint changes

* chore: update endpoint

* chore: changed url pratterns

* chore: use client side for layout and page

* chore: issue vote changes

* chore: project deploy board response change

* refactor: publish project store and components

* fix: update layout options after fetching settings

* chore: remove unnecessary types

* style: peek overview

* refactor: components folder structure

* fix: redirect from old path

* chore: make the whole issue block clickable

* chore: removed the migration file

* chore: add server side redirection for old routes

* chore: is enabled key change

* chore: update types

* chore: removed the migration file

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>

* Merge develop into revamp-estimates-ce

* chore: removed migration file and updated the estimate system order and removed ee banner

* chore: initial radio select in create estimate

* chore: space key changes

* Fix sortable component as the sort order was broken.

* [WEB-1516] refactor: publish project modal and types (#4716)

* refacotr: project publish

* chore: rename service names

* chore: is_deployed changed to anchor

* chore: update is_deployed key

---------

Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>

* [WEB-412] chore: estimates analytics  (#4730)

* chore: estimate points in modules and cycle

* chore: burn down chart analytics

* chore: module serializer change

* dev: handled y-axis estimates in analytics, implemented estimate points on modules

* chore: burn down analytics

* chore: state estimate point analytics

* chore: updated the burn down values

* Remove check mark from estimate point edit field in
create estimate flow

---------

Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: Satish Gandham <satish.iitg@gmail.com>

---------

Co-authored-by: Satish Gandham <satish.iitg@gmail.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Co-authored-by: pushya22 <130810100+pushya22@users.noreply.github.com>
2024-06-10 12:16:23 +05:30

293 lines
9.0 KiB
TypeScript

import orderBy from "lodash/orderBy";
import set from "lodash/set";
import unset from "lodash/unset";
import update from "lodash/update";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
import { IEstimate as IEstimateType, IEstimateFormData } from "@plane/types";
// services
import estimateService from "@/services/project/estimate.service";
// store
import { IEstimate, Estimate } from "@/store/estimates/estimate";
import { RootStore } from "@/store/root.store";
type TEstimateLoader = "init-loader" | "mutation-loader" | undefined;
type TErrorCodes = {
status: string;
message?: string;
};
export interface IProjectEstimateStore {
// observables
loader: TEstimateLoader;
estimates: Record<string, IEstimate>;
error: TErrorCodes | undefined;
// computed
currentActiveEstimateId: string | undefined;
archivedEstimateIds: string[] | undefined;
areEstimateEnabledByProjectId: (projectId: string) => boolean;
estimateIdsByProjectId: (projectId: string) => string[] | undefined;
estimateById: (estimateId: string) => IEstimate | undefined;
// actions
getWorkspaceEstimates: (workspaceSlug: string, loader?: TEstimateLoader) => Promise<IEstimateType[] | undefined>;
getProjectEstimates: (
workspaceSlug: string,
projectId: string,
loader?: TEstimateLoader
) => Promise<IEstimateType[] | undefined>;
getEstimateById: (workspaceSlug: string, projectId: string, estimateId: string) => Promise<IEstimateType | undefined>;
createEstimate: (
workspaceSlug: string,
projectId: string,
data: IEstimateFormData
) => Promise<IEstimateType | undefined>;
deleteEstimate: (workspaceSlug: string, projectId: string, estimateId: string) => Promise<void>;
}
export class ProjectEstimateStore implements IProjectEstimateStore {
// observables
loader: TEstimateLoader = undefined;
estimates: Record<string, IEstimate> = {}; // estimate_id -> estimate
error: TErrorCodes | undefined = undefined;
constructor(private store: RootStore) {
makeObservable(this, {
// observables
loader: observable.ref,
estimates: observable,
error: observable,
// computed
currentActiveEstimateId: computed,
archivedEstimateIds: computed,
// actions
getWorkspaceEstimates: action,
getProjectEstimates: action,
getEstimateById: action,
createEstimate: action,
deleteEstimate: action,
});
}
// computed
/**
* @description get current active estimate id for a project
* @returns { string | undefined }
*/
get currentActiveEstimateId(): string | undefined {
const { projectId } = this.store.router;
if (!projectId) return undefined;
const currentActiveEstimateId = Object.values(this.estimates || {}).find(
(p) => p.project === projectId && p.last_used
);
return currentActiveEstimateId?.id ?? undefined;
}
/**
* @description get all archived estimate ids for a project
* @returns { string[] | undefined }
*/
get archivedEstimateIds(): string[] | undefined {
const { projectId } = this.store.router;
if (!projectId) return undefined;
const archivedEstimates = orderBy(
Object.values(this.estimates || {}).filter((p) => p.project === projectId && !p.last_used),
["created_at"],
"desc"
);
const archivedEstimateIds = archivedEstimates.map((p) => p.id) as string[];
return archivedEstimateIds ?? undefined;
}
/**
* @description get estimates are enabled in the project or not
* @returns { boolean }
*/
areEstimateEnabledByProjectId = computedFn((projectId: string) => {
if (!projectId) return false;
const projectDetails = this.store.projectRoot.project.getProjectById(projectId);
if (!projectDetails) return false;
return Boolean(projectDetails.estimate) || false;
});
/**
* @description get all estimate ids for a project
* @returns { string[] | undefined }
*/
estimateIdsByProjectId = computedFn((projectId: string) => {
if (!projectId) return undefined;
const projectEstimatesIds = Object.values(this.estimates || {})
.filter((p) => p.project === projectId)
.map((p) => p.id) as string[];
return projectEstimatesIds ?? undefined;
});
/**
* @description get estimate by id
* @returns { IEstimate | undefined }
*/
estimateById = computedFn((estimateId: string) => {
if (!estimateId) return undefined;
return this.estimates[estimateId] ?? undefined;
});
// actions
/**
* @description fetch all estimates for a workspace
* @param { string } workspaceSlug
* @returns { IEstimateType[] | undefined }
*/
getWorkspaceEstimates = async (
workspaceSlug: string,
loader: TEstimateLoader = "mutation-loader"
): Promise<IEstimateType[] | undefined> => {
try {
this.error = undefined;
if (Object.keys(this.estimates || {}).length <= 0) this.loader = loader ? loader : "init-loader";
const estimates = await estimateService.fetchWorkspaceEstimates(workspaceSlug);
if (estimates && estimates.length > 0) {
runInAction(() => {
estimates.forEach((estimate) => {
if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
});
});
}
return estimates;
} catch (error) {
this.loader = undefined;
this.error = {
status: "error",
message: "Error fetching estimates",
};
throw error;
}
};
/**
* @description fetch all estimates for a project
* @param { string } workspaceSlug
* @param { string } projectId
* @returns { IEstimateType[] | undefined }
*/
getProjectEstimates = async (
workspaceSlug: string,
projectId: string,
loader: TEstimateLoader = "mutation-loader"
): Promise<IEstimateType[] | undefined> => {
try {
this.error = undefined;
if (!this.estimateIdsByProjectId(projectId)) this.loader = loader ? loader : "init-loader";
const estimates = await estimateService.fetchProjectEstimates(workspaceSlug, projectId);
if (estimates && estimates.length > 0) {
runInAction(() => {
estimates.forEach((estimate) => {
if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
});
});
}
return estimates;
} catch (error) {
this.loader = undefined;
this.error = {
status: "error",
message: "Error fetching estimates",
};
throw error;
}
};
/**
* @description update an estimate for a project
* @param { string } workspaceSlug
* @param { string } projectId
* @param { string } estimateId
* @returns IEstimateType | undefined
*/
getEstimateById = async (
workspaceSlug: string,
projectId: string,
estimateId: string
): Promise<IEstimateType | undefined> => {
try {
this.error = undefined;
const estimate = await estimateService.fetchEstimateById(workspaceSlug, projectId, estimateId);
if (estimate) {
runInAction(() => {
if (estimate.id)
update(this.estimates, [estimate.id], (estimateStore) => {
if (estimateStore) estimateStore.updateEstimate(estimate);
else return new Estimate(this.store, estimate);
});
});
}
return estimate;
} catch (error) {
this.error = {
status: "error",
message: "Error fetching estimate by id",
};
throw error;
}
};
/**
* @description create an estimate for a project
* @param { string } workspaceSlug
* @param { string } projectId
* @param { Partial<IEstimateFormData> } payload
* @returns
*/
createEstimate = async (
workspaceSlug: string,
projectId: string,
payload: IEstimateFormData
): Promise<IEstimateType | undefined> => {
try {
this.error = undefined;
const estimate = await estimateService.createEstimate(workspaceSlug, projectId, payload);
if (estimate) {
// update estimate_id in current project
// await this.store.projectRoot.project.updateProject(workspaceSlug, projectId, {
// estimate: estimate.id,
// });
runInAction(() => {
if (estimate.id) set(this.estimates, [estimate.id], new Estimate(this.store, estimate));
});
}
return estimate;
} catch (error) {
this.error = {
status: "error",
message: "Error creating estimate",
};
throw error;
}
};
/**
* @description delete the estimate for a project
* @param workspaceSlug
* @param projectId
* @param estimateId
*/
deleteEstimate = async (workspaceSlug: string, projectId: string, estimateId: string) => {
try {
await estimateService.deleteEstimate(workspaceSlug, projectId, estimateId);
runInAction(() => estimateId && unset(this.estimates, [estimateId]));
} catch (error) {
this.error = {
status: "error",
message: "Error deleting estimate",
};
throw error;
}
};
}