[WEB-4828] refactor: remove legacy project-level draft work items components (#7694)

This commit is contained in:
Prateek Shourya 2025-09-02 12:29:00 +05:30 committed by GitHub
parent fd5ba6c7b8
commit d960d7ce88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 16 additions and 1313 deletions

View file

@ -1,279 +0,0 @@
import isArray from "lodash/isArray";
import isEmpty from "lodash/isEmpty";
import pickBy from "lodash/pickBy";
import set from "lodash/set";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
// base class
import { computedFn } from "mobx-utils";
import { EIssueFilterType } from "@plane/constants";
import {
EIssuesStoreType,
IIssueFilterOptions,
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
TIssueKanbanFilters,
IIssueFilters,
TIssueParams,
IssuePaginationOptions,
} from "@plane/types";
import { handleIssueQueryParamsByLayout } from "@plane/utils";
import { IssueFiltersService } from "@/services/issue_filter.service";
import { IBaseIssueFilterStore, IssueFilterHelperStore } from "../helpers/issue-filter-helper.store";
// helpers
// types
import { IIssueRootStore } from "../root.store";
// constants
// services
export interface IDraftIssuesFilter extends IBaseIssueFilterStore {
//helper actions
getFilterParams: (
options: IssuePaginationOptions,
projectId: string,
cursor: string | undefined,
groupId: string | undefined,
subGroupId: string | undefined
) => Partial<Record<TIssueParams, string | boolean>>;
getIssueFilters(projectId: string): IIssueFilters | undefined;
// action
fetchFilters: (workspaceSlug: string, projectId: string) => Promise<void>;
updateFilters: (
workspaceSlug: string,
projectId: string,
filterType: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => Promise<void>;
}
export class DraftIssuesFilter extends IssueFilterHelperStore implements IDraftIssuesFilter {
// observables
filters: { [projectId: string]: IIssueFilters } = {};
// root store
rootIssueStore: IIssueRootStore;
// services
issueFilterService;
constructor(_rootStore: IIssueRootStore) {
super();
makeObservable(this, {
// observables
filters: observable,
// computed
issueFilters: computed,
appliedFilters: computed,
// actions
fetchFilters: action,
updateFilters: action,
});
// root store
this.rootIssueStore = _rootStore;
// services
this.issueFilterService = new IssueFiltersService();
}
get issueFilters() {
const projectId = this.rootIssueStore.projectId;
if (!projectId) return undefined;
return this.getIssueFilters(projectId);
}
get appliedFilters() {
const projectId = this.rootIssueStore.projectId;
if (!projectId) return undefined;
return this.getAppliedFilters(projectId);
}
getIssueFilters(projectId: string) {
const displayFilters = this.filters[projectId] || undefined;
if (!projectId || isEmpty(displayFilters)) return undefined;
const _filters: IIssueFilters = this.computedIssueFilters(displayFilters);
return _filters;
}
getAppliedFilters(projectId: string) {
const userFilters = this.getIssueFilters(projectId);
if (!userFilters) return undefined;
const filteredParams = handleIssueQueryParamsByLayout(userFilters?.displayFilters?.layout, "issues");
if (!filteredParams) return undefined;
const filteredRouteParams: Partial<Record<TIssueParams, string | boolean>> = this.computedFilteredParams(
userFilters?.filters as IIssueFilterOptions,
userFilters?.displayFilters as IIssueDisplayFilterOptions,
filteredParams
);
return filteredRouteParams;
}
getFilterParams = computedFn(
(
options: IssuePaginationOptions,
projectId: string,
cursor: string | undefined,
groupId: string | undefined,
subGroupId: string | undefined
) => {
const filterParams = this.getAppliedFilters(projectId);
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
return paginationParams;
}
);
fetchFilters = async (workspaceSlug: string, projectId: string) => {
const _filters = this.handleIssuesLocalFilters.get(EIssuesStoreType.DRAFT, workspaceSlug, projectId, undefined);
const filters: IIssueFilterOptions = this.computedFilters(_filters?.filters);
const displayFilters: IIssueDisplayFilterOptions = this.computedDisplayFilters(_filters?.display_filters);
const displayProperties: IIssueDisplayProperties = this.computedDisplayProperties(_filters?.display_properties);
const kanbanFilters = {
group_by: [],
sub_group_by: [],
};
kanbanFilters.group_by = _filters?.kanban_filters?.group_by || [];
kanbanFilters.sub_group_by = _filters?.kanban_filters?.sub_group_by || [];
runInAction(() => {
set(this.filters, [projectId, "filters"], filters);
set(this.filters, [projectId, "displayFilters"], displayFilters);
set(this.filters, [projectId, "displayProperties"], displayProperties);
set(this.filters, [projectId, "kanbanFilters"], kanbanFilters);
});
};
updateFilters = async (
workspaceSlug: string,
projectId: string,
type: EIssueFilterType,
filters: IIssueFilterOptions | IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters
) => {
try {
if (isEmpty(this.filters) || isEmpty(this.filters[projectId]) || isEmpty(filters)) return;
const _filters = {
filters: this.filters[projectId].filters as IIssueFilterOptions,
displayFilters: this.filters[projectId].displayFilters as IIssueDisplayFilterOptions,
displayProperties: this.filters[projectId].displayProperties as IIssueDisplayProperties,
kanbanFilters: this.filters[projectId].kanbanFilters as TIssueKanbanFilters,
};
switch (type) {
case EIssueFilterType.FILTERS: {
const updatedFilters = filters as IIssueFilterOptions;
_filters.filters = { ..._filters.filters, ...updatedFilters };
runInAction(() => {
Object.keys(updatedFilters).forEach((_key) => {
set(this.filters, [projectId, "filters", _key], updatedFilters[_key as keyof IIssueFilterOptions]);
});
});
const appliedFilters = _filters.filters || {};
const filteredFilters = pickBy(appliedFilters, (value) => value && isArray(value) && value.length > 0);
this.rootIssueStore.draftIssues.fetchIssuesWithExistingPagination(
workspaceSlug,
projectId,
isEmpty(filteredFilters) ? "init-loader" : "mutation"
);
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
filters: _filters.filters,
});
break;
}
case EIssueFilterType.DISPLAY_FILTERS: {
const updatedDisplayFilters = filters as IIssueDisplayFilterOptions;
_filters.displayFilters = { ..._filters.displayFilters, ...updatedDisplayFilters };
// set sub_group_by to null if group_by is set to null
if (_filters.displayFilters.group_by === null) {
_filters.displayFilters.sub_group_by = null;
updatedDisplayFilters.sub_group_by = null;
}
// set sub_group_by to null if layout is switched to kanban group_by and sub_group_by are same
if (
_filters.displayFilters.layout === "kanban" &&
_filters.displayFilters.group_by === _filters.displayFilters.sub_group_by
) {
_filters.displayFilters.sub_group_by = null;
updatedDisplayFilters.sub_group_by = null;
}
// set group_by to state if layout is switched to kanban and group_by is null
if (_filters.displayFilters.layout === "kanban" && _filters.displayFilters.group_by === null) {
_filters.displayFilters.group_by = "state";
updatedDisplayFilters.group_by = "state";
}
runInAction(() => {
Object.keys(updatedDisplayFilters).forEach((_key) => {
set(
this.filters,
[projectId, "displayFilters", _key],
updatedDisplayFilters[_key as keyof IIssueDisplayFilterOptions]
);
});
});
if (this.getShouldReFetchIssues(updatedDisplayFilters)) {
this.rootIssueStore.draftIssues.fetchIssuesWithExistingPagination(workspaceSlug, projectId, "mutation");
}
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
display_filters: _filters.displayFilters,
});
break;
}
case EIssueFilterType.DISPLAY_PROPERTIES: {
const updatedDisplayProperties = filters as IIssueDisplayProperties;
_filters.displayProperties = { ..._filters.displayProperties, ...updatedDisplayProperties };
runInAction(() => {
Object.keys(updatedDisplayProperties).forEach((_key) => {
set(
this.filters,
[projectId, "displayProperties", _key],
updatedDisplayProperties[_key as keyof IIssueDisplayProperties]
);
});
});
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
display_properties: _filters.displayProperties,
});
break;
}
case EIssueFilterType.KANBAN_FILTERS: {
const updatedKanbanFilters = filters as TIssueKanbanFilters;
_filters.kanbanFilters = { ..._filters.kanbanFilters, ...updatedKanbanFilters };
const currentUserId = this.rootIssueStore.currentUserId;
if (currentUserId)
this.handleIssuesLocalFilters.set(EIssuesStoreType.DRAFT, type, workspaceSlug, projectId, undefined, {
kanban_filters: _filters.kanbanFilters,
});
runInAction(() => {
Object.keys(updatedKanbanFilters).forEach((_key) => {
set(
this.filters,
[projectId, "kanbanFilters", _key],
updatedKanbanFilters[_key as keyof TIssueKanbanFilters]
);
});
});
break;
}
default:
break;
}
} catch (error) {
this.fetchFilters(workspaceSlug, projectId);
throw error;
}
};
}

