[WEB-4951] [WEB-4884] feat: work item filters revamp (#7810)
This commit is contained in:
parent
e6a7ca4c72
commit
9aef5d4aa9
160 changed files with 5879 additions and 4881 deletions
|
|
@ -1,4 +1,10 @@
|
|||
import { EIssuesStoreType, ILayoutDisplayFiltersOptions, TIssueActivityComment } from "@plane/types";
|
||||
import {
|
||||
EIssuesStoreType,
|
||||
IIssueFilterOptions,
|
||||
ILayoutDisplayFiltersOptions,
|
||||
TIssueActivityComment,
|
||||
TWorkItemFilterProperty,
|
||||
} from "@plane/types";
|
||||
import {
|
||||
TIssueFilterPriorityObject,
|
||||
ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
|
|
@ -23,12 +29,17 @@ export enum EServerGroupByToFilterOptions {
|
|||
}
|
||||
|
||||
export enum EIssueFilterType {
|
||||
FILTERS = "filters",
|
||||
FILTERS = "rich_filters",
|
||||
DISPLAY_FILTERS = "display_filters",
|
||||
DISPLAY_PROPERTIES = "display_properties",
|
||||
KANBAN_FILTERS = "kanban_filters",
|
||||
}
|
||||
|
||||
export type TSupportedFilterTypeForUpdate =
|
||||
| EIssueFilterType.DISPLAY_FILTERS
|
||||
| EIssueFilterType.DISPLAY_PROPERTIES
|
||||
| EIssueFilterType.KANBAN_FILTERS;
|
||||
|
||||
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: {
|
||||
[key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]>;
|
||||
} = {
|
||||
|
|
@ -82,257 +93,218 @@ export const ISSUE_PRIORITY_FILTERS: TIssueFilterPriorityObject[] = [
|
|||
},
|
||||
];
|
||||
|
||||
export type TFiltersByLayout = {
|
||||
export type TFiltersLayoutOptions = {
|
||||
[layoutType: string]: ILayoutDisplayFiltersOptions;
|
||||
};
|
||||
|
||||
export type TFilterPropertiesByPageType = {
|
||||
filters: TWorkItemFilterProperty[];
|
||||
layoutOptions: TFiltersLayoutOptions;
|
||||
};
|
||||
|
||||
export type TIssueFiltersToDisplayByPageType = {
|
||||
[pageType: string]: TFiltersByLayout;
|
||||
[pageType: string]: TFilterPropertiesByPageType;
|
||||
};
|
||||
|
||||
export const ISSUE_DISPLAY_FILTERS_BY_PAGE: TIssueFiltersToDisplayByPageType = {
|
||||
profile_issues: {
|
||||
list: {
|
||||
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
filters: ["priority", "state_group", "label_id", "start_date", "target_date"],
|
||||
layoutOptions: {
|
||||
list: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: ["priority", "state_group", "labels", "start_date", "target_date"],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
kanban: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state_detail.group", "priority", "project", "labels"],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
archived_issues: {
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state", "cycle", "module", "priority", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"state_id",
|
||||
"cycle_id",
|
||||
"module_id",
|
||||
"assignee_id",
|
||||
"created_by_id",
|
||||
"label_id",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
layoutOptions: {
|
||||
list: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state", "cycle", "module", "priority", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
my_issues: {
|
||||
spreadsheet: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
"subscriber",
|
||||
"project",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
order_by: [],
|
||||
type: [null, "active", "backlog"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"label_id",
|
||||
"assignee_id",
|
||||
"created_by_id",
|
||||
"subscriber_id",
|
||||
"project_id",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
layoutOptions: {
|
||||
spreadsheet: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
order_by: [],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"labels",
|
||||
"assignees",
|
||||
"created_by",
|
||||
"subscriber",
|
||||
"project",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: false,
|
||||
values: [],
|
||||
list: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: false,
|
||||
values: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
issues: {
|
||||
list: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
|
||||
type: [null, "active", "backlog"],
|
||||
filters: [
|
||||
"priority",
|
||||
"state_group",
|
||||
"state_id",
|
||||
"cycle_id",
|
||||
"module_id",
|
||||
"assignee_id",
|
||||
"mention_id",
|
||||
"created_by_id",
|
||||
"label_id",
|
||||
"start_date",
|
||||
"target_date",
|
||||
],
|
||||
layoutOptions: {
|
||||
list: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
kanban: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by"],
|
||||
sub_group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
},
|
||||
},
|
||||
},
|
||||
kanban: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by"],
|
||||
sub_group_by: ["state", "priority", "cycle", "module", "labels", "assignees", "created_by", null],
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority", "target_date"],
|
||||
type: [null, "active", "backlog"],
|
||||
calendar: {
|
||||
display_properties: ["key", "issue_type"],
|
||||
display_filters: {
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["show_empty_groups", "sub_issue"],
|
||||
spreadsheet: {
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
},
|
||||
calendar: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ["key", "issue_type"],
|
||||
display_filters: {
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
spreadsheet: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ISSUE_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
gantt_chart: {
|
||||
filters: [
|
||||
"priority",
|
||||
"state",
|
||||
"cycle",
|
||||
"module",
|
||||
"assignees",
|
||||
"mentions",
|
||||
"created_by",
|
||||
"labels",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"issue_type",
|
||||
],
|
||||
display_properties: ["key", "issue_type"],
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: [null, "active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
gantt_chart: {
|
||||
display_properties: ["key", "issue_type"],
|
||||
display_filters: {
|
||||
order_by: ["sort_order", "-created_at", "-updated_at", "start_date", "-priority"],
|
||||
type: ["active", "backlog"],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sub_work_items: {
|
||||
list: {
|
||||
display_properties: SUB_ISSUES_DISPLAY_PROPERTIES_KEYS,
|
||||
filters: ["priority", "state", "issue_type", "assignees", "start_date", "target_date"],
|
||||
display_filters: {
|
||||
order_by: ["-created_at", "-updated_at", "start_date", "-priority"],
|
||||
group_by: ["state", "priority", "assignees", null],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
filters: ["priority", "state_id", "assignee_id", "start_date", "target_date"],
|
||||
layoutOptions: {
|
||||
list: {
|
||||
display_properties: SUB_ISSUES_DISPLAY_PROPERTIES_KEYS,
|
||||
display_filters: {
|
||||
order_by: ["-created_at", "-updated_at", "start_date", "-priority"],
|
||||
group_by: ["state", "priority", "assignees", null],
|
||||
},
|
||||
extra_options: {
|
||||
access: true,
|
||||
values: ["sub_issue"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ISSUE_STORE_TO_FILTERS_MAP: Partial<Record<EIssuesStoreType, TFiltersByLayout>> = {
|
||||
export const ISSUE_STORE_TO_FILTERS_MAP: Partial<Record<EIssuesStoreType, TFilterPropertiesByPageType>> = {
|
||||
[EIssuesStoreType.PROJECT]: ISSUE_DISPLAY_FILTERS_BY_PAGE.issues,
|
||||
};
|
||||
|
||||
export const SUB_WORK_ITEM_AVAILABLE_FILTERS_FOR_WORK_ITEM_PAGE: (keyof IIssueFilterOptions)[] = [
|
||||
"priority",
|
||||
"state",
|
||||
"issue_type",
|
||||
"assignees",
|
||||
"start_date",
|
||||
"target_date",
|
||||
];
|
||||
|
||||
export enum EActivityFilterType {
|
||||
ACTIVITY = "ACTIVITY",
|
||||
COMMENT = "COMMENT",
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
export * from "./rich-filters";
|
||||
export * from "./work-item-filters";
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@ type TOperatorOptionForDisplay = {
|
|||
export interface IFilterConfig<P extends TFilterProperty, V extends TFilterValue = TFilterValue>
|
||||
extends TFilterConfig<P, V> {
|
||||
// computed
|
||||
allSupportedOperators: TSupportedOperators[];
|
||||
allSupportedOperatorConfigs: TOperatorSpecificConfigs<V>[keyof TOperatorSpecificConfigs<V>][];
|
||||
allEnabledSupportedOperators: TSupportedOperators[];
|
||||
firstOperator: TSupportedOperators | undefined;
|
||||
// computed functions
|
||||
getOperatorConfig: (
|
||||
|
|
@ -76,8 +75,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
|
|||
supportedOperatorConfigsMap: observable,
|
||||
allowMultipleFilters: observable,
|
||||
// computed
|
||||
allSupportedOperators: computed,
|
||||
allSupportedOperatorConfigs: computed,
|
||||
allEnabledSupportedOperators: computed,
|
||||
firstOperator: computed,
|
||||
// actions
|
||||
mutate: action,
|
||||
|
|
@ -90,16 +88,10 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
|
|||
* Returns all supported operators.
|
||||
* @returns All supported operators.
|
||||
*/
|
||||
get allSupportedOperators(): IFilterConfig<P, V>["allSupportedOperators"] {
|
||||
return Array.from(this.supportedOperatorConfigsMap.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all supported operator configs.
|
||||
* @returns All supported operator configs.
|
||||
*/
|
||||
get allSupportedOperatorConfigs(): IFilterConfig<P, V>["allSupportedOperatorConfigs"] {
|
||||
return Array.from(this.supportedOperatorConfigsMap.values());
|
||||
get allEnabledSupportedOperators(): IFilterConfig<P, V>["allEnabledSupportedOperators"] {
|
||||
return Array.from(this.supportedOperatorConfigsMap.entries())
|
||||
.filter(([, operatorConfig]) => operatorConfig.isOperatorEnabled)
|
||||
.map(([operator]) => operator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -107,7 +99,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
|
|||
* @returns The first operator.
|
||||
*/
|
||||
get firstOperator(): IFilterConfig<P, V>["firstOperator"] {
|
||||
return this.allSupportedOperators[0];
|
||||
return this.allEnabledSupportedOperators[0];
|
||||
}
|
||||
|
||||
// ------------ computed functions ------------
|
||||
|
|
@ -168,7 +160,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
|
|||
const operatorOptions: TOperatorOptionForDisplay[] = [];
|
||||
|
||||
// Process each supported operator to build display options
|
||||
for (const operator of this.allSupportedOperators) {
|
||||
for (const operator of this.allEnabledSupportedOperators) {
|
||||
const displayOperator = this.getDisplayOperatorByValue(operator, value);
|
||||
const displayOperatorLabel = this.getLabelForOperator(displayOperator);
|
||||
operatorOptions.push({
|
||||
|
|
|
|||
259
packages/shared-state/src/store/work-item-filters/adapter.ts
Normal file
259
packages/shared-state/src/store/work-item-filters/adapter.ts
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
// plane imports
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import {
|
||||
LOGICAL_OPERATOR,
|
||||
SingleOrArray,
|
||||
TFilterExpression,
|
||||
TFilterValue,
|
||||
TSupportedOperators,
|
||||
TWorkItemFilterConditionData,
|
||||
TWorkItemFilterConditionKey,
|
||||
TWorkItemFilterExpression,
|
||||
TWorkItemFilterExpressionData,
|
||||
TWorkItemFilterProperty,
|
||||
WORK_ITEM_FILTER_PROPERTY_KEYS,
|
||||
} from "@plane/types";
|
||||
import { createConditionNode, createAndGroupNode, isAndGroupNode, isConditionNode } from "@plane/utils";
|
||||
// local imports
|
||||
import { FilterAdapter } from "../rich-filters/adapter";
|
||||
|
||||
class WorkItemFiltersAdapter extends FilterAdapter<TWorkItemFilterProperty, TWorkItemFilterExpression> {
|
||||
/**
|
||||
* Converts external work item filter expression to internal filter tree
|
||||
* @param externalFilter - The external filter expression
|
||||
* @returns Internal filter expression or null
|
||||
*/
|
||||
toInternal(externalFilter: TWorkItemFilterExpression): TFilterExpression<TWorkItemFilterProperty> | null {
|
||||
if (!externalFilter || isEmpty(externalFilter)) return null;
|
||||
|
||||
try {
|
||||
return this._convertExpressionToInternal(externalFilter);
|
||||
} catch (error) {
|
||||
console.error("Failed to convert external filter to internal:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts external expression data to internal filter tree
|
||||
* @param expression - The external expression data
|
||||
* @returns Internal filter expression
|
||||
*/
|
||||
private _convertExpressionToInternal(
|
||||
expression: TWorkItemFilterExpressionData
|
||||
): TFilterExpression<TWorkItemFilterProperty> {
|
||||
if (!expression || isEmpty(expression)) {
|
||||
throw new Error("Invalid expression: empty or null data");
|
||||
}
|
||||
|
||||
// Check if it's a simple condition (has field property)
|
||||
if (this._isWorkItemFilterConditionData(expression)) {
|
||||
const conditionResult = this._extractWorkItemFilterConditionData(expression);
|
||||
if (!conditionResult) {
|
||||
throw new Error("Failed to extract condition data");
|
||||
}
|
||||
|
||||
const [property, operator, value] = conditionResult;
|
||||
return createConditionNode({
|
||||
property,
|
||||
operator,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
// It's a logical group - check which type
|
||||
const expressionKeys = Object.keys(expression);
|
||||
|
||||
if (LOGICAL_OPERATOR.AND in expression) {
|
||||
const andExpression = expression as { [LOGICAL_OPERATOR.AND]: TWorkItemFilterExpressionData[] };
|
||||
const andConditions = andExpression[LOGICAL_OPERATOR.AND];
|
||||
|
||||
if (!Array.isArray(andConditions) || andConditions.length === 0) {
|
||||
throw new Error("AND group must contain at least one condition");
|
||||
}
|
||||
|
||||
const convertedConditions = andConditions.map((item) => this._convertExpressionToInternal(item));
|
||||
return createAndGroupNode(convertedConditions);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid expression: unknown structure with keys [${expressionKeys.join(", ")}]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts internal filter expression to external format
|
||||
* @param internalFilter - The internal filter expression
|
||||
* @returns External filter expression
|
||||
*/
|
||||
toExternal(internalFilter: TFilterExpression<TWorkItemFilterProperty>): TWorkItemFilterExpression {
|
||||
if (!internalFilter) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return this._convertExpressionToExternal(internalFilter);
|
||||
} catch (error) {
|
||||
console.error("Failed to convert internal filter to external:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively converts internal expression to external format
|
||||
* @param expression - The internal filter expression
|
||||
* @returns External expression data
|
||||
*/
|
||||
private _convertExpressionToExternal(
|
||||
expression: TFilterExpression<TWorkItemFilterProperty>
|
||||
): TWorkItemFilterExpressionData {
|
||||
if (isConditionNode(expression)) {
|
||||
return this._createWorkItemFilterConditionData(expression.property, expression.operator, expression.value);
|
||||
}
|
||||
|
||||
// It's a group node
|
||||
|
||||
if (isAndGroupNode(expression)) {
|
||||
return {
|
||||
[LOGICAL_OPERATOR.AND]: expression.children.map((child) => this._convertExpressionToExternal(child)),
|
||||
} as TWorkItemFilterExpressionData;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown group node type for expression`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard to check if data is of type TWorkItemFilterConditionData
|
||||
* @param data - The data to check
|
||||
* @returns True if data is TWorkItemFilterConditionData, false otherwise
|
||||
*/
|
||||
private _isWorkItemFilterConditionData = (data: unknown): data is TWorkItemFilterConditionData => {
|
||||
if (!data || typeof data !== "object" || isEmpty(data)) return false;
|
||||
|
||||
const keys = Object.keys(data);
|
||||
if (keys.length === 0) return false;
|
||||
|
||||
// Check if any key contains logical operators (would indicate it's a group)
|
||||
const hasLogicalOperators = keys.some((key) => key === LOGICAL_OPERATOR.AND);
|
||||
if (hasLogicalOperators) return false;
|
||||
|
||||
// All keys must match the work item filter condition key pattern
|
||||
return keys.every((key) => this._isValidWorkItemFilterConditionKey(key));
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates if a key is a valid work item filter condition key
|
||||
* @param key - The key to validate
|
||||
* @returns True if the key is valid
|
||||
*/
|
||||
private _isValidWorkItemFilterConditionKey = (key: string): key is TWorkItemFilterConditionKey => {
|
||||
if (typeof key !== "string" || key.length === 0) return false;
|
||||
|
||||
// Find the last occurrence of '__' to separate property from operator
|
||||
const lastDoubleUnderscoreIndex = key.lastIndexOf("__");
|
||||
if (
|
||||
lastDoubleUnderscoreIndex === -1 ||
|
||||
lastDoubleUnderscoreIndex === 0 ||
|
||||
lastDoubleUnderscoreIndex === key.length - 2
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const property = key.substring(0, lastDoubleUnderscoreIndex);
|
||||
const operator = key.substring(lastDoubleUnderscoreIndex + 2);
|
||||
|
||||
// Validate property is in allowed list
|
||||
if (!WORK_ITEM_FILTER_PROPERTY_KEYS.includes(property as TWorkItemFilterProperty)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate operator is not empty
|
||||
return operator.length > 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts property, operator and value from work item filter condition data
|
||||
* @param data - The condition data
|
||||
* @returns Tuple of property, operator and value, or null if invalid
|
||||
*/
|
||||
private _extractWorkItemFilterConditionData = (
|
||||
data: TWorkItemFilterConditionData
|
||||
): [TWorkItemFilterProperty, TSupportedOperators, SingleOrArray<TFilterValue>] | null => {
|
||||
const keys = Object.keys(data);
|
||||
if (keys.length !== 1) {
|
||||
console.error("Work item filter condition data must have exactly one key");
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = keys[0];
|
||||
if (!this._isValidWorkItemFilterConditionKey(key)) {
|
||||
console.error(`Invalid work item filter condition key: ${key}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the last occurrence of '__' to separate property from operator
|
||||
const lastDoubleUnderscoreIndex = key.lastIndexOf("__");
|
||||
const property = key.substring(0, lastDoubleUnderscoreIndex);
|
||||
const operator = key.substring(lastDoubleUnderscoreIndex + 2);
|
||||
|
||||
const rawValue = data[key as TWorkItemFilterConditionKey];
|
||||
|
||||
if (typeof rawValue !== "string") {
|
||||
console.error(`Filter value must be a string, got: ${typeof rawValue}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse comma-separated values
|
||||
const parsedValue = this._parseFilterValue(rawValue);
|
||||
|
||||
return [property as TWorkItemFilterProperty, operator as TSupportedOperators, parsedValue];
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses filter value from string format
|
||||
* @param value - The string value to parse
|
||||
* @returns Parsed value as string or array of strings
|
||||
*/
|
||||
private _parseFilterValue = (value: string): SingleOrArray<TFilterValue> => {
|
||||
if (typeof value !== "string") return value;
|
||||
|
||||
// Handle empty string
|
||||
if (value === "") return value;
|
||||
|
||||
// Split by comma if contains comma, otherwise return as single value
|
||||
if (value.includes(",")) {
|
||||
// Split and trim each value, filter out empty strings
|
||||
const splitValues = value
|
||||
.split(",")
|
||||
.map((v) => v.trim())
|
||||
.filter((v) => v.length > 0);
|
||||
|
||||
// Return single value if only one non-empty value after split
|
||||
return splitValues.length === 1 ? splitValues[0] : splitValues;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates TWorkItemFilterConditionData from property, operator and value
|
||||
* @param property - The filter property key
|
||||
* @param operator - The filter operator
|
||||
* @param value - The filter value
|
||||
* @returns The condition data object
|
||||
*/
|
||||
private _createWorkItemFilterConditionData = (
|
||||
property: TWorkItemFilterProperty,
|
||||
operator: TSupportedOperators,
|
||||
value: SingleOrArray<TFilterValue>
|
||||
): TWorkItemFilterConditionData => {
|
||||
const conditionKey = `${property}__${operator}` as TWorkItemFilterConditionKey;
|
||||
|
||||
// Convert value to string format
|
||||
const stringValue = Array.isArray(value) ? value.join(",") : value;
|
||||
|
||||
return {
|
||||
[conditionKey]: stringValue,
|
||||
} as TWorkItemFilterConditionData;
|
||||
};
|
||||
}
|
||||
|
||||
export const workItemFiltersAdapter = new WorkItemFiltersAdapter();
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
import { action, makeObservable, observable } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// plane imports
|
||||
import { TExpressionOptions } from "@plane/constants";
|
||||
import { EIssuesStoreType, LOGICAL_OPERATOR, TWorkItemFilterExpression, TWorkItemFilterProperty } from "@plane/types";
|
||||
import { getOperatorForPayload } from "@plane/utils";
|
||||
// local imports
|
||||
import { buildWorkItemFilterExpressionFromConditions, TWorkItemFilterCondition } from "../../utils";
|
||||
import { FilterInstance, IFilterInstance } from "../rich-filters/filter";
|
||||
import { workItemFiltersAdapter } from "./adapter";
|
||||
|
||||
type TGetOrCreateFilterParams = {
|
||||
entityId: string;
|
||||
entityType: EIssuesStoreType;
|
||||
expressionOptions?: TExpressionOptions<TWorkItemFilterExpression>;
|
||||
initialExpression?: TWorkItemFilterExpression;
|
||||
onExpressionChange?: (expression: TWorkItemFilterExpression) => void;
|
||||
};
|
||||
|
||||
type TWorkItemFilterKey = `${EIssuesStoreType}-${string}`;
|
||||
|
||||
export interface IWorkItemFilterStore {
|
||||
filters: Map<TWorkItemFilterKey, IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>>; // key is the entity id (project, cycle, workspace, teamspace, etc)
|
||||
getFilter: (
|
||||
entityType: EIssuesStoreType,
|
||||
entityId: string
|
||||
) => IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression> | undefined;
|
||||
getOrCreateFilter: (
|
||||
params: TGetOrCreateFilterParams
|
||||
) => IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>;
|
||||
resetExpression: (entityType: EIssuesStoreType, entityId: string, expression: TWorkItemFilterExpression) => void;
|
||||
updateFilterExpressionFromConditions: (
|
||||
entityType: EIssuesStoreType,
|
||||
entityId: string,
|
||||
conditions: TWorkItemFilterCondition[],
|
||||
fallbackFn: (expression: TWorkItemFilterExpression) => Promise<void>
|
||||
) => Promise<void>;
|
||||
updateFilterValueFromSidebar: (
|
||||
entityType: EIssuesStoreType,
|
||||
entityId: string,
|
||||
condition: TWorkItemFilterCondition
|
||||
) => void;
|
||||
deleteFilter: (entityType: EIssuesStoreType, entityId: string) => void;
|
||||
}
|
||||
|
||||
export class WorkItemFilterStore implements IWorkItemFilterStore {
|
||||
// observable
|
||||
filters: IWorkItemFilterStore["filters"];
|
||||
|
||||
constructor() {
|
||||
this.filters = new Map<TWorkItemFilterKey, IFilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>>();
|
||||
makeObservable(this, {
|
||||
filters: observable,
|
||||
getOrCreateFilter: action,
|
||||
resetExpression: action,
|
||||
updateFilterExpressionFromConditions: action,
|
||||
deleteFilter: action,
|
||||
});
|
||||
}
|
||||
|
||||
// ------------ computed functions ------------
|
||||
|
||||
/**
|
||||
* Returns a filter instance.
|
||||
* @param entityType - The entity type.
|
||||
* @param entityId - The entity id.
|
||||
* @returns The filter instance.
|
||||
*/
|
||||
getFilter: IWorkItemFilterStore["getFilter"] = computedFn((entityType, entityId) =>
|
||||
this.filters.get(this._getFilterKey(entityType, entityId))
|
||||
);
|
||||
|
||||
// ------------ actions ------------
|
||||
|
||||
/**
|
||||
* Gets or creates a new filter instance.
|
||||
* If the instance already exists, updates its expression options to ensure they're current.
|
||||
*/
|
||||
getOrCreateFilter: IWorkItemFilterStore["getOrCreateFilter"] = action((params) => {
|
||||
const existingFilter = this.getFilter(params.entityType, params.entityId);
|
||||
if (existingFilter) {
|
||||
// Update expression options on existing filter to ensure they're current
|
||||
if (params.expressionOptions) {
|
||||
existingFilter.updateExpressionOptions(params.expressionOptions);
|
||||
}
|
||||
// Update callback if provided
|
||||
if (params.onExpressionChange) {
|
||||
existingFilter.onExpressionChange = params.onExpressionChange;
|
||||
}
|
||||
return existingFilter;
|
||||
}
|
||||
|
||||
// create new filter instance
|
||||
const newFilter = this._initializeFilterInstance(params);
|
||||
this.filters.set(this._getFilterKey(params.entityType, params.entityId), newFilter);
|
||||
|
||||
return newFilter;
|
||||
});
|
||||
|
||||
/**
|
||||
* Resets the initial expression for a filter instance.
|
||||
* @param entityType - The entity type.
|
||||
* @param entityId - The entity id.
|
||||
* @param expression - The expression to update.
|
||||
*/
|
||||
resetExpression: IWorkItemFilterStore["resetExpression"] = action((entityType, entityId, expression) => {
|
||||
const filter = this.getFilter(entityType, entityId);
|
||||
if (filter) {
|
||||
filter.resetExpression(expression);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the filter expression from conditions.
|
||||
* @param entityType - The entity type.
|
||||
* @param entityId - The entity id.
|
||||
* @param conditions - The conditions to update.
|
||||
* @param fallbackFn - The fallback function to update the expression if the filter instance does not exist.
|
||||
*/
|
||||
updateFilterExpressionFromConditions: IWorkItemFilterStore["updateFilterExpressionFromConditions"] = action(
|
||||
async (entityType, entityId, conditions, fallbackFn) => {
|
||||
const filter = this.getFilter(entityType, entityId);
|
||||
const newFilterExpression = buildWorkItemFilterExpressionFromConditions({
|
||||
conditions,
|
||||
});
|
||||
if (!newFilterExpression) return;
|
||||
|
||||
// Update the filter expression using the filter instance if it exists, otherwise use the fallback function
|
||||
if (filter) {
|
||||
filter.resetExpression(newFilterExpression, false);
|
||||
} else {
|
||||
await fallbackFn(newFilterExpression);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Handles sidebar filter updates by adding new conditions or updating existing ones.
|
||||
* This method processes filter conditions from the sidebar UI and applies them to the
|
||||
* appropriate filter instance, handling both positive and negative operators correctly.
|
||||
*
|
||||
* @param entityType - The entity type (e.g., project, cycle, module)
|
||||
* @param entityId - The unique identifier for the entity
|
||||
* @param condition - The filter condition containing property, operator, and value
|
||||
*/
|
||||
updateFilterValueFromSidebar: IWorkItemFilterStore["updateFilterValueFromSidebar"] = action(
|
||||
(entityType, entityId, condition) => {
|
||||
// Retrieve the filter instance for the specified entity
|
||||
const filter = this.getFilter(entityType, entityId);
|
||||
|
||||
// Early return if filter instance doesn't exist
|
||||
if (!filter) {
|
||||
console.warn(
|
||||
`Cannot handle sidebar filters update: filter instance not found for entity type "${entityType}" with ID "${entityId}"`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for existing conditions with the same property and operator
|
||||
const conditionNode = filter.findFirstConditionByPropertyAndOperator(condition.property, condition.operator);
|
||||
|
||||
// No existing condition found - add new condition with AND logic
|
||||
if (!conditionNode) {
|
||||
const { operator, isNegation } = getOperatorForPayload(condition.operator);
|
||||
|
||||
// Create the condition payload with normalized operator
|
||||
const conditionPayload = {
|
||||
property: condition.property,
|
||||
operator,
|
||||
value: condition.value,
|
||||
};
|
||||
|
||||
filter.addCondition(LOGICAL_OPERATOR.AND, conditionPayload, isNegation);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update existing condition (assuming single condition per property-operator pair)
|
||||
filter.updateConditionValue(conditionNode.id, condition.value);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Deletes a filter instance.
|
||||
* @param entityType - The entity type.
|
||||
* @param entityId - The entity id.
|
||||
*/
|
||||
deleteFilter: IWorkItemFilterStore["deleteFilter"] = action((entityType, entityId) => {
|
||||
this.filters.delete(this._getFilterKey(entityType, entityId));
|
||||
});
|
||||
|
||||
// ------------ private helpers ------------
|
||||
|
||||
/**
|
||||
* Returns a filter key.
|
||||
* @param entityType - The entity type.
|
||||
* @param entityId - The entity id.s
|
||||
* @returns The filter key.
|
||||
*/
|
||||
_getFilterKey = (entityType: EIssuesStoreType, entityId: string): TWorkItemFilterKey => `${entityType}-${entityId}`;
|
||||
|
||||
/**
|
||||
* Initializes a filter instance.
|
||||
* @param params - The parameters for the filter instance.
|
||||
* @returns The filter instance.
|
||||
*/
|
||||
_initializeFilterInstance = (params: TGetOrCreateFilterParams) =>
|
||||
new FilterInstance<TWorkItemFilterProperty, TWorkItemFilterExpression>({
|
||||
adapter: workItemFiltersAdapter,
|
||||
initialExpression: params.initialExpression,
|
||||
onExpressionChange: params.onExpressionChange,
|
||||
options: {
|
||||
expression: params.expressionOptions,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./adapter";
|
||||
export * from "./filter.store";
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from "./rich-filter.helper";
|
||||
export * from "./work-item-filters.helper";
|
||||
|
|
|
|||
32
packages/shared-state/src/utils/work-item-filters.helper.ts
Normal file
32
packages/shared-state/src/utils/work-item-filters.helper.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// plane imports
|
||||
import {
|
||||
TBuildFilterExpressionParams,
|
||||
TFilterConditionForBuild,
|
||||
TFilterValue,
|
||||
TWorkItemFilterExpression,
|
||||
TWorkItemFilterProperty,
|
||||
} from "@plane/types";
|
||||
// local imports
|
||||
import { workItemFiltersAdapter } from "../store/work-item-filters/adapter";
|
||||
import { buildTempFilterExpressionFromConditions } from "./rich-filter.helper";
|
||||
|
||||
export type TWorkItemFilterCondition = TFilterConditionForBuild<TWorkItemFilterProperty, TFilterValue>;
|
||||
|
||||
/**
|
||||
* Builds a work item filter expression from conditions.
|
||||
* @param params.conditions - The conditions for building the filter expression.
|
||||
* @returns The work item filter expression.
|
||||
*/
|
||||
export const buildWorkItemFilterExpressionFromConditions = (
|
||||
params: Omit<
|
||||
TBuildFilterExpressionParams<TWorkItemFilterProperty, TFilterValue, TWorkItemFilterExpression>,
|
||||
"adapter"
|
||||
>
|
||||
): TWorkItemFilterExpression | undefined => {
|
||||
const workItemFilterExpression = buildTempFilterExpressionFromConditions({
|
||||
...params,
|
||||
adapter: workItemFiltersAdapter,
|
||||
});
|
||||
if (!workItemFilterExpression) console.error("Failed to build work item filter expression from conditions");
|
||||
return workItemFilterExpression;
|
||||
};
|
||||
|
|
@ -6,7 +6,6 @@ import { IStateLite } from "./state";
|
|||
import { IUserLite } from "./users";
|
||||
import {
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
TIssueExtraOptions,
|
||||
TIssueGroupByOptions,
|
||||
TIssueGroupingFilters,
|
||||
|
|
@ -219,7 +218,6 @@ export interface IIssueListRow {
|
|||
}
|
||||
|
||||
export interface ILayoutDisplayFiltersOptions {
|
||||
filters: (keyof IIssueFilterOptions)[];
|
||||
display_properties: (keyof IIssueDisplayProperties)[];
|
||||
display_filters: {
|
||||
group_by?: TIssueGroupByOptions[];
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export type TNegativeOperatorConfig = { allowNegative: true; negOperatorLabel?:
|
|||
* - negativeOperatorConfig: Configuration for negative operators
|
||||
*/
|
||||
export type TBaseFilterFieldConfig = {
|
||||
isOperatorEnabled?: boolean;
|
||||
operatorLabel?: string;
|
||||
} & TNegativeOperatorConfig;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,13 +26,16 @@ export const CORE_COMPARISON_OPERATOR = {
|
|||
RANGE: "range",
|
||||
} as const;
|
||||
|
||||
// -------- TYPE EXPORTS --------
|
||||
|
||||
type TCoreEqualityOperator = (typeof CORE_EQUALITY_OPERATOR)[keyof typeof CORE_EQUALITY_OPERATOR];
|
||||
type TCoreCollectionOperator = (typeof CORE_COLLECTION_OPERATOR)[keyof typeof CORE_COLLECTION_OPERATOR];
|
||||
type TCoreComparisonOperator = (typeof CORE_COMPARISON_OPERATOR)[keyof typeof CORE_COMPARISON_OPERATOR];
|
||||
/**
|
||||
* All core operators
|
||||
*/
|
||||
export const CORE_OPERATORS = {
|
||||
...CORE_EQUALITY_OPERATOR,
|
||||
...CORE_COLLECTION_OPERATOR,
|
||||
...CORE_COMPARISON_OPERATOR,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* All core operators that can be used in filter conditions
|
||||
*/
|
||||
export type TCoreSupportedOperators = TCoreEqualityOperator | TCoreCollectionOperator | TCoreComparisonOperator;
|
||||
export type TCoreSupportedOperators = (typeof CORE_OPERATORS)[keyof typeof CORE_OPERATORS];
|
||||
|
|
|
|||
|
|
@ -18,16 +18,15 @@ export const EXTENDED_COLLECTION_OPERATOR = {} as const;
|
|||
*/
|
||||
export const EXTENDED_COMPARISON_OPERATOR = {} as const;
|
||||
|
||||
// -------- TYPE EXPORTS --------
|
||||
|
||||
type TExtendedEqualityOperator = (typeof EXTENDED_EQUALITY_OPERATOR)[keyof typeof EXTENDED_EQUALITY_OPERATOR];
|
||||
type TExtendedCollectionOperator = (typeof EXTENDED_COLLECTION_OPERATOR)[keyof typeof EXTENDED_COLLECTION_OPERATOR];
|
||||
type TExtendedComparisonOperator = (typeof EXTENDED_COMPARISON_OPERATOR)[keyof typeof EXTENDED_COMPARISON_OPERATOR];
|
||||
|
||||
/**
|
||||
* All extended operators
|
||||
*/
|
||||
export const EXTENDED_OPERATORS = {
|
||||
...EXTENDED_EQUALITY_OPERATOR,
|
||||
...EXTENDED_COLLECTION_OPERATOR,
|
||||
...EXTENDED_COMPARISON_OPERATOR,
|
||||
} as const;
|
||||
/**
|
||||
* All extended operators that can be used in filter conditions
|
||||
*/
|
||||
export type TExtendedSupportedOperators =
|
||||
| TExtendedEqualityOperator
|
||||
| TExtendedCollectionOperator
|
||||
| TExtendedComparisonOperator;
|
||||
export type TExtendedSupportedOperators = (typeof EXTENDED_OPERATORS)[keyof typeof EXTENDED_OPERATORS];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { TIssue } from "./issues/issue";
|
||||
import { LOGICAL_OPERATOR, TSupportedOperators } from "./rich-filters";
|
||||
import { CompleteOrEmpty } from "./utils";
|
||||
|
||||
export type TIssueLayouts = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt_chart";
|
||||
|
||||
|
|
@ -47,7 +49,7 @@ export type TIssueOrderByOptions =
|
|||
| "sub_issues_count"
|
||||
| "-sub_issues_count";
|
||||
|
||||
export type TIssueGroupingFilters = "active" | "backlog" | null;
|
||||
export type TIssueGroupingFilters = "active" | "backlog";
|
||||
|
||||
export type TIssueExtraOptions = "show_empty_groups" | "sub_issue";
|
||||
|
||||
|
|
@ -76,10 +78,47 @@ export type TIssueParams =
|
|||
| "per_page"
|
||||
| "issue_type"
|
||||
| "layout"
|
||||
| "expand";
|
||||
| "expand"
|
||||
| "filters";
|
||||
|
||||
export type TCalendarLayouts = "month" | "week";
|
||||
|
||||
/**
|
||||
* Keys for the work item filter properties
|
||||
*/
|
||||
export const WORK_ITEM_FILTER_PROPERTY_KEYS = [
|
||||
"state_group",
|
||||
"priority",
|
||||
"start_date",
|
||||
"target_date",
|
||||
"assignee_id",
|
||||
"mention_id",
|
||||
"created_by_id",
|
||||
"subscriber_id",
|
||||
"label_id",
|
||||
"state_id",
|
||||
"cycle_id",
|
||||
"module_id",
|
||||
"project_id",
|
||||
] as const;
|
||||
export type TWorkItemFilterProperty = (typeof WORK_ITEM_FILTER_PROPERTY_KEYS)[number];
|
||||
|
||||
export type TWorkItemFilterConditionKey = `${TWorkItemFilterProperty}__${TSupportedOperators}`;
|
||||
|
||||
export type TWorkItemFilterConditionData = Partial<{
|
||||
[K in TWorkItemFilterConditionKey]: string;
|
||||
}>;
|
||||
|
||||
export type TWorkItemFilterAndGroup = {
|
||||
[LOGICAL_OPERATOR.AND]: TWorkItemFilterConditionData[];
|
||||
};
|
||||
|
||||
export type TWorkItemFilterGroup = TWorkItemFilterAndGroup;
|
||||
|
||||
export type TWorkItemFilterExpressionData = TWorkItemFilterConditionData | TWorkItemFilterGroup;
|
||||
|
||||
export type TWorkItemFilterExpression = CompleteOrEmpty<TWorkItemFilterExpressionData>;
|
||||
|
||||
export interface IIssueFilterOptions {
|
||||
assignees?: string[] | null;
|
||||
mentions?: string[] | null;
|
||||
|
|
@ -109,7 +148,6 @@ export interface IIssueDisplayFilterOptions {
|
|||
order_by?: TIssueOrderByOptions;
|
||||
show_empty_groups?: boolean;
|
||||
sub_issue?: boolean;
|
||||
type?: TIssueGroupingFilters;
|
||||
}
|
||||
export interface IIssueDisplayProperties {
|
||||
assignee?: boolean;
|
||||
|
|
@ -136,14 +174,20 @@ export type TIssueKanbanFilters = {
|
|||
};
|
||||
|
||||
export interface IIssueFilters {
|
||||
filters: IIssueFilterOptions | undefined;
|
||||
richFilters: TWorkItemFilterExpression;
|
||||
displayFilters: IIssueDisplayFilterOptions | undefined;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
kanbanFilters: TIssueKanbanFilters | undefined;
|
||||
}
|
||||
|
||||
export interface IIssueFiltersResponse {
|
||||
export type TSupportedFilterForUpdate = IIssueDisplayFilterOptions | IIssueDisplayProperties | TIssueKanbanFilters;
|
||||
|
||||
export interface ISubWorkItemFilters extends Omit<IIssueFilters, "richFilters"> {
|
||||
filters: IIssueFilterOptions;
|
||||
}
|
||||
|
||||
export interface IIssueFiltersResponse {
|
||||
rich_filters: TWorkItemFilterExpression;
|
||||
display_filters: IIssueDisplayFilterOptions;
|
||||
display_properties: IIssueDisplayProperties;
|
||||
}
|
||||
|
|
@ -172,17 +216,16 @@ export interface IWorkspaceViewIssuesParams {
|
|||
target_date?: string | undefined;
|
||||
project?: string | undefined;
|
||||
order_by?: string | undefined;
|
||||
type?: "active" | "backlog" | undefined;
|
||||
sub_issue?: boolean;
|
||||
}
|
||||
|
||||
export interface IProjectViewProps {
|
||||
rich_filters: TWorkItemFilterExpression;
|
||||
display_filters: IIssueDisplayFilterOptions | undefined;
|
||||
filters: IIssueFilterOptions;
|
||||
}
|
||||
|
||||
export interface IWorkspaceViewProps {
|
||||
filters: IIssueFilterOptions;
|
||||
rich_filters: TWorkItemFilterExpression;
|
||||
display_filters: IIssueDisplayFilterOptions | undefined;
|
||||
display_properties: IIssueDisplayProperties;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
import { TLogoProps } from "./common";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOptions } from "./view-props";
|
||||
import {
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
TWorkItemFilterExpression,
|
||||
} from "./view-props";
|
||||
|
||||
export enum EViewAccess {
|
||||
PRIVATE,
|
||||
|
|
@ -16,7 +21,7 @@ export interface IProjectView {
|
|||
updated_by: string;
|
||||
name: string;
|
||||
description: string;
|
||||
filters: IIssueFilterOptions;
|
||||
rich_filters: TWorkItemFilterExpression;
|
||||
display_filters: IIssueDisplayFilterOptions;
|
||||
display_properties: IIssueDisplayProperties;
|
||||
query: IIssueFilterOptions;
|
||||
|
|
@ -29,6 +34,10 @@ export interface IProjectView {
|
|||
owned_by: string;
|
||||
}
|
||||
|
||||
export interface IPublishedProjectView extends Omit<IProjectView, "rich_filters"> {
|
||||
filters: IIssueFilterOptions;
|
||||
}
|
||||
|
||||
export type TPublishViewSettings = {
|
||||
is_comments_enabled: boolean;
|
||||
is_reactions_enabled: boolean;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
IWorkspaceViewProps,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IIssueFilterOptions,
|
||||
TWorkItemFilterExpression,
|
||||
} from "./view-props";
|
||||
import { EViewAccess } from "./views";
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export interface IWorkspaceView {
|
|||
updated_by: string;
|
||||
name: string;
|
||||
description: string;
|
||||
filters: IIssueFilterOptions;
|
||||
rich_filters: TWorkItemFilterExpression;
|
||||
display_filters: IIssueDisplayFilterOptions;
|
||||
display_properties: IIssueDisplayProperties;
|
||||
query: any;
|
||||
|
|
@ -32,4 +32,6 @@ export interface IWorkspaceView {
|
|||
};
|
||||
}
|
||||
|
||||
export type TStaticViewTypes = "all-issues" | "assigned" | "created" | "subscribed";
|
||||
export const STATIC_VIEW_TYPES = ["all-issues", "assigned", "created", "subscribed"];
|
||||
|
||||
export type TStaticViewTypes = (typeof STATIC_VIEW_TYPES)[number];
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { differenceInCalendarDays } from "date-fns/differenceInCalendarDays";
|
||||
// plane imports
|
||||
import { IIssueFilters } from "@plane/types";
|
||||
// local imports
|
||||
import { getDate } from "./datetime";
|
||||
|
||||
|
|
@ -63,17 +61,3 @@ export const satisfiesDateFilter = (date: Date, filter: string): boolean => {
|
|||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description checks if the issue filter is active
|
||||
* @param {IIssueFilters} issueFilters
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isIssueFilterActive = (issueFilters: IIssueFilters | undefined): boolean => {
|
||||
if (!issueFilters) return false;
|
||||
|
||||
const issueType = issueFilters?.displayFilters?.type;
|
||||
const isFiltersApplied = calculateTotalFilters(issueFilters?.filters ?? {}) !== 0 || !!issueType;
|
||||
|
||||
return isFiltersApplied;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -28,5 +28,6 @@ export * from "./subscription";
|
|||
export * from "./tab-indices";
|
||||
export * from "./theme";
|
||||
export * from "./url";
|
||||
export * from "./work-item-filters";
|
||||
export * from "./work-item";
|
||||
export * from "./workspace";
|
||||
|
|
|
|||
|
|
@ -1,30 +1,7 @@
|
|||
// plane imports
|
||||
import {
|
||||
FILTER_FIELD_TYPE,
|
||||
TFilterValue,
|
||||
TFilterProperty,
|
||||
TFilterConfig,
|
||||
TSupportedOperators,
|
||||
TBaseFilterFieldConfig,
|
||||
} from "@plane/types";
|
||||
import { FILTER_FIELD_TYPE, TFilterValue, TSupportedOperators, TBaseFilterFieldConfig } from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createFilterFieldConfig,
|
||||
DEFAULT_DATE_FILTER_TYPE_CONFIG,
|
||||
DEFAULT_DATE_RANGE_FILTER_TYPE_CONFIG,
|
||||
DEFAULT_MULTI_SELECT_FILTER_TYPE_CONFIG,
|
||||
DEFAULT_SINGLE_SELECT_FILTER_TYPE_CONFIG,
|
||||
IFilterIconConfig,
|
||||
} from "./shared";
|
||||
|
||||
/**
|
||||
* Helper to create a type-safe filter config
|
||||
* @param config - The filter config to create
|
||||
* @returns The created filter config
|
||||
*/
|
||||
export const createFilterConfig = <P extends TFilterProperty, V extends TFilterValue>(
|
||||
config: TFilterConfig<P, V>
|
||||
): TFilterConfig<P, V> => config;
|
||||
import { createFilterFieldConfig, IFilterIconConfig } from "./shared";
|
||||
|
||||
// ------------ Selection filters ------------
|
||||
|
||||
|
|
@ -59,12 +36,11 @@ export const getSingleSelectConfig = <
|
|||
TIconData extends string | number | boolean | object | undefined = undefined,
|
||||
>(
|
||||
transforms: TOptionTransforms<TItem, TValue, TIconData>,
|
||||
config?: TSingleSelectConfig<TValue>,
|
||||
config: TSingleSelectConfig<TValue>,
|
||||
iconConfig?: IFilterIconConfig<TIconData>
|
||||
) =>
|
||||
createFilterFieldConfig<typeof FILTER_FIELD_TYPE.SINGLE_SELECT, TValue>({
|
||||
type: FILTER_FIELD_TYPE.SINGLE_SELECT,
|
||||
...DEFAULT_SINGLE_SELECT_FILTER_TYPE_CONFIG,
|
||||
...config,
|
||||
getOptions: () =>
|
||||
transforms.items.map((item) => ({
|
||||
|
|
@ -101,7 +77,6 @@ export const getMultiSelectConfig = <
|
|||
) =>
|
||||
createFilterFieldConfig<typeof FILTER_FIELD_TYPE.MULTI_SELECT, TValue>({
|
||||
type: FILTER_FIELD_TYPE.MULTI_SELECT,
|
||||
...DEFAULT_MULTI_SELECT_FILTER_TYPE_CONFIG,
|
||||
...config,
|
||||
operatorLabel: config?.operatorLabel,
|
||||
getOptions: () =>
|
||||
|
|
@ -136,10 +111,9 @@ export type TDateRangeConfig = TBaseFilterFieldConfig & {
|
|||
* @param config - Date-specific configuration
|
||||
* @returns The date picker config
|
||||
*/
|
||||
export const getDatePickerConfig = (config?: TDateConfig) =>
|
||||
export const getDatePickerConfig = (config: TDateConfig) =>
|
||||
createFilterFieldConfig<typeof FILTER_FIELD_TYPE.DATE, Date>({
|
||||
type: FILTER_FIELD_TYPE.DATE,
|
||||
...DEFAULT_DATE_FILTER_TYPE_CONFIG,
|
||||
...config,
|
||||
});
|
||||
|
||||
|
|
@ -148,9 +122,8 @@ export const getDatePickerConfig = (config?: TDateConfig) =>
|
|||
* @param config - Date range-specific configuration
|
||||
* @returns The date range picker config
|
||||
*/
|
||||
export const getDateRangePickerConfig = (config?: TDateRangeConfig) =>
|
||||
export const getDateRangePickerConfig = (config: TDateRangeConfig) =>
|
||||
createFilterFieldConfig<typeof FILTER_FIELD_TYPE.DATE_RANGE, Date>({
|
||||
type: FILTER_FIELD_TYPE.DATE_RANGE,
|
||||
...DEFAULT_DATE_RANGE_FILTER_TYPE_CONFIG,
|
||||
...config,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,8 +10,59 @@ import {
|
|||
TMultiSelectFilterFieldConfig,
|
||||
TSingleSelectFilterFieldConfig,
|
||||
TSupportedFilterFieldConfigs,
|
||||
TSupportedOperators,
|
||||
} from "@plane/types";
|
||||
|
||||
/**
|
||||
* Helper to create a type-safe filter config
|
||||
* @param config - The filter config to create
|
||||
* @returns The created filter config
|
||||
*/
|
||||
export const createFilterConfig = <P extends TFilterProperty, V extends TFilterValue>(
|
||||
config: TFilterConfig<P, V>
|
||||
): TFilterConfig<P, V> => config;
|
||||
|
||||
/**
|
||||
* Base parameters for filter type config factory functions.
|
||||
* - operator: The operator to use for the filter.
|
||||
*/
|
||||
export type TCreateFilterConfigParams = Omit<TBaseFilterFieldConfig, "isOperatorEnabled"> & {
|
||||
isEnabled: boolean;
|
||||
allowedOperators: Set<TSupportedOperators>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Icon configuration for filters and their options.
|
||||
* - filterIcon: Optional icon for the filter
|
||||
* - getOptionIcon: Function to get icon for specific option values
|
||||
*/
|
||||
export interface IFilterIconConfig<T extends string | number | boolean | object | undefined = undefined> {
|
||||
filterIcon?: React.FC<React.SVGAttributes<SVGElement>>;
|
||||
getOptionIcon?: (value: T) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Date filter config params
|
||||
*/
|
||||
export type TCreateDateFilterParams = TCreateFilterConfigParams & IFilterIconConfig<Date>;
|
||||
|
||||
/**
|
||||
* Helper to create an operator entry for the supported operators map.
|
||||
* This ensures consistency between the operator key and the operator passed to the config function.
|
||||
* @param operator - The operator to use as both key and parameter
|
||||
* @param createParams - The base filter configuration parameters
|
||||
* @param configFn - Function that creates the operator config using base configuration
|
||||
* @returns A tuple of operator and its config
|
||||
*/
|
||||
export const createOperatorConfigEntry = <T, P extends TCreateFilterConfigParams>(
|
||||
operator: TSupportedOperators,
|
||||
createParams: P,
|
||||
configFn: (updatedParams: P) => T
|
||||
): [TSupportedOperators, T] => [
|
||||
operator,
|
||||
configFn({ isOperatorEnabled: createParams.allowedOperators.has(operator), ...createParams }),
|
||||
];
|
||||
|
||||
/**
|
||||
* Factory function signature for creating filter configurations.
|
||||
*/
|
||||
|
|
@ -33,44 +84,3 @@ export const createFilterFieldConfig = <T extends TFilterFieldType, V extends TF
|
|||
? TDateRangeFilterFieldConfig<V>
|
||||
: never
|
||||
): TSupportedFilterFieldConfigs<V> => config as TSupportedFilterFieldConfigs<V>;
|
||||
|
||||
/**
|
||||
* Base parameters for filter type config factory functions.
|
||||
* - operator: The operator to use for the filter.
|
||||
*/
|
||||
export type TCreateFilterConfigParams = TBaseFilterFieldConfig & {
|
||||
isEnabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Icon configuration for filters and their options.
|
||||
* - filterIcon: Optional icon for the filter
|
||||
* - getOptionIcon: Function to get icon for specific option values
|
||||
*/
|
||||
export interface IFilterIconConfig<T extends string | number | boolean | object | undefined = undefined> {
|
||||
filterIcon?: React.FC<React.SVGAttributes<SVGElement>>;
|
||||
getOptionIcon?: (value: T) => React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Date filter config params
|
||||
*/
|
||||
export type TCreateDateFilterParams = TCreateFilterConfigParams & IFilterIconConfig<Date>;
|
||||
|
||||
// ------------ Default filter type configs ------------
|
||||
|
||||
export const DEFAULT_SINGLE_SELECT_FILTER_TYPE_CONFIG = {
|
||||
allowNegative: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_MULTI_SELECT_FILTER_TYPE_CONFIG = {
|
||||
allowNegative: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_DATE_FILTER_TYPE_CONFIG = {
|
||||
allowNegative: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_DATE_RANGE_FILTER_TYPE_CONFIG = {
|
||||
allowNegative: false,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// plane imports
|
||||
import {
|
||||
EQUALITY_OPERATOR,
|
||||
ICycle,
|
||||
TCycleGroups,
|
||||
TFilterProperty,
|
||||
COLLECTION_OPERATOR,
|
||||
TSupportedOperators,
|
||||
} from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createFilterConfig,
|
||||
TCreateFilterConfigParams,
|
||||
IFilterIconConfig,
|
||||
TCreateFilterConfig,
|
||||
getMultiSelectConfig,
|
||||
createOperatorConfigEntry,
|
||||
} from "../../../rich-filters";
|
||||
|
||||
/**
|
||||
* Cycle filter specific params
|
||||
*/
|
||||
export type TCreateCycleFilterParams = TCreateFilterConfigParams &
|
||||
IFilterIconConfig<TCycleGroups> & {
|
||||
cycles: ICycle[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to get the cycle multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The cycle multi select config
|
||||
*/
|
||||
export const getCycleMultiSelectConfig = (params: TCreateCycleFilterParams, singleValueOperator: TSupportedOperators) =>
|
||||
getMultiSelectConfig<ICycle, string, TCycleGroups>(
|
||||
{
|
||||
items: params.cycles,
|
||||
getId: (cycle) => cycle.id,
|
||||
getLabel: (cycle) => cycle.name,
|
||||
getValue: (cycle) => cycle.id,
|
||||
getIconData: (cycle) => cycle.status || "draft",
|
||||
},
|
||||
{
|
||||
singleValueOperator,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
...params,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the cycle filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the cycle filter config
|
||||
*/
|
||||
export const getCycleFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateCycleFilterParams> =>
|
||||
(params: TCreateCycleFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Cycle",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getCycleMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
|
||||
),
|
||||
]),
|
||||
});
|
||||
43
packages/utils/src/work-item-filters/configs/filters/date.ts
Normal file
43
packages/utils/src/work-item-filters/configs/filters/date.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// plane imports
|
||||
import { TFilterProperty } from "@plane/types";
|
||||
// local imports
|
||||
import { createFilterConfig, TCreateFilterConfig, TCreateDateFilterParams } from "../../../rich-filters";
|
||||
import { getSupportedDateOperators } from "./shared";
|
||||
|
||||
// ------------ Date filters ------------
|
||||
|
||||
/**
|
||||
* Get the start date filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the start date filter config
|
||||
*/
|
||||
export const getStartDateFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateDateFilterParams> =>
|
||||
(params: TCreateDateFilterParams) =>
|
||||
createFilterConfig<P, Date>({
|
||||
id: key,
|
||||
label: "Start date",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
allowMultipleFilters: true,
|
||||
supportedOperatorConfigsMap: getSupportedDateOperators(params),
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the target date filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the target date filter config
|
||||
*/
|
||||
export const getTargetDateFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateDateFilterParams> =>
|
||||
(params: TCreateDateFilterParams) =>
|
||||
createFilterConfig<P, Date>({
|
||||
id: key,
|
||||
label: "Target date",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
allowMultipleFilters: true,
|
||||
supportedOperatorConfigsMap: getSupportedDateOperators(params),
|
||||
});
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export * from "./cycle";
|
||||
export * from "./date";
|
||||
export * from "./label";
|
||||
export * from "./module";
|
||||
export * from "./priority";
|
||||
export * from "./project";
|
||||
export * from "./state";
|
||||
export * from "./user";
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// plane imports
|
||||
import {
|
||||
EQUALITY_OPERATOR,
|
||||
IIssueLabel,
|
||||
TFilterProperty,
|
||||
COLLECTION_OPERATOR,
|
||||
TSupportedOperators,
|
||||
} from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createFilterConfig,
|
||||
TCreateFilterConfigParams,
|
||||
IFilterIconConfig,
|
||||
TCreateFilterConfig,
|
||||
getMultiSelectConfig,
|
||||
createOperatorConfigEntry,
|
||||
} from "../../../rich-filters";
|
||||
|
||||
/**
|
||||
* Label filter specific params
|
||||
*/
|
||||
export type TCreateLabelFilterParams = TCreateFilterConfigParams &
|
||||
IFilterIconConfig<string> & {
|
||||
labels: IIssueLabel[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to get the label multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The label multi select config
|
||||
*/
|
||||
export const getLabelMultiSelectConfig = (params: TCreateLabelFilterParams, singleValueOperator: TSupportedOperators) =>
|
||||
getMultiSelectConfig<IIssueLabel, string, string>(
|
||||
{
|
||||
items: params.labels,
|
||||
getId: (label) => label.id,
|
||||
getLabel: (label) => label.name,
|
||||
getValue: (label) => label.id,
|
||||
getIconData: (label) => label.color,
|
||||
},
|
||||
{
|
||||
singleValueOperator,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
getOptionIcon: params.getOptionIcon,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the label filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the label filter config
|
||||
*/
|
||||
export const getLabelFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateLabelFilterParams> =>
|
||||
(params: TCreateLabelFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Label",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getLabelMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// plane imports
|
||||
import { EQUALITY_OPERATOR, IModule, TFilterProperty, COLLECTION_OPERATOR } from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createFilterConfig,
|
||||
TCreateFilterConfigParams,
|
||||
IFilterIconConfig,
|
||||
TCreateFilterConfig,
|
||||
getMultiSelectConfig,
|
||||
createOperatorConfigEntry,
|
||||
} from "../../../rich-filters";
|
||||
|
||||
/**
|
||||
* Module filter specific params
|
||||
*/
|
||||
export type TCreateModuleFilterParams = TCreateFilterConfigParams &
|
||||
IFilterIconConfig<undefined> & {
|
||||
modules: IModule[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to get the module multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The module multi select config
|
||||
*/
|
||||
export const getModuleMultiSelectConfig = (params: TCreateModuleFilterParams) =>
|
||||
getMultiSelectConfig<IModule, string, undefined>(
|
||||
{
|
||||
items: params.modules,
|
||||
getId: (module) => module.id,
|
||||
getLabel: (module) => module.name,
|
||||
getValue: (module) => module.id,
|
||||
getIconData: () => undefined,
|
||||
},
|
||||
{
|
||||
singleValueOperator: EQUALITY_OPERATOR.EXACT,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
...params,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the module filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the module filter config
|
||||
*/
|
||||
export const getModuleFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateModuleFilterParams> =>
|
||||
(params: TCreateModuleFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Module",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getModuleMultiSelectConfig(updatedParams)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// plane imports
|
||||
import { ISSUE_PRIORITIES, TIssuePriorities } from "@plane/constants";
|
||||
import { EQUALITY_OPERATOR, TFilterProperty, COLLECTION_OPERATOR, TSupportedOperators } from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createFilterConfig,
|
||||
TCreateFilterConfigParams,
|
||||
IFilterIconConfig,
|
||||
TCreateFilterConfig,
|
||||
getMultiSelectConfig,
|
||||
createOperatorConfigEntry,
|
||||
} from "../../../rich-filters";
|
||||
|
||||
// ------------ Priority filter ------------
|
||||
|
||||
/**
|
||||
* Priority filter specific params
|
||||
*/
|
||||
export type TCreatePriorityFilterParams = TCreateFilterConfigParams & IFilterIconConfig<TIssuePriorities>;
|
||||
|
||||
/**
|
||||
* Helper to get the priority multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The priority multi select config
|
||||
*/
|
||||
export const getPriorityMultiSelectConfig = (
|
||||
params: TCreatePriorityFilterParams,
|
||||
singleValueOperator: TSupportedOperators
|
||||
) =>
|
||||
getMultiSelectConfig<{ key: TIssuePriorities; title: string }, TIssuePriorities, TIssuePriorities>(
|
||||
{
|
||||
items: ISSUE_PRIORITIES,
|
||||
getId: (priority) => priority.key,
|
||||
getLabel: (priority) => priority.title,
|
||||
getValue: (priority) => priority.key,
|
||||
getIconData: (priority) => priority.key,
|
||||
},
|
||||
{
|
||||
singleValueOperator,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
getOptionIcon: params.getOptionIcon,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the priority filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the priority filter config
|
||||
*/
|
||||
export const getPriorityFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreatePriorityFilterParams> =>
|
||||
(params: TCreatePriorityFilterParams) =>
|
||||
createFilterConfig<P, TIssuePriorities>({
|
||||
id: key,
|
||||
label: "Priority",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getPriorityMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
// plane imports
|
||||
import { EQUALITY_OPERATOR, TFilterProperty, COLLECTION_OPERATOR } from "@plane/types";
|
||||
// local imports
|
||||
import { createFilterConfig, createOperatorConfigEntry, TCreateFilterConfig } from "../../../rich-filters";
|
||||
import { getProjectMultiSelectConfig, TCreateProjectFilterParams } from "./shared";
|
||||
|
||||
// ------------ Project filter ------------
|
||||
|
||||
/**
|
||||
* Get the project filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the project filter config
|
||||
*/
|
||||
export const getProjectFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateProjectFilterParams> =>
|
||||
(params: TCreateProjectFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Projects",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getProjectMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// plane imports
|
||||
import {
|
||||
COMPARISON_OPERATOR,
|
||||
EQUALITY_OPERATOR,
|
||||
IProject,
|
||||
TOperatorConfigMap,
|
||||
TSupportedOperators,
|
||||
} from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createOperatorConfigEntry,
|
||||
getDatePickerConfig,
|
||||
getDateRangePickerConfig,
|
||||
getMultiSelectConfig,
|
||||
IFilterIconConfig,
|
||||
TCreateDateFilterParams,
|
||||
TCreateFilterConfigParams,
|
||||
} from "../../../rich-filters";
|
||||
|
||||
// ------------ Date filter ------------
|
||||
|
||||
export const getSupportedDateOperators = (params: TCreateDateFilterParams): TOperatorConfigMap<Date> =>
|
||||
new Map([
|
||||
createOperatorConfigEntry(EQUALITY_OPERATOR.EXACT, params, (updatedParams) => getDatePickerConfig(updatedParams)),
|
||||
createOperatorConfigEntry(COMPARISON_OPERATOR.RANGE, params, (updatedParams) =>
|
||||
getDateRangePickerConfig(updatedParams)
|
||||
),
|
||||
]);
|
||||
|
||||
// ------------ Project filter ------------
|
||||
|
||||
/**
|
||||
* Project filter specific params
|
||||
*/
|
||||
export type TCreateProjectFilterParams = TCreateFilterConfigParams &
|
||||
IFilterIconConfig<IProject> & {
|
||||
projects: IProject[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to get the project multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The member multi select config
|
||||
*/
|
||||
export const getProjectMultiSelectConfig = (
|
||||
params: TCreateProjectFilterParams,
|
||||
singleValueOperator: TSupportedOperators
|
||||
) =>
|
||||
getMultiSelectConfig<IProject, string, IProject>(
|
||||
{
|
||||
items: params.projects,
|
||||
getId: (project) => project.id,
|
||||
getLabel: (project) => project.name,
|
||||
getValue: (project) => project.id,
|
||||
getIconData: (project) => project,
|
||||
},
|
||||
{
|
||||
singleValueOperator,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
...params,
|
||||
}
|
||||
);
|
||||
127
packages/utils/src/work-item-filters/configs/filters/state.ts
Normal file
127
packages/utils/src/work-item-filters/configs/filters/state.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// plane imports
|
||||
import { STATE_GROUPS } from "@plane/constants";
|
||||
import {
|
||||
COLLECTION_OPERATOR,
|
||||
EQUALITY_OPERATOR,
|
||||
IState,
|
||||
TFilterProperty,
|
||||
TStateGroups,
|
||||
TSupportedOperators,
|
||||
} from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createFilterConfig,
|
||||
getMultiSelectConfig,
|
||||
IFilterIconConfig,
|
||||
TCreateFilterConfig,
|
||||
TCreateFilterConfigParams,
|
||||
createOperatorConfigEntry,
|
||||
} from "../../../rich-filters";
|
||||
|
||||
// ------------ State group filter ------------
|
||||
|
||||
/**
|
||||
* State group filter specific params
|
||||
*/
|
||||
export type TCreateStateGroupFilterParams = TCreateFilterConfigParams & IFilterIconConfig<TStateGroups>;
|
||||
|
||||
/**
|
||||
* Helper to get the state group multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The state group multi select config
|
||||
*/
|
||||
export const getStateGroupMultiSelectConfig = (
|
||||
params: TCreateStateGroupFilterParams,
|
||||
singleValueOperator: TSupportedOperators
|
||||
) =>
|
||||
getMultiSelectConfig<{ key: TStateGroups; label: string }, TStateGroups, TStateGroups>(
|
||||
{
|
||||
items: Object.values(STATE_GROUPS),
|
||||
getId: (state) => state.key,
|
||||
getLabel: (state) => state.label,
|
||||
getValue: (state) => state.key,
|
||||
getIconData: (state) => state.key,
|
||||
},
|
||||
{
|
||||
singleValueOperator,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
...params,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the state group filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the state group filter config
|
||||
*/
|
||||
export const getStateGroupFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateStateGroupFilterParams> =>
|
||||
(params: TCreateStateGroupFilterParams) =>
|
||||
createFilterConfig<P, TStateGroups>({
|
||||
id: key,
|
||||
label: "State Group",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getStateGroupMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
||||
// ------------ State filter ------------
|
||||
|
||||
/**
|
||||
* State filter specific params
|
||||
*/
|
||||
export type TCreateStateFilterParams = TCreateFilterConfigParams &
|
||||
IFilterIconConfig<IState> & {
|
||||
states: IState[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to get the state multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The state multi select config
|
||||
*/
|
||||
export const getStateMultiSelectConfig = (params: TCreateStateFilterParams) =>
|
||||
getMultiSelectConfig<IState, string, IState>(
|
||||
{
|
||||
items: params.states,
|
||||
getId: (state) => state.id,
|
||||
getLabel: (state) => state.name,
|
||||
getValue: (state) => state.id,
|
||||
getIconData: (state) => state,
|
||||
},
|
||||
{
|
||||
singleValueOperator: EQUALITY_OPERATOR.EXACT,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
...params,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the state filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the state filter config
|
||||
*/
|
||||
export const getStateFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateStateFilterParams> =>
|
||||
(params: TCreateStateFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "State",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getStateMultiSelectConfig(updatedParams)
|
||||
),
|
||||
]),
|
||||
});
|
||||
156
packages/utils/src/work-item-filters/configs/filters/user.ts
Normal file
156
packages/utils/src/work-item-filters/configs/filters/user.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
// plane imports
|
||||
import { EQUALITY_OPERATOR, IUserLite, TFilterProperty, COLLECTION_OPERATOR } from "@plane/types";
|
||||
// local imports
|
||||
import {
|
||||
createFilterConfig,
|
||||
TCreateFilterConfigParams,
|
||||
IFilterIconConfig,
|
||||
TCreateFilterConfig,
|
||||
getMultiSelectConfig,
|
||||
createOperatorConfigEntry,
|
||||
} from "../../../rich-filters";
|
||||
|
||||
// ------------ Base User Filter Types ------------
|
||||
|
||||
/**
|
||||
* User filter specific params
|
||||
*/
|
||||
export type TCreateUserFilterParams = TCreateFilterConfigParams &
|
||||
IFilterIconConfig<IUserLite> & {
|
||||
members: IUserLite[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to get the member multi select config
|
||||
* @param params - The filter params
|
||||
* @returns The member multi select config
|
||||
*/
|
||||
export const getMemberMultiSelectConfig = (params: TCreateUserFilterParams) =>
|
||||
getMultiSelectConfig<IUserLite, string, IUserLite>(
|
||||
{
|
||||
items: params.members,
|
||||
getId: (member) => member.id,
|
||||
getLabel: (member) => member.display_name,
|
||||
getValue: (member) => member.id,
|
||||
getIconData: (member) => member,
|
||||
},
|
||||
{
|
||||
singleValueOperator: EQUALITY_OPERATOR.EXACT,
|
||||
...params,
|
||||
},
|
||||
{
|
||||
...params,
|
||||
}
|
||||
);
|
||||
|
||||
// ------------ Assignee filter ------------
|
||||
|
||||
/**
|
||||
* Assignee filter specific params
|
||||
*/
|
||||
export type TCreateAssigneeFilterParams = TCreateUserFilterParams;
|
||||
|
||||
/**
|
||||
* Get the assignee filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the assignee filter config
|
||||
*/
|
||||
export const getAssigneeFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateAssigneeFilterParams> =>
|
||||
(params: TCreateAssigneeFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Assignees",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getMemberMultiSelectConfig(updatedParams)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
||||
// ------------ Mention filter ------------
|
||||
|
||||
/**
|
||||
* Mention filter specific params
|
||||
*/
|
||||
export type TCreateMentionFilterParams = TCreateUserFilterParams;
|
||||
|
||||
/**
|
||||
* Get the mention filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the mention filter config
|
||||
*/
|
||||
export const getMentionFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateMentionFilterParams> =>
|
||||
(params: TCreateMentionFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Mentions",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getMemberMultiSelectConfig(updatedParams)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
||||
// ------------ Created by filter ------------
|
||||
|
||||
/**
|
||||
* Created by filter specific params
|
||||
*/
|
||||
export type TCreateCreatedByFilterParams = TCreateUserFilterParams;
|
||||
|
||||
/**
|
||||
* Get the created by filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the created by filter config
|
||||
*/
|
||||
export const getCreatedByFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateCreatedByFilterParams> =>
|
||||
(params: TCreateCreatedByFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Created by",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getMemberMultiSelectConfig(updatedParams)
|
||||
),
|
||||
]),
|
||||
});
|
||||
|
||||
// ------------ Subscriber filter ------------
|
||||
|
||||
/**
|
||||
* Subscriber filter specific params
|
||||
*/
|
||||
export type TCreateSubscriberFilterParams = TCreateUserFilterParams;
|
||||
|
||||
/**
|
||||
* Get the subscriber filter config
|
||||
* @template K - The filter key
|
||||
* @param key - The filter key to use
|
||||
* @returns A function that takes parameters and returns the subscriber filter config
|
||||
*/
|
||||
export const getSubscriberFilterConfig =
|
||||
<P extends TFilterProperty>(key: P): TCreateFilterConfig<P, TCreateSubscriberFilterParams> =>
|
||||
(params: TCreateSubscriberFilterParams) =>
|
||||
createFilterConfig<P, string>({
|
||||
id: key,
|
||||
label: "Subscriber",
|
||||
icon: params.filterIcon,
|
||||
isEnabled: params.isEnabled,
|
||||
supportedOperatorConfigsMap: new Map([
|
||||
createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
|
||||
getMemberMultiSelectConfig(updatedParams)
|
||||
),
|
||||
]),
|
||||
});
|
||||
1
packages/utils/src/work-item-filters/configs/index.ts
Normal file
1
packages/utils/src/work-item-filters/configs/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./filters";
|
||||
1
packages/utils/src/work-item-filters/index.ts
Normal file
1
packages/utils/src/work-item-filters/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./configs";
|
||||
|
|
@ -4,15 +4,16 @@ import { v4 as uuidv4 } from "uuid";
|
|||
// plane imports
|
||||
import {
|
||||
ISSUE_DISPLAY_FILTERS_BY_PAGE,
|
||||
STATE_GROUPS,
|
||||
TIssuePriorities,
|
||||
ISSUE_PRIORITY_FILTERS,
|
||||
STATE_GROUPS,
|
||||
TIssueFilterPriorityObject,
|
||||
TIssuePriorities,
|
||||
} from "@plane/constants";
|
||||
import {
|
||||
EIssueLayoutTypes,
|
||||
IGanttBlock,
|
||||
IIssueDisplayFilterOptions,
|
||||
IIssueDisplayProperties,
|
||||
IGanttBlock,
|
||||
TGroupedIssues,
|
||||
TIssue,
|
||||
TIssueGroupByOptions,
|
||||
|
|
@ -21,7 +22,6 @@ import {
|
|||
TStateGroups,
|
||||
TSubGroupedIssues,
|
||||
TUnGroupedIssues,
|
||||
EIssueLayoutTypes,
|
||||
} from "@plane/types";
|
||||
// local imports
|
||||
import { orderArrayBy } from "../array";
|
||||
|
|
@ -111,25 +111,20 @@ export const handleIssueQueryParamsByLayout = (
|
|||
| "team_issues"
|
||||
| "team_project_work_items"
|
||||
): TIssueParams[] | null => {
|
||||
const queryParams: TIssueParams[] = [];
|
||||
const queryParams: TIssueParams[] = ["filters"];
|
||||
|
||||
if (!layout) return null;
|
||||
|
||||
const layoutOptions = ISSUE_DISPLAY_FILTERS_BY_PAGE[viewType][layout];
|
||||
|
||||
// add filters query params
|
||||
layoutOptions.filters.forEach((option) => {
|
||||
queryParams.push(option);
|
||||
});
|
||||
const currentViewLayoutOptions = ISSUE_DISPLAY_FILTERS_BY_PAGE[viewType].layoutOptions[layout];
|
||||
|
||||
// add display filters query params
|
||||
Object.keys(layoutOptions.display_filters).forEach((option) => {
|
||||
Object.keys(currentViewLayoutOptions.display_filters).forEach((option) => {
|
||||
queryParams.push(option as TIssueParams);
|
||||
});
|
||||
|
||||
// add extra options query params
|
||||
if (layoutOptions.extra_options.access) {
|
||||
layoutOptions.extra_options.values.forEach((option) => {
|
||||
if (currentViewLayoutOptions.extra_options.access) {
|
||||
currentViewLayoutOptions.extra_options.values.forEach((option) => {
|
||||
queryParams.push(option);
|
||||
});
|
||||
}
|
||||
|
|
@ -286,7 +281,6 @@ export const getComputedDisplayFilters = (
|
|||
order_by: filters?.order_by || "sort_order",
|
||||
group_by: filters?.group_by || null,
|
||||
sub_group_by: filters?.sub_group_by || null,
|
||||
type: filters?.type || null,
|
||||
sub_issue: filters?.sub_issue || false,
|
||||
show_empty_groups: filters?.show_empty_groups || false,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue