[WEB-3980]feat:epic sub work item filters (#3117)

* feat: added filters for sub issues

* feat: added list groups for sub issues

* drat: epics groupby and filters

* feat: sorting, ordering and filtering for sub work items

* chore: reverted filter name change

* chore: reverted filter change

* feat: added sort and filter for initiatives

* fix: build errors
This commit is contained in:
Vamsi Krishna
2025-05-09 18:24:35 +05:30
committed by GitHub
parent 416ca22d42
commit e67c553876
17 changed files with 298 additions and 121 deletions

View File

@@ -1,5 +1,9 @@
import { ILayoutDisplayFiltersOptions, TIssueActivityComment } from "@plane/types";
import { ISSUE_DISPLAY_PROPERTIES_KEYS, EPICS_DISPLAY_PROPERTIES_KEYS } from "./common";
import {
ISSUE_DISPLAY_PROPERTIES_KEYS,
EPICS_DISPLAY_PROPERTIES_KEYS,
SUB_ISSUES_DISPLAY_PROPERTIES_KEYS,
} from "./common";
import { TActivityFilters } from "./filter";
import { EActivityFilterType } from "./filter";
@@ -170,6 +174,20 @@ export const ADDITIONAL_ISSUE_DISPLAY_FILTERS_BY_PAGE: {
},
},
},
initiatives: {
list: {
display_properties: SUB_ISSUES_DISPLAY_PROPERTIES_KEYS,
filters: ["priority", "state_group", "project", "issue_type", "assignees", "start_date", "target_date"],
display_filters: {
order_by: ["-created_at", "-updated_at", "start_date", "-priority"],
group_by: ["state_detail.group", "priority", "assignees", null],
},
extra_options: {
access: true,
values: ["sub_issue"],
},
},
},
};
export enum EActivityFilterTypeEE {

View File

@@ -70,6 +70,7 @@ export type TIssue = TBaseIssue &
sourceIssueId?: string;
state__group?: string | null;
update_status?: EUpdateStatus | undefined;
state__group?: string | null;
};
export type TIssueMap = {

View File

@@ -1,8 +1,6 @@
"use client";
import { FC } from "react";
import { cn } from "@plane/utils";
type Props = {
icon: React.ReactNode;
title: string;

View File

@@ -6,7 +6,6 @@ import { IIssueDisplayFilterOptions, ILayoutDisplayFiltersOptions, IIssueDisplay
import { cn } from "@plane/utils";
import { FilterDisplayProperties, FilterGroupBy, FilterOrderBy, FiltersDropdown } from "@/components/issues";
import { isDisplayFiltersApplied } from "@/components/issues/issue-layouts/utils";
type TSubIssueDisplayFiltersProps = {
displayProperties: IIssueDisplayProperties;
displayFilters: IIssueDisplayFilterOptions;

View File

@@ -16,7 +16,6 @@ import {
} from "@/components/issues";
import { isFiltersApplied } from "@/components/issues/issue-layouts/utils";
import { FilterIssueTypes } from "@/plane-web/components/issues/filters/issue-types";
type TSubIssueFiltersProps = {
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
filters: IIssueFilterOptions;

View File

@@ -35,10 +35,9 @@ export const SubWorkItemTitleActions: FC<TSubWorkItemTitleActionsProps> = observ
} = useMember();
// derived values
const subIssueFilters = getSubIssueFilters(parentId);
const projectStates = getProjectStates(projectId);
const projectMemberIds = getProjectMemberIds(projectId, false);
const subIssueFilters = getSubIssueFilters(parentId);
const layoutDisplayFiltersOptions = ISSUE_DISPLAY_FILTERS_BY_PAGE["sub_work_items"].list;
const handleDisplayFilters = useCallback(
@@ -69,7 +68,6 @@ export const SubWorkItemTitleActions: FC<TSubWorkItemTitleActionsProps> = observ
if (subIssueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateSubWorkItemFilters(EIssueFilterType.FILTERS, { [key]: newValues }, parentId);
},
[subIssueFilters?.filters, updateSubWorkItemFilters, parentId]

View File

@@ -126,7 +126,6 @@ export class IssueSubIssuesStore implements IIssueSubIssuesStore {
fetchSubIssues = async (workspaceSlug: string, projectId: string, parentIssueId: string) => {
this.loader = "init-loader";
const response = await this.issueService.subIssues(workspaceSlug, projectId, parentIssueId);
const subIssuesStateDistribution = response?.state_distribution ?? {};

View File

@@ -23,7 +23,6 @@ export const DEFAULT_DISPLAY_PROPERTIES = {
priority: true,
state: true,
};
export interface IWorkItemSubIssueFiltersStore {
subIssueFilters: Record<string, Partial<IIssueFilters>>;
// helpers methods
@@ -50,6 +49,8 @@ export class WorkItemSubIssueFiltersStore implements IWorkItemSubIssueFiltersSto
subIssueFilters: observable,
updateSubWorkItemFilters: action,
getSubIssueFilters: action,
getFilteredSubWorkItems: action,
resetFilters: action,
});
// root store

View File

@@ -7,7 +7,7 @@ import { EIssueServiceType } from "@plane/constants";
import { useLocalStorage } from "@plane/hooks";
import { Tabs } from "@plane/ui";
// components
import { RelationActionButton, SubIssuesActionButton } from "@/components/issues";
import { RelationActionButton } from "@/components/issues";
// plane web
import { OverviewSection } from "@/plane-web/components/common/layout/main/sections/overview-root";
// local components

View File

@@ -3,7 +3,7 @@ import React, { FC, useEffect, useState, useCallback } from "react";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// plane imports
import { EIssueServiceType } from "@plane/constants";
import { EIssueServiceType, EIssuesStoreType } from "@plane/constants";
import { TIssue, TSubIssueOperations } from "@plane/types";
import { getButtonStyling, LayersIcon } from "@plane/ui";
// components
@@ -119,9 +119,6 @@ export const EpicIssuesOverviewRoot: FC<Props> = observer((props) => {
useEffect(() => {
handleFetchSubIssues();
return () => {
handleFetchSubIssues();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [epicId]);
@@ -185,7 +182,6 @@ export const EpicIssuesOverviewRoot: FC<Props> = observer((props) => {
return (
<>
{/* Display list when group is none */}
<SubIssuesListRoot
workspaceSlug={workspaceSlug}
projectId={projectId}
@@ -196,6 +192,7 @@ export const EpicIssuesOverviewRoot: FC<Props> = observer((props) => {
handleIssueCrudState={handleIssueCrudState}
subIssueOperations={epicSubIssuesOperation}
issueServiceType={EIssueServiceType.EPICS}
storeType={EIssuesStoreType.EPIC}
/>
{shouldRenderDeleteIssueModal && (

View File

@@ -3,10 +3,16 @@ import { FC, useCallback } from "react";
import { observer } from "mobx-react";
import { EIssueFilterType, EIssueServiceType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
// types
import { IIssueDisplayFilterOptions, TIssueServiceType, IIssueDisplayProperties } from "@plane/types";
import {
IIssueDisplayFilterOptions,
TIssueServiceType,
IIssueDisplayProperties,
IIssueFilterOptions,
} from "@plane/types";
// components
import { SubIssuesActionButton, SubIssueDisplayFilters } from "@/components/issues/issue-detail-widgets/sub-issues";
import { useIssueDetail } from "@/hooks/store";
import { SubIssueFilters } from "@/components/issues/issue-detail-widgets/sub-issues/filters";
import { useIssueDetail, useMember, useProjectState } from "@/hooks/store";
type TSubWorkItemsActionsProps = {
workItemId: string;
@@ -22,40 +28,55 @@ export const SubWorkItemsActions: FC<TSubWorkItemsActionsProps> = observer((prop
// store hooks
const {
subIssues: {
filters: { getSubIssueFilters, updateSubIssueFilters },
filters: { getSubIssueFilters, updateSubWorkItemFilters },
},
} = useIssueDetail(workItemServiceType);
// derived values
const subIssueFilters = getSubIssueFilters(workItemId);
const { getProjectStates } = useProjectState();
const {
project: { getProjectMemberIds },
} = useMember();
// derived values
const projectMemberIds = getProjectMemberIds(projectId, false);
const projectStates = getProjectStates(projectId);
const subIssueFilters = getSubIssueFilters(workItemId);
// handlers
const handleDisplayFilters = useCallback(
(updatedDisplayFilter: Partial<IIssueDisplayFilterOptions>) => {
if (!workspaceSlug || !projectId) return;
updateSubIssueFilters(
workspaceSlug,
projectId,
EIssueFilterType.DISPLAY_FILTERS,
updatedDisplayFilter,
workItemId
);
updateSubWorkItemFilters(EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilter, workItemId);
},
[workspaceSlug, projectId, updateSubIssueFilters, workItemId]
[workspaceSlug, projectId, updateSubWorkItemFilters, workItemId]
);
const handleDisplayPropertiesUpdate = useCallback(
(updatedDisplayProperties: Partial<IIssueDisplayProperties>) => {
if (!workspaceSlug || !projectId) return;
updateSubIssueFilters(
workspaceSlug,
projectId,
EIssueFilterType.DISPLAY_PROPERTIES,
updatedDisplayProperties,
workItemId
);
updateSubWorkItemFilters(EIssueFilterType.DISPLAY_PROPERTIES, updatedDisplayProperties, workItemId);
},
[workspaceSlug, projectId, updateSubIssueFilters, workItemId]
[workspaceSlug, projectId, updateSubWorkItemFilters, workItemId]
);
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug || !projectId) return;
const newValues = subIssueFilters?.filters?.[key] ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (subIssueFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateSubWorkItemFilters(EIssueFilterType.FILTERS, { [key]: newValues }, workItemId);
},
[workspaceSlug, projectId, subIssueFilters?.filters, updateSubWorkItemFilters, workItemId]
);
const layoutDisplayFiltersOptions = ISSUE_DISPLAY_FILTERS_BY_PAGE["sub_work_items"]?.["list"];
@@ -70,6 +91,13 @@ export const SubWorkItemsActions: FC<TSubWorkItemsActionsProps> = observer((prop
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
handleDisplayFiltersUpdate={handleDisplayFilters}
/>
<SubIssueFilters
handleFiltersUpdate={handleFiltersUpdate}
filters={subIssueFilters?.filters ?? {}}
memberIds={projectMemberIds ?? undefined}
states={projectStates}
layoutDisplayFiltersOptions={layoutDisplayFiltersOptions}
/>
<SubIssuesActionButton issueId={workItemId} issueServiceType={workItemServiceType} disabled={disabled} />
</div>
);

View File

@@ -3,15 +3,18 @@
import React, { FC } from "react";
import { observer } from "mobx-react";
// plane imports
import { ListFilter } from "lucide-react";
import { useTranslation } from "@plane/i18n";
import { EpicIcon, getButtonStyling } from "@plane/ui";
import { GroupByColumnTypes } from "@plane/types";
import { Button, EpicIcon, getButtonStyling } from "@plane/ui";
// helpers
import { getGroupByColumns } from "@/components/issues/issue-layouts/utils";
import { cn } from "@/helpers/common.helper";
// plane-web
import { SectionEmptyState } from "@/plane-web/components/common";
import { useInitiatives } from "@/plane-web/hooks/store/use-initiatives";
// local components
import { EpicListItem } from "./epic-list-item/root";
import { EpicsGroup } from "./epic-list-item/epics-list-group";
type Props = {
workspaceSlug: string;
@@ -24,26 +27,58 @@ export const InitiativeEpicsCollapsibleContent: FC<Props> = observer((props) =>
const { workspaceSlug, initiativeId, toggleEpicModal, disabled = false } = props;
// store hooks
const {
initiative: { epics: {getInitiativeEpicsById} },
initiative: {
epics: {
getInitiativeEpicsById,
filters: { getInitiativeEpicsFiltersById, getGroupedEpics, getFilteredEpics, resetFilters },
},
},
} = useInitiatives();
const { t } = useTranslation();
// derived values
const filters = getInitiativeEpicsFiltersById(initiativeId);
const groupBy = filters?.displayFilters?.group_by;
const groupedEpicIds = getGroupedEpics(initiativeId);
const filteredEpicsCount = getFilteredEpics(initiativeId, filters?.filters ?? {}).length;
const groups = getGroupByColumns({
groupBy: groupBy as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: true,
isEpic: false,
});
// derived values
const initiativeEpicIds = getInitiativeEpicsById(initiativeId) ?? [];
return (
<div className="mt-3">
{initiativeEpicIds.length > 0 ? (
initiativeEpicIds.map((epicId) => (
<EpicListItem
key={epicId}
workspaceSlug={workspaceSlug}
epicId={epicId}
initiativeId={initiativeId}
disabled={disabled}
filteredEpicsCount > 0 ? (
groups?.map((group) => (
<EpicsGroup
key={group.id}
group={group}
epicIds={groupedEpicIds?.[group.id] ?? []}
disabled={disabled}
workspaceSlug={workspaceSlug}
initiativeId={initiativeId}
/>
))
) : (
<SectionEmptyState
heading="You dont have epics that match the filters you have applied."
subHeading="To see all epics, clear all applied filters."
icon={<ListFilter />}
actionElement={
<Button variant="neutral-primary" size="sm" onClick={() => resetFilters(initiativeId)}>
Clear filters
</Button>
}
/>
))
)
) : (
<SectionEmptyState
heading={t("epics.empty_state.section.title")}

View File

@@ -0,0 +1,67 @@
import { FC, useState } from "react";
import { observer } from "mobx-react";
import { ChevronRight, CircleDashed } from "lucide-react";
import { ALL_ISSUES } from "@plane/constants";
import { IGroupByColumn } from "@plane/types";
import { Collapsible } from "@plane/ui";
import { cn } from "@plane/utils";
import { EpicListItem } from "./root";
interface TEpicsGroupProps {
epicIds: string[];
workspaceSlug: string;
group: IGroupByColumn;
disabled: boolean;
initiativeId: string;
}
export const EpicsGroup: FC<TEpicsGroupProps> = observer((props) => {
const { group, disabled, initiativeId, workspaceSlug, epicIds } = props;
const isAllIssues = group.id === ALL_ISSUES;
// states
const [isCollapsibleOpen, setIsCollapsibleOpen] = useState(isAllIssues);
if (!epicIds.length) return null;
return (
<>
<Collapsible
isOpen={isCollapsibleOpen}
onToggle={() => setIsCollapsibleOpen(!isCollapsibleOpen)}
title={
!isAllIssues && (
<div className="flex items-center gap-2 p-3">
<ChevronRight
className={cn("size-3.5 transition-all text-custom-text-400", {
"rotate-90": isCollapsibleOpen,
})}
strokeWidth={2.5}
/>
<div className="flex-shrink-0 grid place-items-center overflow-hidden">
{group.icon ?? <CircleDashed className="size-3.5" strokeWidth={2} />}
</div>
<span className="text-sm text-custom-text-100 font-medium">{group.name}</span>
<span className="text-sm text-custom-text-400">{epicIds.length}</span>
</div>
)
}
buttonClassName={cn("hidden", !isAllIssues && "block")}
>
{/* Epics list */}
<div className="pl-2">
{epicIds?.map((epicId) => (
<EpicListItem
key={epicId}
workspaceSlug={workspaceSlug}
epicId={epicId}
initiativeId={initiativeId}
disabled={disabled}
/>
))}
</div>
</Collapsible>
</>
);
});

View File

@@ -1,12 +1,15 @@
import { FC } from "react";
import { FC, useCallback } from "react";
import cloneDeep from "lodash/cloneDeep";
import { observer } from "mobx-react";
import { Plus } from "lucide-react";
// plane imports
import { EIssueFilterType, ISSUE_DISPLAY_FILTERS_BY_PAGE } from "@plane/constants";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "@plane/types";
// components
import { SubIssueDisplayFilters } from "@/components/issues/issue-detail-widgets/sub-issues";
import { SubIssueFilters } from "@/components/issues/issue-detail-widgets/sub-issues/filters";
// hooks
import { useMember } from "@/hooks/store";
import { useInitiatives } from "@/plane-web/hooks/store/use-initiatives";
type TEpicsTitleActionsProps = {
@@ -23,25 +26,51 @@ export const EpicsTitleActions: FC<TEpicsTitleActionsProps> = observer((props) =
const {
initiative: {
epics: {
filters: { getInitiativeEpicsFiltersById, updateSubIssueFilters },
filters: { getInitiativeEpicsFiltersById, updateEpicsFilters },
},
},
} = useInitiatives();
const {
workspace: { getWorkspaceMemberIds },
} = useMember();
const memberIds = getWorkspaceMemberIds(workspaceSlug);
// derived values
const epicsFilters = getInitiativeEpicsFiltersById(initiativeId);
const displayProperties = epicsFilters?.displayProperties;
const displayFilters = epicsFilters?.displayFilters;
const handleDisplayPropertiesUpdate = (updatedDisplayProperties: Partial<IIssueDisplayProperties>) => {
updateSubIssueFilters(workspaceSlug, EIssueFilterType.DISPLAY_PROPERTIES, updatedDisplayProperties, initiativeId);
updateEpicsFilters(workspaceSlug, EIssueFilterType.DISPLAY_PROPERTIES, updatedDisplayProperties, initiativeId);
};
const handleDisplayFiltersUpdate = (updatedDisplayFilters: Partial<IIssueDisplayFilterOptions>) => {
updateSubIssueFilters(workspaceSlug, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilters, initiativeId);
updateEpicsFilters(workspaceSlug, EIssueFilterType.DISPLAY_FILTERS, updatedDisplayFilters, initiativeId);
};
const layoutDisplayFiltersOptions = ISSUE_DISPLAY_FILTERS_BY_PAGE.sub_work_items.list;
const handleFiltersUpdate = useCallback(
(key: keyof IIssueFilterOptions, value: string | string[]) => {
if (!workspaceSlug) return;
const newValues = cloneDeep(epicsFilters?.filters?.[key]) ?? [];
if (Array.isArray(value)) {
// this validation is majorly for the filter start_date, target_date custom
value.forEach((val) => {
if (!newValues.includes(val)) newValues.push(val);
else newValues.splice(newValues.indexOf(val), 1);
});
} else {
if (epicsFilters?.filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1);
else newValues.push(value);
}
updateEpicsFilters(workspaceSlug, EIssueFilterType.FILTERS, { [key]: newValues }, initiativeId);
},
[workspaceSlug, epicsFilters?.filters, updateEpicsFilters, initiativeId]
);
const layoutDisplayFiltersOptions = ISSUE_DISPLAY_FILTERS_BY_PAGE.initiatives.list;
const handlePropagation = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
@@ -58,10 +87,18 @@ export const EpicsTitleActions: FC<TEpicsTitleActionsProps> = observer((props) =
handleDisplayPropertiesUpdate={handleDisplayPropertiesUpdate}
handleDisplayFiltersUpdate={handleDisplayFiltersUpdate}
/>
<SubIssueFilters
handleFiltersUpdate={handleFiltersUpdate}
filters={epicsFilters?.filters ?? {}}
memberIds={memberIds ?? undefined}
layoutDisplayFiltersOptions={layoutDisplayFiltersOptions}
/>
{!disabled && (
<button type="button" onClick={() => toggleEpicModal()}>
<Plus className="h-4 w-4" />
</button>
<div>
<button type="button" onClick={() => toggleEpicModal()}>
<Plus className="h-4 w-4" />
</button>
</div>
)}
</div>
);

View File

@@ -45,6 +45,7 @@ export const ProjectNavigationRoot: FC<TProjectItemsRootProps> = observer((props
shouldRender: !!isProjectOverviewEnabled,
sortOrder: -2,
i18n_key: "common.overview",
key: "overview",
},
{
name: "Epics",
@@ -54,6 +55,7 @@ export const ProjectNavigationRoot: FC<TProjectItemsRootProps> = observer((props
shouldRender: !!isEpicsEnabled,
sortOrder: -1,
i18n_key: "sidebar.epics",
key: "epics",
},
];

View File

@@ -1,22 +1,30 @@
import set from "lodash/set";
import { action, makeObservable, observable } from "mobx";
import { action, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
import { EIssueFilterType } from "@plane/constants";
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilters, TIssueParams } from "@plane/types";
import {
IIssueDisplayFilterOptions,
IIssueDisplayProperties,
IIssueFilterOptions,
IIssueFilters,
TIssue,
} from "@plane/types";
import { getFilteredWorkItems, getGroupedWorkItemIds, updateFilters } from "@/store/issue/helpers/base-issues-utils";
import { DEFAULT_DISPLAY_PROPERTIES } from "@/store/issue/issue-details/sub_issues_filter.store";
import { InitiativeEpicStore } from "./initiative-epics.store";
export interface IInitiativeEpicsFilterStore {
initiativeEpicsFiltersMap: Record<string, Partial<IIssueFilters>>;
getInitiativeEpicsFiltersById: (initiativeId: string) => Partial<IIssueFilters> | undefined;
updateSubIssueFilters: (
updateEpicsFilters: (
workspaceSlug: string,
filterType: EIssueFilterType,
filters: IIssueDisplayFilterOptions | IIssueDisplayProperties,
initiativeId: string
) => Promise<void>;
// helpers
computedFilterParams: (initiativeId: string) => Partial<Record<TIssueParams, boolean | string>>;
getGroupedEpics: (initiativeId: string) => Record<string, string[]>;
getFilteredEpics: (initiativeId: string, filters: IIssueFilterOptions) => TIssue[];
resetFilters: (workItemId: string) => void;
// store
initiativeEpicStore: InitiativeEpicStore;
}
@@ -28,8 +36,11 @@ export class InitiativeEpicsFilterStore implements IInitiativeEpicsFilterStore {
constructor(initiativeEpicStore: InitiativeEpicStore) {
makeObservable(this, {
initiativeEpicsFiltersMap: observable,
updateSubIssueFilters: action,
updateEpicsFilters: action,
getInitiativeEpicsFiltersById: action,
getGroupedEpics: action,
getFilteredEpics: action,
resetFilters: action,
});
this.initiativeEpicStore = initiativeEpicStore;
@@ -39,20 +50,10 @@ export class InitiativeEpicsFilterStore implements IInitiativeEpicsFilterStore {
* Initialize the initiative epics filters
* @param initiativeId - The initiative id
*/
initInitiativeEpicsFilters = (initiativeId: string) => {
set(this.initiativeEpicsFiltersMap, [initiativeId], {
displayFilters: {},
displayProperties: {
key: true,
issue_type: true,
assignee: true,
start_date: true,
due_date: true,
labels: true,
priority: true,
state: true,
},
});
initializeFilters = (initiativeId: string) => {
set(this.initiativeEpicsFiltersMap, [initiativeId, "displayProperties"], DEFAULT_DISPLAY_PROPERTIES);
set(this.initiativeEpicsFiltersMap, [initiativeId, "filters"], {});
set(this.initiativeEpicsFiltersMap, [initiativeId, "displayFilters"], {});
};
/**
@@ -63,7 +64,7 @@ export class InitiativeEpicsFilterStore implements IInitiativeEpicsFilterStore {
getInitiativeEpicsFiltersById = (initiativeId: string) => {
// initialize the filters if no exists before
if (!this.initiativeEpicsFiltersMap?.[initiativeId]) {
this.initInitiativeEpicsFilters(initiativeId);
this.initializeFilters(initiativeId);
}
return this.initiativeEpicsFiltersMap?.[initiativeId];
};
@@ -75,54 +76,54 @@ export class InitiativeEpicsFilterStore implements IInitiativeEpicsFilterStore {
* @param filters - The filters
* @param initiativeId - The initiative id
*/
updateSubIssueFilters = async (
updateEpicsFilters = async (
workspaceSlug: string,
filterType: EIssueFilterType,
filters: IIssueDisplayFilterOptions | IIssueDisplayProperties,
initiativeId: string
) => {
const _filters = this.getInitiativeEpicsFiltersById(initiativeId);
switch (filterType) {
case EIssueFilterType.DISPLAY_FILTERS: {
set(this.initiativeEpicsFiltersMap, [initiativeId, "displayFilters"], {
..._filters.displayFilters,
...filters,
});
this.initiativeEpicStore.fetchInitiativeEpics(workspaceSlug, initiativeId);
break;
}
case EIssueFilterType.DISPLAY_PROPERTIES:
set(this.initiativeEpicsFiltersMap, [initiativeId, "displayProperties"], {
..._filters.displayProperties,
...filters,
});
break;
}
runInAction(() => {
updateFilters(this.initiativeEpicsFiltersMap, filterType, filters, initiativeId);
});
};
/**
* Compute the filter params for the initiative epics
* @param initiativeId - The initiative id
* @returns The computed filter params
* @description This method is used to get the grouped epics
* @param parentWorkItemId
* @returns
*/
computedFilterParams = (initiativeId: string) => {
const displayFilters = this.getInitiativeEpicsFiltersById(initiativeId).displayFilters;
getGroupedEpics = computedFn((parentWorkItemId: string) => {
const epicsFilters = this.getInitiativeEpicsFiltersById(parentWorkItemId);
// get group by and order by
const orderByKey = epicsFilters.displayFilters?.order_by;
const groupByKey = epicsFilters.displayFilters?.group_by;
const computedFilters: Partial<Record<TIssueParams, undefined | string[] | boolean | string>> = {
order_by: displayFilters?.order_by || undefined,
};
const filteredEpics = this.getFilteredEpics(parentWorkItemId, epicsFilters.filters ?? {});
const issueFiltersParams: Partial<Record<TIssueParams, boolean | string>> = {};
Object.keys(computedFilters).forEach((key) => {
const _key = key as TIssueParams;
const _value: string | boolean | string[] | undefined = computedFilters[_key];
const nonEmptyArrayValue = Array.isArray(_value) && _value.length === 0 ? undefined : _value;
if (nonEmptyArrayValue != undefined)
issueFiltersParams[_key] = Array.isArray(nonEmptyArrayValue)
? nonEmptyArrayValue.join(",")
: nonEmptyArrayValue;
});
const groupedEpics = getGroupedWorkItemIds(filteredEpics, groupByKey, orderByKey);
return issueFiltersParams;
return groupedEpics;
});
/**
* @description This method is used to get the filtered epics
* @param initiativeId
* @returns
*/
getFilteredEpics = computedFn((initiativeId: string, filters: IIssueFilterOptions) => {
const epicIds = this.initiativeEpicStore.getInitiativeEpicsById(initiativeId);
const epics = this.initiativeEpicStore.rootStore.issue.issues.getIssuesByIds(epicIds, "un-archived");
const filteredEpics = getFilteredWorkItems(epics, filters);
return filteredEpics;
});
/**
* @description This method is used to reset the filters
* @param initiativeId
*/
resetFilters = (initiativeId: string) => {
this.initializeFilters(initiativeId);
};
}

View File

@@ -158,11 +158,8 @@ export class InitiativeEpicStore implements IInitiativeEpicStore {
};
});
// get computed filters
const queries = this.filters.computedFilterParams(initiativeId);
// fetch the initiative epics
const response = await this.initiativeService.fetchInitiativeEpics(workspaceSlug, initiativeId, queries);
const response = await this.initiativeService.fetchInitiativeEpics(workspaceSlug, initiativeId);
const transformedResponse = response.map((epic) => ({
...epic,