Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useCallback, useMemo } from "react";
import { AtSign, Briefcase, Calendar } from "lucide-react";
import { AtSign, Briefcase } from "lucide-react";
// plane imports
import { Logo } from "@plane/propel/emoji-icon-picker";
import {
CalendarLayoutIcon,
CycleGroupIcon,
CycleIcon,
ModuleIcon,
Expand All @@ -21,7 +22,6 @@ import type {
IState,
IUserLite,
TFilterConfig,
TFilterValue,
IIssueLabel,
IModule,
IProject,
Expand Down Expand Up @@ -74,9 +74,9 @@ export type TUseWorkItemFiltersConfigProps = {

export type TWorkItemFiltersConfig = {
areAllConfigsInitialized: boolean;
configs: TFilterConfig<TWorkItemFilterProperty, TFilterValue>[];
configs: TFilterConfig<TWorkItemFilterProperty>[];
configMap: {
[key in TWorkItemFilterProperty]?: TFilterConfig<TWorkItemFilterProperty, TFilterValue>;
[key in TWorkItemFilterProperty]?: TFilterConfig<TWorkItemFilterProperty>;
};
isFilterEnabled: (key: TWorkItemFilterProperty) => boolean;
members: IUserLite[];
Expand Down Expand Up @@ -326,7 +326,7 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps):
() =>
getCreatedAtFilterConfig<TWorkItemFilterProperty>("created_at")({
isEnabled: true,
filterIcon: Calendar,
filterIcon: CalendarLayoutIcon,
...operatorConfigs,
}),
[operatorConfigs]
Expand All @@ -337,7 +337,7 @@ export const useWorkItemFiltersConfig = (props: TUseWorkItemFiltersConfigProps):
() =>
getUpdatedAtFilterConfig<TWorkItemFilterProperty>("updated_at")({
isEnabled: true,
filterIcon: Calendar,
filterIcon: CalendarLayoutIcon,
...operatorConfigs,
}),
[operatorConfigs]
Expand Down
20 changes: 10 additions & 10 deletions packages/shared-state/src/store/rich-filters/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { computedFn } from "mobx-utils";
// plane imports
import type { TConfigOptions } from "@plane/constants";
import { DEFAULT_FILTER_CONFIG_OPTIONS } from "@plane/constants";
import type { TExternalFilter, TFilterConfig, TFilterProperty, TFilterValue } from "@plane/types";
import type { TExternalFilter, TFilterConfig, TFilterProperty } from "@plane/types";
// local imports
import type { IFilterConfig } from "./config";
import { FilterConfig } from "./config";
Expand All @@ -24,17 +24,17 @@ import type { IFilterInstance } from "./filter";
*/
export interface IFilterConfigManager<P extends TFilterProperty> {
// observables
filterConfigs: Map<P, IFilterConfig<P, TFilterValue>>; // filter property -> config
filterConfigs: Map<P, IFilterConfig<P>>; // filter property -> config
configOptions: TConfigOptions;
areConfigsReady: boolean;
// computed
allAvailableConfigs: IFilterConfig<P, TFilterValue>[];
allAvailableConfigs: IFilterConfig<P>[];
// computed functions
getConfigByProperty: (property: P) => IFilterConfig<P, TFilterValue> | undefined;
getConfigByProperty: (property: P) => IFilterConfig<P> | undefined;
// helpers
register: <C extends TFilterConfig<P, TFilterValue>>(config: C) => void;
registerAll: (configs: TFilterConfig<P, TFilterValue>[]) => void;
updateConfigByProperty: (property: P, configUpdates: Partial<TFilterConfig<P, TFilterValue>>) => void;
register: <C extends TFilterConfig<P>>(config: C) => void;
registerAll: (configs: TFilterConfig<P>[]) => void;
updateConfigByProperty: (property: P, configUpdates: Partial<TFilterConfig<P>>) => void;
setAreConfigsReady: (value: boolean) => void;
}

Expand Down Expand Up @@ -115,7 +115,7 @@ export class FilterConfigManager<
* @returns The config for the property, or undefined if not found.
*/
getConfigByProperty: IFilterConfigManager<P>["getConfigByProperty"] = computedFn(
(property) => this.filterConfigs.get(property) as IFilterConfig<P, TFilterValue>
(property) => this.filterConfigs.get(property) as IFilterConfig<P>
);

// ------------ helpers ------------
Expand Down Expand Up @@ -165,15 +165,15 @@ export class FilterConfigManager<

// ------------ private computed ------------

private get _allConfigs(): IFilterConfig<P, TFilterValue>[] {
private get _allConfigs(): IFilterConfig<P>[] {
return Array.from(this.filterConfigs.values());
}

/**
* Returns all enabled filterConfigs.
* @returns All enabled filterConfigs.
*/
private get _allEnabledConfigs(): IFilterConfig<P, TFilterValue>[] {
private get _allEnabledConfigs(): IFilterConfig<P>[] {
return this._allConfigs.filter((config) => config.isEnabled);
}

Expand Down
86 changes: 39 additions & 47 deletions packages/shared-state/src/store/rich-filters/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,35 @@ type TOperatorOptionForDisplay = {
label: string;
};

export interface IFilterConfig<P extends TFilterProperty, V extends TFilterValue = TFilterValue> extends TFilterConfig<
P,
V
> {
export interface IFilterConfig<P extends TFilterProperty> extends TFilterConfig<P> {
// computed
allEnabledSupportedOperators: TSupportedOperators[];
firstOperator: TSupportedOperators | undefined;
// computed functions
getOperatorConfig: (
operator: TAllAvailableOperatorsForDisplay
) => TOperatorSpecificConfigs<V>[keyof TOperatorSpecificConfigs<V>] | undefined;
) => TOperatorSpecificConfigs[keyof TOperatorSpecificConfigs] | undefined;
getLabelForOperator: (operator: TAllAvailableOperatorsForDisplay | undefined) => string;
getDisplayOperatorByValue: <T extends TSupportedOperators>(operator: T, value: V) => T;
getAllDisplayOperatorOptionsByValue: (value: V) => TOperatorOptionForDisplay[];
getDisplayOperatorByValue: <T extends TSupportedOperators>(operator: T, value: TFilterValue) => T;
getAllDisplayOperatorOptionsByValue: (value: TFilterValue) => TOperatorOptionForDisplay[];
// actions
mutate: (updates: Partial<TFilterConfig<P, V>>) => void;
mutate: (updates: Partial<TFilterConfig<P>>) => void;
}

export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TFilterValue> implements IFilterConfig<
P,
V
> {
export class FilterConfig<P extends TFilterProperty> implements IFilterConfig<P> {
// observables
id: IFilterConfig<P, V>["id"];
label: IFilterConfig<P, V>["label"];
icon?: IFilterConfig<P, V>["icon"];
isEnabled: IFilterConfig<P, V>["isEnabled"];
supportedOperatorConfigsMap: IFilterConfig<P, V>["supportedOperatorConfigsMap"];
allowMultipleFilters: IFilterConfig<P, V>["allowMultipleFilters"];
id: IFilterConfig<P>["id"];
label: IFilterConfig<P>["label"];
icon?: IFilterConfig<P>["icon"];
isEnabled: IFilterConfig<P>["isEnabled"];
supportedOperatorConfigsMap: IFilterConfig<P>["supportedOperatorConfigsMap"];
allowMultipleFilters: IFilterConfig<P>["allowMultipleFilters"];

/**
* Creates a new FilterConfig instance.
* @param params - The parameters for the filter config.
*/
constructor(params: TFilterConfig<P, V>) {
constructor(params: TFilterConfig<P>) {
this.id = params.id;
this.label = params.label;
this.icon = params.icon;
Expand Down Expand Up @@ -88,7 +82,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
* Returns all supported operators.
* @returns All supported operators.
*/
get allEnabledSupportedOperators(): IFilterConfig<P, V>["allEnabledSupportedOperators"] {
get allEnabledSupportedOperators(): IFilterConfig<P>["allEnabledSupportedOperators"] {
return Array.from(this.supportedOperatorConfigsMap.entries())
.filter(([, operatorConfig]) => operatorConfig.isOperatorEnabled)
.map(([operator]) => operator);
Expand All @@ -98,7 +92,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
* Returns the first operator.
* @returns The first operator.
*/
get firstOperator(): IFilterConfig<P, V>["firstOperator"] {
get firstOperator(): IFilterConfig<P>["firstOperator"] {
return this.allEnabledSupportedOperators[0];
}

Expand All @@ -109,7 +103,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
* @param operator - The operator.
* @returns The operator config.
*/
getOperatorConfig: IFilterConfig<P, V>["getOperatorConfig"] = computedFn((operator) =>
getOperatorConfig: IFilterConfig<P>["getOperatorConfig"] = computedFn((operator) =>
this.supportedOperatorConfigsMap.get(getOperatorForPayload(operator).operator)
);

Expand All @@ -118,7 +112,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
* @param operator - The operator.
* @returns The label for the operator.
*/
getLabelForOperator: IFilterConfig<P, V>["getLabelForOperator"] = computedFn((operator) => {
getLabelForOperator: IFilterConfig<P>["getLabelForOperator"] = computedFn((operator) => {
if (!operator) return EMPTY_OPERATOR_LABEL;

const operatorConfig = this.getOperatorConfig(operator);
Expand All @@ -139,7 +133,7 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
* @param value - The value.
* @returns The operator for the value.
*/
getDisplayOperatorByValue: IFilterConfig<P, V>["getDisplayOperatorByValue"] = computedFn((operator, value) => {
getDisplayOperatorByValue: IFilterConfig<P>["getDisplayOperatorByValue"] = computedFn((operator, value) => {
const operatorConfig = this.getOperatorConfig(operator);
if (operatorConfig?.type === FILTER_FIELD_TYPE.MULTI_SELECT && (Array.isArray(value) ? value.length : 0) <= 1) {
return operatorConfig.singleValueOperator as typeof operator;
Expand All @@ -155,40 +149,38 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF
* @param value - The current filter value used to determine the appropriate operator variant
* @returns Array of operator options with their display labels and values
*/
getAllDisplayOperatorOptionsByValue: IFilterConfig<P, V>["getAllDisplayOperatorOptionsByValue"] = computedFn(
(value) => {
const operatorOptions: TOperatorOptionForDisplay[] = [];

// Process each supported operator to build display options
for (const operator of this.allEnabledSupportedOperators) {
const displayOperator = this.getDisplayOperatorByValue(operator, value);
const displayOperatorLabel = this.getLabelForOperator(displayOperator);
operatorOptions.push({
value: operator,
label: displayOperatorLabel,
});

const additionalOperatorOption = this._getAdditionalOperatorOptions(operator, value);
if (additionalOperatorOption) {
operatorOptions.push(additionalOperatorOption);
}
getAllDisplayOperatorOptionsByValue: IFilterConfig<P>["getAllDisplayOperatorOptionsByValue"] = computedFn((value) => {
const operatorOptions: TOperatorOptionForDisplay[] = [];

// Process each supported operator to build display options
for (const operator of this.allEnabledSupportedOperators) {
const displayOperator = this.getDisplayOperatorByValue(operator, value);
const displayOperatorLabel = this.getLabelForOperator(displayOperator);
operatorOptions.push({
value: operator,
label: displayOperatorLabel,
});

const additionalOperatorOption = this._getAdditionalOperatorOptions(operator, value);
if (additionalOperatorOption) {
operatorOptions.push(additionalOperatorOption);
}

return operatorOptions;
}
);

return operatorOptions;
});

// ------------ actions ------------

/**
* Mutates the config.
* @param updates - The updates to apply to the config.
*/
mutate: IFilterConfig<P, V>["mutate"] = action((updates) => {
mutate: IFilterConfig<P>["mutate"] = action((updates) => {
runInAction(() => {
for (const key in updates) {
if (updates.hasOwnProperty(key)) {
const configKey = key as keyof TFilterConfig<P, V>;
const configKey = key as keyof TFilterConfig<P>;
set(this, configKey, updates[configKey]);
}
}
Expand All @@ -199,6 +191,6 @@ export class FilterConfig<P extends TFilterProperty, V extends TFilterValue = TF

private _getAdditionalOperatorOptions = (
_operator: TSupportedOperators,
_value: V
_value: TFilterValue
): TOperatorOptionForDisplay | undefined => undefined;
}
11 changes: 8 additions & 3 deletions packages/shared-state/src/store/rich-filters/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ export interface IFilterInstance<P extends TFilterProperty, E extends TExternalF
isNegation: boolean
) => void;
updateConditionOperator: (conditionId: string, operator: TSupportedOperators, isNegation: boolean) => void;
updateConditionValue: <V extends TFilterValue>(conditionId: string, value: SingleOrArray<V>) => void;
updateConditionValue: <V extends TFilterValue>(
conditionId: string,
value: SingleOrArray<V>,
forceUpdate?: boolean
) => void;
removeCondition: (conditionId: string) => void;
// config actions
clearFilters: () => Promise<void>;
Expand Down Expand Up @@ -439,9 +443,10 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
* Updates the value of a condition in the filter expression with automatic optimization.
* @param conditionId - The id of the condition to update.
* @param value - The new value for the condition.
* @param forceUpdate - Whether to force the update even if the value is the same as the condition before update.
*/
updateConditionValue: IFilterInstance<P, E>["updateConditionValue"] = action(
<V extends TFilterValue>(conditionId: string, value: SingleOrArray<V>) => {
<V extends TFilterValue>(conditionId: string, value: SingleOrArray<V>, forceUpdate: boolean = false) => {
// If the expression is not valid, return
if (!this.expression) return;

Expand All @@ -458,7 +463,7 @@ export class FilterInstance<P extends TFilterProperty, E extends TExternalFilter
}

// If the value is the same as the condition before update, return
if (isEqual(conditionBeforeUpdate.value, value)) {
if (!forceUpdate && isEqual(conditionBeforeUpdate.value, value)) {
return;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/types/src/rich-filters/config/filter-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TFilterProperty, TFilterValue } from "../expression";
import type { TFilterProperty } from "../expression";
import type { TOperatorConfigMap } from "../operator-configs";

/**
Expand All @@ -8,13 +8,13 @@ import type { TOperatorConfigMap } from "../operator-configs";
* @template P - Property key type (e.g., 'state_id', 'priority', 'assignee')
* @template V - Value type for the filter
*/
export type TFilterConfig<P extends TFilterProperty, V extends TFilterValue = TFilterValue> = {
export type TFilterConfig<P extends TFilterProperty> = {
id: P;
label: string;
icon?: React.FC<React.SVGAttributes<SVGElement>>;
isEnabled: boolean;
allowMultipleFilters?: boolean;
supportedOperatorConfigsMap: TOperatorConfigMap<V>;
supportedOperatorConfigsMap: TOperatorConfigMap;
rightContent?: React.ReactNode; // content to display on the right side of the filter option in the dropdown
tooltipContent?: React.ReactNode; // content to display when hovering over the applied filter item in the filter list
};
24 changes: 12 additions & 12 deletions packages/types/src/rich-filters/derived/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@ import type { TFilterOperatorHelper } from "./shared";
* Union type representing all core operators that support single date filter types.
*/
export type TCoreSupportedSingleDateFilterOperators<V extends TFilterValue = TFilterValue> = {
[K in keyof TCoreOperatorSpecificConfigs<V>]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs<V>,
[K in keyof TCoreOperatorSpecificConfigs]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs,
K,
TDateFilterFieldConfig<V>
>;
}[keyof TCoreOperatorSpecificConfigs<V>];
}[keyof TCoreOperatorSpecificConfigs];

/**
* Union type representing all core operators that support range date filter types.
*/
export type TCoreSupportedRangeDateFilterOperators<V extends TFilterValue = TFilterValue> = {
[K in keyof TCoreOperatorSpecificConfigs<V>]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs<V>,
[K in keyof TCoreOperatorSpecificConfigs]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs,
K,
TDateRangeFilterFieldConfig<V>
>;
}[keyof TCoreOperatorSpecificConfigs<V>];
}[keyof TCoreOperatorSpecificConfigs];

/**
* Union type representing all core operators that support date filter types.
Expand All @@ -48,23 +48,23 @@ export type TCoreAllAvailableDateFilterOperatorsForDisplay<V extends TFilterValu
* Union type representing all core operators that support single select filter types.
*/
export type TCoreSupportedSingleSelectFilterOperators<V extends TFilterValue = TFilterValue> = {
[K in keyof TCoreOperatorSpecificConfigs<V>]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs<V>,
[K in keyof TCoreOperatorSpecificConfigs]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs,
K,
TSingleSelectFilterFieldConfig<V>
>;
}[keyof TCoreOperatorSpecificConfigs<V>];
}[keyof TCoreOperatorSpecificConfigs];

/**
* Union type representing all core operators that support multi select filter types.
*/
export type TCoreSupportedMultiSelectFilterOperators<V extends TFilterValue = TFilterValue> = {
[K in keyof TCoreOperatorSpecificConfigs<V>]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs<V>,
[K in keyof TCoreOperatorSpecificConfigs]: TFilterOperatorHelper<
TCoreOperatorSpecificConfigs,
K,
TMultiSelectFilterFieldConfig<V>
>;
}[keyof TCoreOperatorSpecificConfigs<V>];
}[keyof TCoreOperatorSpecificConfigs];

/**
* Union type representing all core operators that support any select filter types.
Expand Down
Loading
Loading