View file

@ -1,2 +0,0 @@
export * from "./filter.store";
export * from "./issue.store";

View file

@ -1,186 +0,0 @@
import { action, makeObservable, runInAction } from "mobx";
// base class
// services
// types
import {
TIssue,
TLoader,
ViewFlags,
IssuePaginationOptions,
TIssuesResponse,
TBulkOperationsPayload,
} from "@plane/types";
import { BaseIssuesStore, IBaseIssuesStore } from "../helpers/base-issues.store";
import { IIssueRootStore } from "../root.store";
import { IDraftIssuesFilter } from "./filter.store";
export interface IDraftIssues extends IBaseIssuesStore {
// observable
viewFlags: ViewFlags;
// actions
fetchIssues: (
workspaceSlug: string,
projectId: string,
loadType: TLoader,
option: IssuePaginationOptions
) => Promise<TIssuesResponse | undefined>;
fetchIssuesWithExistingPagination: (
workspaceSlug: string,
projectId: string,
loadType: TLoader
) => Promise<TIssuesResponse | undefined>;
fetchNextIssues: (
workspaceSlug: string,
projectId: string,
groupId?: string,
subGroupId?: string
) => Promise<TIssuesResponse | undefined>;
createIssue: (workspaceSlug: string, projectId: string, data: Partial<TIssue>) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
removeBulkIssues: (workspaceSlug: string, projectId: string, issueIds: string[]) => Promise<void>;
bulkUpdateProperties: (workspaceSlug: string, projectId: string, data: TBulkOperationsPayload) => Promise<void>;
archiveBulkIssues: undefined;
quickAddIssue: undefined;
archiveIssue: undefined;
}
export class DraftIssues extends BaseIssuesStore implements IDraftIssues {
viewFlags = {
enableQuickAdd: false,
enableIssueCreation: true,
enableInlineEditing: true,
};
// filter store
issueFilterStore: IDraftIssuesFilter;
constructor(_rootStore: IIssueRootStore, issueFilterStore: IDraftIssuesFilter) {
super(_rootStore, issueFilterStore);
makeObservable(this, {
// action
fetchIssues: action,
fetchNextIssues: action,
fetchIssuesWithExistingPagination: action,
});
// filter store
this.issueFilterStore = issueFilterStore;
}
/**
* Fetches the project details
* @param workspaceSlug
* @param projectId
*/
fetchParentStats = async (workspaceSlug: string, projectId?: string) => {
projectId && this.rootIssueStore.rootStore.projectRoot.project.fetchProjectDetails(workspaceSlug, projectId);
};
/** */
updateParentStats = () => {};
/**
* This method is called to fetch the first issues of pagination
* @param workspaceSlug
* @param projectId
* @param loadType
* @param options
* @returns
*/
fetchIssues = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader = "init-loader",
options: IssuePaginationOptions,
isExistingPaginationOptions: boolean = false
) => {
try {
// set loader and clear store
runInAction(() => {
this.setLoader(loadType);
});
this.clear(!isExistingPaginationOptions);
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, projectId, undefined, undefined, undefined);
// call the fetch issues API with the params
const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params, {
signal: this.controller.signal,
});
// after fetching issues, call the base method to process the response further
this.onfetchIssues(response, options, workspaceSlug, projectId, undefined, !isExistingPaginationOptions);
return response;
} catch (error) {
// set loader to undefined if errored out
this.setLoader(undefined);
throw error;
}
};
/**
* This method is called subsequent pages of pagination
* if groupId/subgroupId is provided, only that specific group's next page is fetched
* else all the groups' next page is fetched
* @param workspaceSlug
* @param projectId
* @param groupId
* @param subGroupId
* @returns
*/
fetchNextIssues = async (workspaceSlug: string, projectId: string, groupId?: string, subGroupId?: string) => {
const cursorObject = this.getPaginationData(groupId, subGroupId);
// if there are no pagination options and the next page results do not exist the return
if (!this.paginationOptions || (cursorObject && !cursorObject?.nextPageResults)) return;
try {
// set Loader
this.setLoader("pagination", groupId, subGroupId);
// get params from stored pagination options
const params = this.issueFilterStore?.getFilterParams(
this.paginationOptions,
projectId,
this.getNextCursor(groupId, subGroupId),
groupId,
subGroupId
);
// call the fetch issues API with the params for next page in issues
const response = await this.issueDraftService.getDraftIssues(workspaceSlug, projectId, params);
// after the next page of issues are fetched, call the base method to process the response
this.onfetchNexIssues(response, groupId, subGroupId);
return response;
} catch (error) {
// set Loader as undefined if errored out
this.setLoader(undefined, groupId, subGroupId);
throw error;
}
};
/**
* This Method exists to fetch the first page of the issues with the existing stored pagination
* This is useful for refetching when filters, groupBy, orderBy etc changes
* @param workspaceSlug
* @param projectId
* @param loadType
* @returns
*/
fetchIssuesWithExistingPagination = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader = "mutation"
) => {
if (!this.paginationOptions) return;
return await this.fetchIssues(workspaceSlug, projectId, loadType, this.paginationOptions, true);
};
// Using aliased names as they cannot be overridden in other stores
createIssue = this.createDraftIssue;
updateIssue = this.updateDraftIssue;
// Setting them as undefined as they can not performed on draft issues
archiveBulkIssues = undefined;
quickAddIssue = undefined;
archiveIssue = undefined;
}

View file

@ -41,7 +41,7 @@ import { updatePersistentLayer } from "@/local-db/utils/utils";
import { workItemSortWithOrderByExtended } from "@/plane-web/store/issue/helpers/base-issue.store";
// services
import { CycleService } from "@/services/cycle.service";
import { IssueArchiveService, IssueDraftService, IssueService } from "@/services/issue";
import { IssueArchiveService, IssueService } from "@/services/issue";
import { ModuleService } from "@/services/module.service";
//
import { IIssueRootStore } from "../root.store";
@ -194,7 +194,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
// services
issueService;
issueArchiveService;
issueDraftService;
moduleService;
cycleService;
// root store
@ -238,8 +237,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
createIssue: action,
issueUpdate: action,
createDraftIssue: action,
updateDraftIssue: action,
updateIssueDates: action,
issueQuickAdd: action.bound,
removeIssue: action.bound,
@ -264,7 +261,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
this.issueService = new IssueService(serviceType);
this.issueArchiveService = new IssueArchiveService();
this.issueDraftService = new IssueDraftService();
this.moduleService = new ModuleService();
this.cycleService = new CycleService();
@ -613,54 +609,6 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
}
}
/**
* Similar to Create Issue but for creating Draft issues
* @param workspaceSlug
* @param projectId
* @param data draft issue data
* @returns
*/
async createDraftIssue(workspaceSlug: string, projectId: string, data: Partial<TIssue>) {
// call API to create a Draft issue
const response = await this.issueDraftService.createDraftIssue(workspaceSlug, projectId, data);
// call Fetch parent stats
this.fetchParentStats(workspaceSlug, projectId);
// Add issue to store
this.addIssue(response);
return response;
}
/**
* Similar to update issue but for draft issues.
* @param workspaceSlug
* @param projectId
* @param issueId
* @param data Partial Issue Data to be updated
*/
async updateDraftIssue(workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) {
// Store Before state of the issue
const issueBeforeUpdate = clone(this.rootIssueStore.issues.getIssueById(issueId));
try {
// Update the Respective Stores
this.rootIssueStore.issues.updateIssue(issueId, data);
this.updateIssueList({ ...issueBeforeUpdate, ...data } as TIssue, issueBeforeUpdate);
// call API to update the issue
await this.issueDraftService.updateDraftIssue(workspaceSlug, projectId, issueId, data);
// call Fetch parent stats
this.fetchParentStats(workspaceSlug, projectId);
// If the issue is updated to not a draft issue anymore remove from the store list
if (!isNil(data.is_draft) && !data.is_draft) this.removeIssueFromList(issueId);
} catch (error) {
// If errored out update store again to revert the change
this.rootIssueStore.issues.updateIssue(issueId, issueBeforeUpdate ?? {});
this.updateIssueList(issueBeforeUpdate, { ...issueBeforeUpdate, ...data } as TIssue);
throw error;
}
}
/**
* This method is called to delete an issue
* @param workspaceSlug

View file

@ -5,18 +5,13 @@ import { EIssueServiceType, TIssue, TIssueServiceType } from "@plane/types";
// local
import { persistence } from "@/local-db/storage.sqlite";
// services
import { IssueArchiveService, IssueDraftService, IssueService } from "@/services/issue";
import { IssueArchiveService, WorkspaceDraftService, IssueService } from "@/services/issue";
// types
import { IIssueDetail } from "./root.store";
export interface IIssueStoreActions {
// actions
fetchIssue: (
workspaceSlug: string,
projectId: string,
issueId: string,
issueStatus?: "DEFAULT" | "DRAFT"
) => Promise<TIssue>;
fetchIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<TIssue>;
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
archiveIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>;
@ -52,7 +47,7 @@ export class IssueStore implements IIssueStore {
issueService;
epicService;
issueArchiveService;
issueDraftService;
draftWorkItemService;
constructor(rootStore: IIssueDetail, serviceType: TIssueServiceType) {
makeObservable(this, {
@ -66,7 +61,7 @@ export class IssueStore implements IIssueStore {
this.issueService = new IssueService(serviceType);
this.epicService = new IssueService(EIssueServiceType.EPICS);
this.issueArchiveService = new IssueArchiveService(serviceType);
this.issueDraftService = new IssueDraftService();
this.draftWorkItemService = new WorkspaceDraftService();
}
getIsFetchingIssueDetails = computedFn((issueId: string | undefined) => {
@ -93,7 +88,7 @@ export class IssueStore implements IIssueStore {
});
// actions
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string, issueStatus = "DEFAULT") => {
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
const query = {
expand: "issue_reactions,issue_attachments,issue_link,parent",
};
@ -112,9 +107,7 @@ export class IssueStore implements IIssueStore {
this.localDBIssueDescription = issueId;
}
if (issueStatus === "DRAFT")
issue = await this.issueDraftService.getDraftIssueById(workspaceSlug, projectId, issueId, query);
else issue = await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
await this.issueService.retrieve(workspaceSlug, projectId, issueId, query);
if (!issue) throw new Error("Work item not found");

View file

@ -272,12 +272,8 @@ export abstract class IssueDetail implements IIssueDetail {
setIssueLinkData = (issueLinkData: TIssueLink | null) => (this.issueLinkData = issueLinkData);
// issue
fetchIssue = async (
workspaceSlug: string,
projectId: string,
issueId: string,
issueStatus: "DEFAULT" | "DRAFT" = "DEFAULT"
) => this.issue.fetchIssue(workspaceSlug, projectId, issueId, issueStatus);
fetchIssue = async (workspaceSlug: string, projectId: string, issueId: string) =>
this.issue.fetchIssue(workspaceSlug, projectId, issueId);
fetchIssueWithIdentifier = async (workspaceSlug: string, projectIdentifier: string, sequenceId: string) =>
this.issue.fetchIssueWithIdentifier(workspaceSlug, projectIdentifier, sequenceId);
updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial<TIssue>) =>

View file

@ -33,7 +33,6 @@ import { IWorkspaceMembership } from "@/store/member/workspace-member.store";
// issues data store
import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedIssues } from "./archived";
import { ICycleIssuesFilter, CycleIssuesFilter, ICycleIssues, CycleIssues } from "./cycle";
import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft";
import { IIssueStore, IssueStore } from "./issue.store";
import { ICalendarStore, CalendarStore } from "./issue_calendar_view.store";
import { IIssueKanBanViewStore, IssueKanBanViewStore } from "./issue_kanban_view.store";
@ -115,9 +114,6 @@ export interface IIssueRootStore {
archivedIssuesFilter: IArchivedIssuesFilter;
archivedIssues: IArchivedIssues;
draftIssuesFilter: IDraftIssuesFilter;
draftIssues: IDraftIssues;
issueKanBanView: IIssueKanBanViewStore;
issueCalendarView: ICalendarStore;
@ -186,9 +182,6 @@ export class IssueRootStore implements IIssueRootStore {
archivedIssuesFilter: IArchivedIssuesFilter;
archivedIssues: IArchivedIssues;
draftIssuesFilter: IDraftIssuesFilter;
draftIssues: IDraftIssues;
issueKanBanView: IIssueKanBanViewStore;
issueCalendarView: ICalendarStore;
@ -280,9 +273,6 @@ export class IssueRootStore implements IIssueRootStore {
this.archivedIssuesFilter = new ArchivedIssuesFilter(this);
this.archivedIssues = new ArchivedIssues(this, this.archivedIssuesFilter);
this.draftIssuesFilter = new DraftIssuesFilter(this);
this.draftIssues = new DraftIssues(this, this.draftIssuesFilter);
this.issueKanBanView = new IssueKanBanViewStore(this);
this.issueCalendarView = new CalendarStore();