Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add aggregation filter operators #659

Merged
merged 2 commits into from
Jan 20, 2025
Merged
Changes from 1 commit
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
Next Next commit
feat: add aggregation filter operators
ChibiBlasphem committed Jan 16, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit b06beefa8cd34882fec2fc5ffa898e3e01b6820d
Original file line number Diff line number Diff line change
@@ -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 {
@@ -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,
@@ -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(
@@ -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,
Original file line number Diff line number Diff line change
@@ -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,
@@ -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 = () => ({
@@ -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(),
};
}),
);
};

@@ -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 */}
@@ -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>
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import {
import {
type AggregationAstNode,
isAggregation,
isBinaryAggregationFilter,
} from '@app-builder/models/astNode/aggregation';
import {
type CustomListAccessAstNode,
@@ -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
@@ -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>
);
Loading