Skip to content

Commit

Permalink
feat: add aggregation filter operators (#659)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChibiBlasphem authored Jan 20, 2025
1 parent 912ef9d commit e928552
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import { type AstNode } from '@app-builder/models';
import {
type AggregationAstNode,
aggregationAstNodeName,
type AggregationFilterAstNode,
type AggregationFilterOperator,
type BinaryAggregationFilterOperator,
binaryAggregationFilterOperators,
type GetAggregationFilterOperator,
isUnaryAggregationFilter,
type UnaryAggregationFilterOperator,
unaryAggregationFilterOperators,
} from '@app-builder/models/astNode/aggregation';
import { NewConstantAstNode } from '@app-builder/models/astNode/constant';
import {
Expand Down Expand Up @@ -47,17 +55,44 @@ export interface AggregationViewModel {
aggregatedField: EvaluationError[];
};
}
export interface FilterViewModel {
operator: string | null;
export type FilterViewModel<
T extends AggregationFilterOperator = AggregationFilterOperator,
> = {
operator: T | null;
filteredField: DataModelField | null;
value: { astNode: AstNode; astNodeErrors?: AstNodeErrors };
errors: {
filter: EvaluationError[];
operator: EvaluationError[];
filteredField: EvaluationError[];
value: EvaluationError[];
};
}
} & (T extends UnaryAggregationFilterOperator
? { value?: undefined }
: {
value: { astNode: AstNode; astNodeErrors?: AstNodeErrors };
});

export const isUnaryFilterModel = (
filter: FilterViewModel,
): filter is FilterViewModel<UnaryAggregationFilterOperator> => {
return (
!!filter.operator &&
(
unaryAggregationFilterOperators as ReadonlyArray<AggregationFilterOperator>
).includes(filter.operator)
);
};

export const isBinaryFilterModel = (
filter: FilterViewModel,
): filter is FilterViewModel<BinaryAggregationFilterOperator> => {
return (
filter.operator === null ||
(
binaryAggregationFilterOperators as ReadonlyArray<AggregationFilterOperator>
).includes(filter.operator)
);
};

export const adaptAggregationViewModel = (
initialAggregationAstNode: AggregationAstNode,
Expand Down Expand Up @@ -114,20 +149,26 @@ export const adaptAggregationViewModel = (
};
};

function adaptFilterViewModel(
filterAstNode: AggregationAstNode['namedChildren']['filters']['children'][number],
function adaptFilterViewModel<
T extends AggregationFilterAstNode = AggregationFilterAstNode,
>(
filterAstNode: T,
initialAstNodeErrors: AstNodeErrors,
): FilterViewModel {
): FilterViewModel<GetAggregationFilterOperator<T>> {
return {
operator: filterAstNode.namedChildren.operator.constant,
filteredField: {
tableName: filterAstNode.namedChildren.tableName?.constant,
fieldName: filterAstNode.namedChildren.fieldName?.constant,
},
value: {
astNode: filterAstNode.namedChildren.value,
astNodeErrors: initialAstNodeErrors.namedChildren['value'],
},
...(isUnaryAggregationFilter(filterAstNode)
? {}
: {
value: {
astNode: filterAstNode.namedChildren.value,
astNodeErrors: initialAstNodeErrors.namedChildren['value'],
},
}),
errors: {
filter: initialAstNodeErrors.errors,
operator: computeValidationForNamedChildren(
Expand All @@ -140,36 +181,41 @@ function adaptFilterViewModel(
initialAstNodeErrors,
['tableName', 'fieldName'],
),
value: computeValidationForNamedChildren(
filterAstNode,
initialAstNodeErrors,
'value',
),
...(isUnaryAggregationFilter(filterAstNode)
? {}
: {
value: computeValidationForNamedChildren(
filterAstNode,
initialAstNodeErrors,
'value',
),
}),
},
};
} as FilterViewModel<GetAggregationFilterOperator<T>>;
}

export const adaptAggregationAstNode = (
aggregationViewModel: AggregationViewModel,
): AggregationAstNode => {
const filters = aggregationViewModel.filters.map(
(filter: FilterViewModel) => ({
name: 'Filter' as const,
constant: undefined,
children: [],
namedChildren: {
operator: NewConstantAstNode({
constant: filter.operator,
}),
tableName: NewConstantAstNode({
constant: filter.filteredField?.tableName ?? null,
}),
fieldName: NewConstantAstNode({
constant: filter.filteredField?.fieldName ?? null,
}),
value: filter.value.astNode,
},
}),
(filter: FilterViewModel) =>
({
name: 'Filter' as const,
constant: undefined,
children: [],
namedChildren: {
operator: NewConstantAstNode({
constant: filter.operator,
}),
tableName: NewConstantAstNode({
constant: filter.filteredField?.tableName ?? null,
}),
fieldName: NewConstantAstNode({
constant: filter.filteredField?.fieldName ?? null,
}),
value: filter.value?.astNode,
},
}) as AggregationFilterAstNode,
);
return {
name: aggregationAstNodeName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { type AstNode, NewUndefinedAstNode } from '@app-builder/models';
import {
aggregationFilterOperators,
isAggregationFilterOperator,
} from '@app-builder/models/modale-operators';
isUnaryAggregationFilterOperator,
} from '@app-builder/models/astNode/aggregation';
import {
useDefaultCoerceToConstant,
useGetAstNodeOperandProps,
Expand All @@ -30,7 +31,7 @@ import { Icon } from 'ui-icons';

import { Operator } from '../../../../Operator';
import { Operand } from '../../../Operand';
import { type FilterViewModel } from './AggregationEdit';
import { type FilterViewModel, isBinaryFilterModel } from './AggregationEdit';
import { type DataModelField, EditDataModelField } from './EditDataModelField';

const newFilterValidation = () => ({
Expand Down Expand Up @@ -68,15 +69,46 @@ export function EditFilters({
filterIndex: number,
): void => {
onChange(
value.map((filter, index) =>
index === filterIndex
? {
...filter,
...newFieldValue,
validation: newFilterValidation(),
value.map((filter, index) => {
if (index !== filterIndex) {
return filter;
}

if ('operator' in newFieldValue && !!newFieldValue.operator) {
const isOldOpUnary = isUnaryAggregationFilterOperator(
filter.operator,
);
const isNewOpUnary = isUnaryAggregationFilterOperator(
newFieldValue.operator,
);
const isBinaryToUnary = !isOldOpUnary && isNewOpUnary;
const isUnaryToBinary = isOldOpUnary && !isNewOpUnary;

if (isBinaryToUnary || isUnaryToBinary) {
if (isBinaryToUnary) {
return {
operator: newFieldValue.operator,
filteredField: filter.filteredField,
value: undefined,
errors: newFilterValidation(),
};
} else {
return {
operator: newFieldValue.operator,
filteredField: filter.filteredField,
value: { astNode: NewUndefinedAstNode() },
errors: newFilterValidation(),
};
}
: filter,
),
}
}

return {
...filter,
...newFieldValue,
errors: newFilterValidation(),
};
}),
);
};

Expand Down Expand Up @@ -104,6 +136,8 @@ export function EditFilters({
{value.map((filter, filterIndex) => {
const isFirstCondition = filterIndex === 0;
const isLastCondition = filterIndex === value.length - 1;
const binaryFilter = isBinaryFilterModel(filter);

return (
<Fragment key={filterIndex}>
{/* Row 1 */}
Expand Down Expand Up @@ -152,21 +186,23 @@ export function EditFilters({
}
operators={aggregationFilterOperators}
/>
<FilterValue
filterValue={filter.value.astNode}
onSave={(astNode) =>
onFilterChange({ value: { astNode } }, filterIndex)
}
astNodeErrors={filter.value.astNodeErrors}
validationStatus={
filter.errors.value.length > 0 ? 'error' : 'valid'
}
/>
{binaryFilter ? (
<FilterValue
filterValue={filter.value.astNode}
onSave={(astNode) =>
onFilterChange({ value: { astNode } }, filterIndex)
}
astNodeErrors={filter.value.astNodeErrors}
validationStatus={
filter.errors.value.length > 0 ? 'error' : 'valid'
}
/>
) : null}
</div>
<EvaluationErrors
errors={adaptEvaluationErrorViewModels([
...filter.errors.filter,
...filter.errors.value,
...(isBinaryFilterModel(filter) ? filter.errors.value : []),
]).map(getNodeEvaluationErrorMessage)}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
type AggregationAstNode,
isAggregation,
isBinaryAggregationFilter,
} from '@app-builder/models/astNode/aggregation';
import {
type CustomListAccessAstNode,
Expand Down Expand Up @@ -225,7 +226,7 @@ function AggregatorDescription({ astNode }: { astNode: AggregationAstNode }) {
</span>
<span className="font-bold">{aggregatedFieldName}</span>
{filters.children.map((filter, index) => {
const { operator, fieldName, value } = filter.namedChildren;
const { operator, fieldName } = filter.namedChildren;
return (
<Fragment key={`filter_${index}`}>
<LogicalOperatorLabel
Expand All @@ -250,10 +251,12 @@ function AggregatorDescription({ astNode }: { astNode: AggregationAstNode }) {
operators={[operator?.constant as OperatorOption]}
viewOnly
/>
<OperandLabel
interactionMode="viewer"
{...getAstNodeOperandProps(value)}
/>
{isBinaryAggregationFilter(filter) ? (
<OperandLabel
interactionMode="viewer"
{...getAstNodeOperandProps(filter.namedChildren.value)}
/>
) : null}
</div>
</Fragment>
);
Expand Down
Loading

0 comments on commit e928552

Please sign in to comment.