[WEB-5099] improvement: enhance rich filters with new components and configurations (#7916)
* feat: enhance rich filters with new components and configurations - Added `AdditionalFilterValueInput` for unsupported filter types. - Introduced `FilterItem` and related components for better filter item management. - Updated filter configurations to include new properties and support for multiple values. - Improved loading states and error handling in filter components. - Refactored existing filter logic to streamline operations and enhance performance. * Refactor rich filters component structure and enhance filter item functionality - Moved AddFilterButton and AddFilterDropdown to a new directory structure for better organization. - Updated FilterItemProperty to handle filter selection and condition updates more effectively. - Enhanced the FilterInstance class with methods to update condition properties and operators, improving filter management. - Added new functionality to handle invalid filter states and improve user feedback. * [WEB-5111] feat: add 'created_at' and 'updated_at' filters to work item configuration - Introduced new filter configurations for 'created_at' and 'updated_at' in the work item filters. - Updated relevant components to utilize these new filters, enhancing filtering capabilities. - Added corresponding filter configuration functions in the utils for better date handling. * fix: build
This commit is contained in:
parent
9f41e92d21
commit
cfb4a8212c
49 changed files with 854 additions and 247 deletions
|
|
@ -24,6 +24,7 @@ export interface IFilterConfigManager<P extends TFilterProperty> {
|
|||
// observables
|
||||
filterConfigs: Map<P, IFilterConfig<P, TFilterValue>>; // filter property -> config
|
||||
configOptions: TConfigOptions;
|
||||
areConfigsReady: boolean;
|
||||
// computed
|
||||
allAvailableConfigs: IFilterConfig<P, TFilterValue>[];
|
||||
// computed functions
|
||||
|
|
@ -32,6 +33,7 @@ export interface IFilterConfigManager<P extends TFilterProperty> {
|
|||
register: <C extends TFilterConfig<P, TFilterValue>>(config: C) => void;
|
||||
registerAll: (configs: TFilterConfig<P, TFilterValue>[]) => void;
|
||||
updateConfigByProperty: (property: P, configUpdates: Partial<TFilterConfig<P, TFilterValue>>) => void;
|
||||
setAreConfigsReady: (value: boolean) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -57,6 +59,7 @@ export class FilterConfigManager<P extends TFilterProperty, E extends TExternalF
|
|||
// observables
|
||||
filterConfigs: IFilterConfigManager<P>["filterConfigs"];
|
||||
configOptions: IFilterConfigManager<P>["configOptions"];
|
||||
areConfigsReady: IFilterConfigManager<P>["areConfigsReady"];
|
||||
// parent filter instance
|
||||
private _filterInstance: IFilterInstance<P, E>;
|
||||
|
||||
|
|
@ -69,18 +72,21 @@ export class FilterConfigManager<P extends TFilterProperty, E extends TExternalF
|
|||
constructor(filterInstance: IFilterInstance<P, E>, params: TConfigManagerParams) {
|
||||
this.filterConfigs = new Map<P, IFilterConfig<P>>();
|
||||
this.configOptions = this._initializeConfigOptions(params.options);
|
||||
this.areConfigsReady = true;
|
||||
// parent filter instance
|
||||
this._filterInstance = filterInstance;
|
||||
|
||||
makeObservable(this, {
|
||||
filterConfigs: observable,
|
||||
configOptions: observable,
|
||||
areConfigsReady: observable,
|
||||
// computed
|
||||
allAvailableConfigs: computed,
|
||||
// helpers
|
||||
register: action,
|
||||
registerAll: action,
|
||||
updateConfigByProperty: action,
|
||||
setAreConfigsReady: action,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -146,6 +152,14 @@ export class FilterConfigManager<P extends TFilterProperty, E extends TExternalF
|
|||
prevConfig?.mutate(configUpdates);
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the configs ready state.
|
||||
* @param value - The new configs ready state.
|
||||
*/
|
||||
setAreConfigsReady: IFilterConfigManager<P>["setAreConfigsReady"] = action((value) => {
|
||||
this.areConfigsReady = value;
|
||||
});
|
||||
|
||||
// ------------ private computed ------------
|
||||
|
||||
private get _allConfigs(): IFilterConfig<P, TFilterValue>[] {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
IFilterAdapter,
|
||||
LOGICAL_OPERATOR,
|
||||
TSupportedOperators,
|
||||
TFilterConditionNode,
|
||||
TFilterExpression,
|
||||
TFilterValue,
|
||||
TFilterProperty,
|
||||
|
|
@ -43,9 +44,16 @@ export interface IFilterInstanceHelper<P extends TFilterProperty, E extends TExt
|
|||
condition: TFilterConditionPayload<P, V>,
|
||||
isNegation: boolean
|
||||
) => TFilterExpression<P> | null;
|
||||
handleConditionPropertyUpdate: (
|
||||
expression: TFilterExpression<P>,
|
||||
conditionId: string,
|
||||
property: P,
|
||||
operator: TSupportedOperators,
|
||||
isNegation: boolean
|
||||
) => TFilterExpression<P> | null;
|
||||
// group operations
|
||||
restructureExpressionForOperatorChange: (
|
||||
expression: TFilterExpression<P> | null,
|
||||
expression: TFilterExpression<P>,
|
||||
conditionId: string,
|
||||
newOperator: TSupportedOperators,
|
||||
isNegation: boolean,
|
||||
|
|
@ -162,6 +170,28 @@ export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternal
|
|||
isNegation
|
||||
) => this._addConditionByOperator(expression, groupOperator, this._getConditionPayloadToAdd(condition, isNegation));
|
||||
|
||||
/**
|
||||
* Updates the property and operator of a condition in the filter expression.
|
||||
* This method updates the property, operator, resets the value, and handles negation properly.
|
||||
* @param expression - The filter expression to operate on
|
||||
* @param conditionId - The ID of the condition being updated
|
||||
* @param property - The new property for the condition
|
||||
* @param operator - The new operator for the condition
|
||||
* @param isNegation - Whether the condition should be negated
|
||||
* @returns The updated expression
|
||||
*/
|
||||
handleConditionPropertyUpdate: IFilterInstanceHelper<P, E>["handleConditionPropertyUpdate"] = (
|
||||
expression,
|
||||
conditionId,
|
||||
property,
|
||||
operator,
|
||||
isNegation
|
||||
) => {
|
||||
const payload = { property, operator, value: undefined };
|
||||
|
||||
return this._updateCondition(expression, conditionId, payload, isNegation);
|
||||
};
|
||||
|
||||
// ------------ group operations ------------
|
||||
|
||||
/**
|
||||
|
|
@ -177,17 +207,12 @@ export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternal
|
|||
expression,
|
||||
conditionId,
|
||||
newOperator,
|
||||
_isNegation,
|
||||
isNegation,
|
||||
shouldResetValue
|
||||
) => {
|
||||
if (!expression) return null;
|
||||
|
||||
const payload = shouldResetValue ? { operator: newOperator, value: undefined } : { operator: newOperator };
|
||||
|
||||
// Update the condition with the new operator
|
||||
updateNodeInExpression(expression, conditionId, payload);
|
||||
|
||||
return expression;
|
||||
return this._updateCondition(expression, conditionId, payload, isNegation);
|
||||
};
|
||||
|
||||
// ------------ private helpers ------------
|
||||
|
|
@ -227,4 +252,24 @@ export class FilterInstanceHelper<P extends TFilterProperty, E extends TExternal
|
|||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a condition with the given payload and handles negation wrapping/unwrapping.
|
||||
* @param expression - The filter expression to operate on
|
||||
* @param conditionId - The ID of the condition being updated
|
||||
* @param payload - The payload to update the condition with
|
||||
* @param isNegation - Whether the condition should be negated
|
||||
* @returns The updated expression with proper negation handling
|
||||
*/
|
||||
private _updateCondition = (
|
||||
expression: TFilterExpression<P>,
|
||||
conditionId: string,
|
||||
payload: Partial<TFilterConditionNode<P, TFilterValue>>,
|
||||
_isNegation: boolean
|
||||
): TFilterExpression<P> | null => {
|
||||
// Update the condition with the payload
|
||||
updateNodeInExpression(expression, conditionId, payload);
|
||||
|
||||
return expression;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { cloneDeep } from "lodash-es";
|
||||
import { cloneDeep, isEqual } from "lodash-es";
|
||||
import { action, computed, makeObservable, observable, toJS } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
|
@ -101,6 +101,12 @@ export interface IFilterInstance<P extends TFilterProperty, E extends TExternalF
|
|||
condition: TFilterConditionPayload<P, V>,
|
||||
isNegation: boolean
|
||||
) => void;
|
||||
updateConditionProperty: (
|
||||
conditionId: string,
|
||||
property: P,
|
||||
operator: TSupportedOperators,
|
||||
isNegation: boolean
|
||||
) => void;
|
||||
updateConditionOperator: (conditionId: string, operator: TSupportedOperators, isNegation: boolean) => void;
|
||||
updateConditionValue: <V extends TFilterValue>(conditionId: string, value: SingleOrArray<V>) => void;
|
||||
removeCondition: (conditionId: string) => void;
|
||||
|
|
@ -360,6 +366,33 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the property of a condition in the filter expression.
|
||||
* @param conditionId - The id of the condition to update.
|
||||
* @param property - The new property for the condition.
|
||||
*/
|
||||
updateConditionProperty: IFilterInstance<P, E>["updateConditionProperty"] = action(
|
||||
(conditionId: string, property: P, operator: TSupportedOperators, isNegation: boolean) => {
|
||||
if (!this.expression) return;
|
||||
const conditionBeforeUpdate = cloneDeep(findNodeById(this.expression, conditionId));
|
||||
if (!conditionBeforeUpdate || conditionBeforeUpdate.type !== FILTER_NODE_TYPE.CONDITION) return;
|
||||
|
||||
// Update the condition property
|
||||
const updatedExpression = this.helper.handleConditionPropertyUpdate(
|
||||
this.expression,
|
||||
conditionId,
|
||||
property,
|
||||
operator,
|
||||
isNegation
|
||||
);
|
||||
|
||||
if (updatedExpression) {
|
||||
this.expression = updatedExpression;
|
||||
this._notifyExpressionChange();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Updates the operator of a condition in the filter expression.
|
||||
* @param conditionId - The id of the condition to update.
|
||||
|
|
@ -410,12 +443,23 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
|
|||
// If the expression is not valid, return
|
||||
if (!this.expression) return;
|
||||
|
||||
// Get the condition before update
|
||||
const conditionBeforeUpdate = cloneDeep(findNodeById(this.expression, conditionId));
|
||||
|
||||
// If the condition is not valid, return
|
||||
if (!conditionBeforeUpdate || conditionBeforeUpdate.type !== FILTER_NODE_TYPE.CONDITION) return;
|
||||
|
||||
// If the value is not valid, remove the condition
|
||||
if (!hasValidValue(value)) {
|
||||
this.removeCondition(conditionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the value is the same as the condition before update, return
|
||||
if (isEqual(conditionBeforeUpdate.value, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the condition value
|
||||
updateNodeInExpression(this.expression, conditionId, {
|
||||
value,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { isEmpty } from "lodash-es";
|
||||
import {
|
||||
LOGICAL_OPERATOR,
|
||||
MULTI_VALUE_OPERATORS,
|
||||
SingleOrArray,
|
||||
TFilterExpression,
|
||||
TFilterValue,
|
||||
|
|
@ -161,7 +162,8 @@ class WorkItemFiltersAdapter extends FilterAdapter<TWorkItemFilterProperty, TWor
|
|||
const operator = key.substring(lastDoubleUnderscoreIndex + 2);
|
||||
|
||||
// Validate property is in allowed list
|
||||
if (!WORK_ITEM_FILTER_PROPERTY_KEYS.includes(property as TWorkItemFilterProperty)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if (!WORK_ITEM_FILTER_PROPERTY_KEYS.includes(property as any) && !property.startsWith("customproperty_")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -192,17 +194,12 @@ class WorkItemFiltersAdapter extends FilterAdapter<TWorkItemFilterProperty, TWor
|
|||
// 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 operator = key.substring(lastDoubleUnderscoreIndex + 2) as TSupportedOperators;
|
||||
|
||||
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);
|
||||
const parsedValue = MULTI_VALUE_OPERATORS.includes(operator) ? this._parseFilterValue(rawValue) : rawValue;
|
||||
|
||||
return [property as TWorkItemFilterProperty, operator as TSupportedOperators, parsedValue];
|
||||
};
|
||||
|
|
@ -212,7 +209,9 @@ class WorkItemFiltersAdapter extends FilterAdapter<TWorkItemFilterProperty, TWor
|
|||
* @param value - The string value to parse
|
||||
* @returns Parsed value as string or array of strings
|
||||
*/
|
||||
private _parseFilterValue = (value: string): SingleOrArray<TFilterValue> => {
|
||||
private _parseFilterValue = (value: TFilterValue): SingleOrArray<TFilterValue> => {
|
||||
if (!value) return value;
|
||||
|
||||
if (typeof value !== "string") return value;
|
||||
|
||||
// Handle empty string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue