[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 +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;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue