][];
+ allEnabledSupportedOperators: TSupportedOperators[];
firstOperator: TSupportedOperators | undefined;
// computed functions
getOperatorConfig: (
@@ -76,8 +75,7 @@ export class FilterConfig["allSupportedOperators"] {
- return Array.from(this.supportedOperatorConfigsMap.keys());
- }
-
- /**
- * Returns all supported operator configs.
- * @returns All supported operator configs.
- */
- get allSupportedOperatorConfigs(): IFilterConfig
["allSupportedOperatorConfigs"] {
- return Array.from(this.supportedOperatorConfigsMap.values());
+ get allEnabledSupportedOperators(): IFilterConfig
["allEnabledSupportedOperators"] {
+ return Array.from(this.supportedOperatorConfigsMap.entries())
+ .filter(([, operatorConfig]) => operatorConfig.isOperatorEnabled)
+ .map(([operator]) => operator);
}
/**
@@ -107,7 +99,7 @@ export class FilterConfig
["firstOperator"] {
- return this.allSupportedOperators[0];
+ return this.allEnabledSupportedOperators[0];
}
// ------------ computed functions ------------
@@ -168,7 +160,7 @@ export class FilterConfig
{
+ /**
+ * 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 | 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 {
+ 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): 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
+ ): 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] | 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 => {
+ 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
+ ): 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();
diff --git a/packages/shared-state/src/store/work-item-filters/filter.store.ts b/packages/shared-state/src/store/work-item-filters/filter.store.ts
new file mode 100644
index 000000000..6c43b7fbf
--- /dev/null
+++ b/packages/shared-state/src/store/work-item-filters/filter.store.ts
@@ -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;
+ initialExpression?: TWorkItemFilterExpression;
+ onExpressionChange?: (expression: TWorkItemFilterExpression) => void;
+};
+
+type TWorkItemFilterKey = `${EIssuesStoreType}-${string}`;
+
+export interface IWorkItemFilterStore {
+ filters: Map>; // key is the entity id (project, cycle, workspace, teamspace, etc)
+ getFilter: (
+ entityType: EIssuesStoreType,
+ entityId: string
+ ) => IFilterInstance | undefined;
+ getOrCreateFilter: (
+ params: TGetOrCreateFilterParams
+ ) => IFilterInstance;
+ resetExpression: (entityType: EIssuesStoreType, entityId: string, expression: TWorkItemFilterExpression) => void;
+ updateFilterExpressionFromConditions: (
+ entityType: EIssuesStoreType,
+ entityId: string,
+ conditions: TWorkItemFilterCondition[],
+ fallbackFn: (expression: TWorkItemFilterExpression) => Promise
+ ) => Promise;
+ 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>();
+ 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({
+ adapter: workItemFiltersAdapter,
+ initialExpression: params.initialExpression,
+ onExpressionChange: params.onExpressionChange,
+ options: {
+ expression: params.expressionOptions,
+ },
+ });
+}
diff --git a/packages/shared-state/src/store/work-item-filters/index.ts b/packages/shared-state/src/store/work-item-filters/index.ts
new file mode 100644
index 000000000..e4acf3b74
--- /dev/null
+++ b/packages/shared-state/src/store/work-item-filters/index.ts
@@ -0,0 +1,2 @@
+export * from "./adapter";
+export * from "./filter.store";
diff --git a/packages/shared-state/src/utils/index.ts b/packages/shared-state/src/utils/index.ts
index 42270deb7..38083592b 100644
--- a/packages/shared-state/src/utils/index.ts
+++ b/packages/shared-state/src/utils/index.ts
@@ -1 +1,2 @@
export * from "./rich-filter.helper";
+export * from "./work-item-filters.helper";
diff --git a/packages/shared-state/src/utils/work-item-filters.helper.ts b/packages/shared-state/src/utils/work-item-filters.helper.ts
new file mode 100644
index 000000000..8763e3f75
--- /dev/null
+++ b/packages/shared-state/src/utils/work-item-filters.helper.ts
@@ -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;
+
+/**
+ * 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,
+ "adapter"
+ >
+): TWorkItemFilterExpression | undefined => {
+ const workItemFilterExpression = buildTempFilterExpressionFromConditions({
+ ...params,
+ adapter: workItemFiltersAdapter,
+ });
+ if (!workItemFilterExpression) console.error("Failed to build work item filter expression from conditions");
+ return workItemFilterExpression;
+};
diff --git a/packages/types/src/issues.ts b/packages/types/src/issues.ts
index de64c3923..44bb09e75 100644
--- a/packages/types/src/issues.ts
+++ b/packages/types/src/issues.ts
@@ -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[];
diff --git a/packages/types/src/rich-filters/field-types/shared.ts b/packages/types/src/rich-filters/field-types/shared.ts
index 0163e9743..8626837f1 100644
--- a/packages/types/src/rich-filters/field-types/shared.ts
+++ b/packages/types/src/rich-filters/field-types/shared.ts
@@ -13,6 +13,7 @@ export type TNegativeOperatorConfig = { allowNegative: true; negOperatorLabel?:
* - negativeOperatorConfig: Configuration for negative operators
*/
export type TBaseFilterFieldConfig = {
+ isOperatorEnabled?: boolean;
operatorLabel?: string;
} & TNegativeOperatorConfig;
diff --git a/packages/types/src/rich-filters/operators/core.ts b/packages/types/src/rich-filters/operators/core.ts
index 91c9adc28..573b1a0a3 100644
--- a/packages/types/src/rich-filters/operators/core.ts
+++ b/packages/types/src/rich-filters/operators/core.ts
@@ -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];
diff --git a/packages/types/src/rich-filters/operators/extended.ts b/packages/types/src/rich-filters/operators/extended.ts
index 56870326c..db54ec91e 100644
--- a/packages/types/src/rich-filters/operators/extended.ts
+++ b/packages/types/src/rich-filters/operators/extended.ts
@@ -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];
diff --git a/packages/types/src/view-props.ts b/packages/types/src/view-props.ts
index 2a6b9f22b..d073eafb9 100644
--- a/packages/types/src/view-props.ts
+++ b/packages/types/src/view-props.ts
@@ -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;
+
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 {
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;
}
diff --git a/packages/types/src/views.ts b/packages/types/src/views.ts
index 79af8b739..42fc3ef41 100644
--- a/packages/types/src/views.ts
+++ b/packages/types/src/views.ts
@@ -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 {
+ filters: IIssueFilterOptions;
+}
+
export type TPublishViewSettings = {
is_comments_enabled: boolean;
is_reactions_enabled: boolean;
diff --git a/packages/types/src/workspace-views.ts b/packages/types/src/workspace-views.ts
index 00c07aec5..f31cdc2bd 100644
--- a/packages/types/src/workspace-views.ts
+++ b/packages/types/src/workspace-views.ts
@@ -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];
diff --git a/packages/utils/src/filter.ts b/packages/utils/src/filter.ts
index 4052d3477..ee2ca5e33 100644
--- a/packages/utils/src/filter.ts
+++ b/packages/utils/src/filter.ts
@@ -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;
-};
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
index d411a69d2..ddd80a886 100644
--- a/packages/utils/src/index.ts
+++ b/packages/utils/src/index.ts
@@ -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";
diff --git a/packages/utils/src/rich-filters/factories/configs/core.ts b/packages/utils/src/rich-filters/factories/configs/core.ts
index a3450d2c0..51b16ff74 100644
--- a/packages/utils/src/rich-filters/factories/configs/core.ts
+++ b/packages/utils/src/rich-filters/factories/configs/core.ts
@@ -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 = (
- config: TFilterConfig
-): TFilterConfig
=> 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,
- config?: TSingleSelectConfig,
+ config: TSingleSelectConfig,
iconConfig?: IFilterIconConfig
) =>
createFilterFieldConfig({
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({
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({
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({
type: FILTER_FIELD_TYPE.DATE_RANGE,
- ...DEFAULT_DATE_RANGE_FILTER_TYPE_CONFIG,
...config,
});
diff --git a/packages/utils/src/rich-filters/factories/configs/shared.ts b/packages/utils/src/rich-filters/factories/configs/shared.ts
index 8647ea5d6..1c43e0a9d 100644
--- a/packages/utils/src/rich-filters/factories/configs/shared.ts
+++ b/packages/utils/src/rich-filters/factories/configs/shared.ts
@@ -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 = (
+ config: TFilterConfig
+): TFilterConfig
=> config;
+
+/**
+ * Base parameters for filter type config factory functions.
+ * - operator: The operator to use for the filter.
+ */
+export type TCreateFilterConfigParams = Omit & {
+ isEnabled: boolean;
+ allowedOperators: Set;
+};
+
+/**
+ * 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 {
+ filterIcon?: React.FC>;
+ getOptionIcon?: (value: T) => React.ReactNode;
+}
+
+/**
+ * Date filter config params
+ */
+export type TCreateDateFilterParams = TCreateFilterConfigParams & IFilterIconConfig;
+
+/**
+ * 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 = (
+ 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 =
: never
): TSupportedFilterFieldConfigs => config as TSupportedFilterFieldConfigs;
-
-/**
- * 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 {
- filterIcon?: React.FC>;
- getOptionIcon?: (value: T) => React.ReactNode;
-}
-
-/**
- * Date filter config params
- */
-export type TCreateDateFilterParams = TCreateFilterConfigParams & IFilterIconConfig;
-
-// ------------ 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,
-};
diff --git a/packages/utils/src/work-item-filters/configs/filters/cycle.ts b/packages/utils/src/work-item-filters/configs/filters/cycle.ts
new file mode 100644
index 000000000..08b8aff6b
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/cycle.ts
@@ -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 & {
+ 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(
+ {
+ 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 =
+ (key: P): TCreateFilterConfig
=>
+ (params: TCreateCycleFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "Cycle",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ supportedOperatorConfigsMap: new Map([
+ createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
+ getCycleMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
+ ),
+ ]),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/filters/date.ts b/packages/utils/src/work-item-filters/configs/filters/date.ts
new file mode 100644
index 000000000..1de8f9728
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/date.ts
@@ -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 =
+
(key: P): TCreateFilterConfig
=>
+ (params: TCreateDateFilterParams) =>
+ createFilterConfig
({
+ 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 =
+
(key: P): TCreateFilterConfig
=>
+ (params: TCreateDateFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "Target date",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ allowMultipleFilters: true,
+ supportedOperatorConfigsMap: getSupportedDateOperators(params),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/filters/index.ts b/packages/utils/src/work-item-filters/configs/filters/index.ts
new file mode 100644
index 000000000..7498018b1
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/index.ts
@@ -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";
diff --git a/packages/utils/src/work-item-filters/configs/filters/label.ts b/packages/utils/src/work-item-filters/configs/filters/label.ts
new file mode 100644
index 000000000..41fa84bf3
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/label.ts
@@ -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 & {
+ 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(
+ {
+ 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 =
+ (key: P): TCreateFilterConfig
=>
+ (params: TCreateLabelFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "Label",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ supportedOperatorConfigsMap: new Map([
+ createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
+ getLabelMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
+ ),
+ ]),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/filters/module.ts b/packages/utils/src/work-item-filters/configs/filters/module.ts
new file mode 100644
index 000000000..0c595eb25
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/module.ts
@@ -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 & {
+ 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(
+ {
+ 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 =
+ (key: P): TCreateFilterConfig
=>
+ (params: TCreateModuleFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "Module",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ supportedOperatorConfigsMap: new Map([
+ createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
+ getModuleMultiSelectConfig(updatedParams)
+ ),
+ ]),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/filters/priority.ts b/packages/utils/src/work-item-filters/configs/filters/priority.ts
new file mode 100644
index 000000000..b04a3b786
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/priority.ts
@@ -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;
+
+/**
+ * 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 =
+ (key: P): TCreateFilterConfig
=>
+ (params: TCreatePriorityFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "Priority",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ supportedOperatorConfigsMap: new Map([
+ createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
+ getPriorityMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
+ ),
+ ]),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/filters/project.ts b/packages/utils/src/work-item-filters/configs/filters/project.ts
new file mode 100644
index 000000000..b5c123ed4
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/project.ts
@@ -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 =
+
(key: P): TCreateFilterConfig
=>
+ (params: TCreateProjectFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "Projects",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ supportedOperatorConfigsMap: new Map([
+ createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
+ getProjectMultiSelectConfig(updatedParams, EQUALITY_OPERATOR.EXACT)
+ ),
+ ]),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/filters/shared.ts b/packages/utils/src/work-item-filters/configs/filters/shared.ts
new file mode 100644
index 000000000..af501d62e
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/shared.ts
@@ -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 =>
+ 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 & {
+ 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(
+ {
+ items: params.projects,
+ getId: (project) => project.id,
+ getLabel: (project) => project.name,
+ getValue: (project) => project.id,
+ getIconData: (project) => project,
+ },
+ {
+ singleValueOperator,
+ ...params,
+ },
+ {
+ ...params,
+ }
+ );
diff --git a/packages/utils/src/work-item-filters/configs/filters/state.ts b/packages/utils/src/work-item-filters/configs/filters/state.ts
new file mode 100644
index 000000000..2281171e1
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/state.ts
@@ -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;
+
+/**
+ * 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 =
+ (key: P): TCreateFilterConfig
=>
+ (params: TCreateStateGroupFilterParams) =>
+ createFilterConfig
({
+ 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 & {
+ 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(
+ {
+ 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 =
+ (key: P): TCreateFilterConfig
=>
+ (params: TCreateStateFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "State",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ supportedOperatorConfigsMap: new Map([
+ createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
+ getStateMultiSelectConfig(updatedParams)
+ ),
+ ]),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/filters/user.ts b/packages/utils/src/work-item-filters/configs/filters/user.ts
new file mode 100644
index 000000000..cae90b871
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/filters/user.ts
@@ -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 & {
+ 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(
+ {
+ 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 =
+ (key: P): TCreateFilterConfig
=>
+ (params: TCreateAssigneeFilterParams) =>
+ createFilterConfig
({
+ 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 =
+
(key: P): TCreateFilterConfig
=>
+ (params: TCreateMentionFilterParams) =>
+ createFilterConfig
({
+ 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 =
+
(key: P): TCreateFilterConfig
=>
+ (params: TCreateCreatedByFilterParams) =>
+ createFilterConfig
({
+ 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 =
+
(key: P): TCreateFilterConfig
=>
+ (params: TCreateSubscriberFilterParams) =>
+ createFilterConfig
({
+ id: key,
+ label: "Subscriber",
+ icon: params.filterIcon,
+ isEnabled: params.isEnabled,
+ supportedOperatorConfigsMap: new Map([
+ createOperatorConfigEntry(COLLECTION_OPERATOR.IN, params, (updatedParams) =>
+ getMemberMultiSelectConfig(updatedParams)
+ ),
+ ]),
+ });
diff --git a/packages/utils/src/work-item-filters/configs/index.ts b/packages/utils/src/work-item-filters/configs/index.ts
new file mode 100644
index 000000000..302e3a1a6
--- /dev/null
+++ b/packages/utils/src/work-item-filters/configs/index.ts
@@ -0,0 +1 @@
+export * from "./filters";
diff --git a/packages/utils/src/work-item-filters/index.ts b/packages/utils/src/work-item-filters/index.ts
new file mode 100644
index 000000000..3158367fc
--- /dev/null
+++ b/packages/utils/src/work-item-filters/index.ts
@@ -0,0 +1 @@
+export * from "./configs";
diff --git a/packages/utils/src/work-item/base.ts b/packages/utils/src/work-item/base.ts
index b16289c93..61fd44c5f 100644
--- a/packages/utils/src/work-item/base.ts
+++ b/packages/utils/src/work-item/base.ts
@@ -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,
};