Skip to content

Commit

Permalink
Added UI support for wavg
Browse files Browse the repository at this point in the history
  • Loading branch information
kelly-thai authored and aormerod-gs committed Nov 25, 2024
1 parent da82302 commit a5ca813
Show file tree
Hide file tree
Showing 19 changed files with 679 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .changeset/pretty-balloons-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@finos/legend-manual-tests': patch
'@finos/legend-query-builder': patch
---

Added UI support for wavg aggregation. It has been added as an aggregate operator and a drop zone has been added for weights to be used as the parameter.
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ const TEST_CASES: QueryTestCase[] = [
convertedRelation:
"|showcase::northwind::model::crm::Customer.all()->filter(x|$x.companyTitle == 'company title')->project(~['Company Name':x|$x.companyName, 'Company Title':x|$x.companyTitle])->filter(row|$row.'Company Name' == 'company name')",
},
//aggregation
{
testName: '[AGGREGATION] Simple wavg query',
model: 'Northwind',
queryGrammar:
"|showcase::northwind::model::OrderLineItem.all()->groupBy([], [agg(x|$x.quantity->meta::pure::functions::math::wavgUtility::wavgRowMapper($x.unitPrice), y|$y->wavg())], ['Quantity (wavg)'])",
},
];

const globalGraphManagerStates = new Map<string, GraphManagerState>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export enum QUERY_BUILDER_TEST_ID {
QUERY_BUILDER_EXPLORER = 'query__builder__explorer',
QUERY_BUILDER_PROPERTY_SEARCH_PANEL = 'query__builder__property__search__panel',
QUERY_BUILDER_PERCENTILE_PANEL = 'query__builder__percentile__panel',
QUERY_BUILDER_WAVG_DROPZONE = 'query__builder__wavg__dropzone',
//preview data modal
QUERY_BUILDER_PREVIEW_DATA_MODAL = 'query__builder__preview-data__modal',
// parameter panel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,100 @@ test(
},
);

test(
integrationTest('Query builder wavg aggregation loads correctly'),
async () => {
const { renderResult, queryBuilderState } = await TEST__setUpQueryBuilder(
TEST_DATA__ComplexRelationalModel,
stub_RawLambda(),
'model::relational::tests::simpleRelationalMapping',
'model::MyRuntime',
TEST_DATA__ModelCoverageAnalysisResult_ComplexRelational,
);
const _personClass = queryBuilderState.graphManagerState.graph.getClass(
'model::pure::tests::model::simple::Person',
);
await act(async () => {
queryBuilderState.changeClass(_personClass);
});
const queryBuilderSetup = await waitFor(() =>
renderResult.getByTestId(QUERY_BUILDER_TEST_ID.QUERY_BUILDER_SETUP),
);
await waitFor(() => getByText(queryBuilderSetup, 'Person'));
await waitFor(() =>
getByText(queryBuilderSetup, 'simpleRelationalMapping'),
);
await waitFor(() => getByText(queryBuilderSetup, 'MyRuntime'));
const explorerPanel = await waitFor(() =>
renderResult.getByTestId(QUERY_BUILDER_TEST_ID.QUERY_BUILDER_EXPLORER),
);

const projectionPanel = await waitFor(() =>
renderResult.getByTestId(
QUERY_BUILDER_TEST_ID.QUERY_BUILDER_TDS_PROJECTION,
),
);

// Drag and drop to the projection panel
let dropZone = await waitFor(() =>
getByText(projectionPanel, 'Add a projection column'),
);
let dragSource = await waitFor(() => getByText(explorerPanel, 'Age'));
await dragAndDrop(
dragSource,
dropZone,
projectionPanel,
'Add a projection column',
);
await waitFor(() => getByText(projectionPanel, 'Age'));
const tdsState = guaranteeType(
queryBuilderState.fetchStructureState.implementation,
QueryBuilderTDSState,
);
expect(tdsState.projectionColumns.length).toBe(1);
fireEvent.click(renderResult.getByTitle('Choose Aggregate Operator...'));
fireEvent.click(renderResult.getByText('wavg'));

const weightDropZone = renderResult.getByTestId(
QUERY_BUILDER_TEST_ID.QUERY_BUILDER_WAVG_DROPZONE,
);

expect(getByText(projectionPanel, 'wavg')).not.toBeNull();
expect(getByText(weightDropZone, 'Drop weight value')).not.toBeNull();
// Drag and drop wavg weight param
dropZone = await waitFor(() =>
getByText(projectionPanel, 'Drop weight value'),
);

// Expand explorer tree node
fireEvent.click(await findByText(explorerPanel, 'Firm'));

// Drag weight parameter
dragSource = await waitFor(() =>
getByText(explorerPanel, 'Average Employees Age'),
);
await dragAndDrop(
dragSource,
dropZone,
projectionPanel,
'Drop Numeric Property',
);
expect(getByText(weightDropZone, 'averageEmployeesAge')).not.toBeNull();

// Test changing weight
dragSource = await waitFor(() =>
getByText(explorerPanel, 'Sum Employees Age'),
);
await dragAndDrop(
dragSource,
dropZone,
projectionPanel,
'Drop Numeric Property',
);
expect(getByText(weightDropZone, 'sumEmployeesAge')).not.toBeNull();
},
);

test(
integrationTest(
'Query builder allows DND filter panel node to TDS fetch structure panel and keeps derived parameters independent',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { QueryBuilderSimpleProjectionColumnState } from '../../stores/fetch-structure/tds/projection/QueryBuilderProjectionColumnState.js';
import { useDragLayer, useDrop } from 'react-dnd';
import {
ExclamationCircleIcon,
PanelEntryDropZonePlaceholder,
} from '@finos/legend-art';
import {
QUERY_BUILDER_EXPLORER_TREE_DND_TYPE,
buildPropertyExpressionFromExplorerTreeNodeData,
type QueryBuilderExplorerTreeDragSource,
} from '../../stores/explorer/QueryBuilderExplorerState.js';
import { type QueryBuilderTDSPanelDropTarget } from './QueryBuilderTDSPanel.js';
import { QueryBuilderAggregateOperator_Wavg } from '../../stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Wavg.js';
import type { QueryBuilderTDSState } from '../../stores/fetch-structure/tds/QueryBuilderTDSState.js';
import { QUERY_BUILDER_TEST_ID } from '../../__lib__/QueryBuilderTesting.js';

export const WavgParamDNDZone = (props: {
column: QueryBuilderAggregateOperator_Wavg;
tdsState: QueryBuilderTDSState;
}): React.ReactNode => {
const { column, tdsState } = props;

const handleDrop = (item: QueryBuilderTDSPanelDropTarget): void => {
const projectionColumnState = new QueryBuilderSimpleProjectionColumnState(
tdsState,
buildPropertyExpressionFromExplorerTreeNodeData(
(item as QueryBuilderExplorerTreeDragSource).node,
tdsState.queryBuilderState.explorerState,
),
tdsState.queryBuilderState.explorerState.humanizePropertyName,
);
if (
column instanceof QueryBuilderAggregateOperator_Wavg &&
column.isCompatibleWithColumn(projectionColumnState)
) {
column.setWeight(
projectionColumnState.propertyExpressionState.propertyExpression,
);
}
};

const [{ isDragOver }, dropWavgParam] = useDrop<
QueryBuilderTDSPanelDropTarget,
void,
{ isDragOver: boolean }
>(
() => ({
accept: QUERY_BUILDER_EXPLORER_TREE_DND_TYPE.PRIMITIVE_PROPERTY,
drop: (item, monitor): void => {
handleDrop(item);
},
collect: (monitor) => ({
isDragOver: monitor.isOver({ shallow: true }),
}),
}),
[handleDrop],
);

const { isDroppable } = useDragLayer((monitor) => ({
isDroppable:
monitor.isDragging() &&
QUERY_BUILDER_EXPLORER_TREE_DND_TYPE.PRIMITIVE_PROPERTY.includes(
monitor.getItemType()?.toString() ?? '',
),
}));

const weightIsSet = () => column.weight !== undefined;

const getWeightName = (): string => {
if (column instanceof QueryBuilderAggregateOperator_Wavg) {
return column.weight?.func.value.name ?? '';
} else {
return '';
}
};

return (
<div
data-testid={QUERY_BUILDER_TEST_ID.QUERY_BUILDER_WAVG_DROPZONE}
ref={dropWavgParam}
>
<div>
<PanelEntryDropZonePlaceholder
isDragOver={isDragOver}
isDroppable={isDroppable}
label="Drop Numeric Property"
>
<div
className={
weightIsSet()
? 'query-builder__projection__column__aggregate__operator__percentile__badge'
: 'query-builder__projection__column__aggregate__operator__percentile__badge__unset'
}
>
{!weightIsSet() && (
<>
<ExclamationCircleIcon /> Drop weight value{' '}
</>
)}
{weightIsSet() && getWeightName()}
</div>
</PanelEntryDropZonePlaceholder>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ import {
} from '../../stores/filter/QueryBuilderFilterState.js';
import { cloneAbstractPropertyExpression } from '../../stores/shared/ValueSpecificationEditorHelper.js';
import { buildPropertyExpressionFromExistsNode } from '../filter/QueryBuilderFilterPanel.js';
import { QueryBuilderAggregateOperator_Wavg } from '../../stores/fetch-structure/tds/aggregation/operators/QueryBuilderAggregateOperator_Wavg.js';
import { WavgParamDNDZone } from './QueryBuilderAggParam.js';

const CAN_DROP_MAIN_GROUP_DND_TYPES = [
QUERY_BUILDER_EXPLORER_TREE_DND_TYPE.ENUM_PROPERTY,
Expand All @@ -143,8 +145,7 @@ const CAN_DROP_DERIVATION_PROJECTION_COLUMN_DND_TYPES = [
QUERY_BUILDER_VARIABLE_DND_TYPE,
QUERY_BUILDER_FUNCTION_DND_TYPE,
];

type QueryBuilderTDSPanelDropTarget =
export type QueryBuilderTDSPanelDropTarget =
| QueryBuilderExplorerTreeDragSource
| QueryBuilderFunctionsExplorerDragSource
| QueryBuilderFilterConditionDragSource;
Expand Down Expand Up @@ -899,6 +900,13 @@ const QueryBuilderProjectionColumnEditor = observer(
{aggregateColumnState.operator.getLabel(
projectionColumnState,
)}
{aggregateColumnState.operator instanceof
QueryBuilderAggregateOperator_Wavg && (
<WavgParamDNDZone
column={aggregateColumnState.operator}
tdsState={tdsState}
/>
)}
{aggregateColumnState.operator instanceof
QueryBuilderAggregateOperator_Percentile && (
<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ const buildProjectionColumnLambda = (
V1_Lambda,
`Can't build projection column: only support lambda`,
);

// lambda parameter
assertTrue(
valueSpecification.parameters.length === 1,
Expand All @@ -106,6 +105,24 @@ const buildProjectionColumnLambda = (
let currentPropertyExpression: V1_ValueSpecification = valueSpecification
.body[0] as V1_ValueSpecification;

//catch if wavg row mapper
if (
currentPropertyExpression instanceof V1_AppliedFunction &&
currentPropertyExpression.parameters.length === 2 &&
matchFunctionName(
currentPropertyExpression.function,
QUERY_BUILDER_SUPPORTED_FUNCTIONS.WAVG_ROW_MAPPER,
)
) {
return valueSpecification.accept_ValueSpecificationVisitor(
new V1_ValueSpecificationBuilder(
compileContext,
processingContext,
openVariables,
),
);
}

assertType(
currentPropertyExpression,
V1_AppliedProperty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ export enum QUERY_BUILDER_SUPPORTED_FUNCTIONS {
STD_DEV_SAMPLE = 'meta::pure::functions::math::stdDevSample',
SUM = 'meta::pure::functions::math::sum',
UNIQUE_VALUE_ONLY = 'meta::pure::functions::collection::uniqueValueOnly',
WAVG = 'meta::pure::functions::math::wavg',
WAVG_ROW_MAPPER = 'meta::pure::functions::math::wavgUtility::wavgRowMapper',

// watermark
WATERMARK = 'meta::datalake::functions::forWatermark',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { processFilterExpression } from './filter/QueryBuilderFilterStateBuilder
import {
processTDSAggregateExpression,
processTDSGroupByExpression,
processWAVGRowMapperExpression,
} from './fetch-structure/tds/aggregation/QueryBuilderAggregationStateBuilder.js';
import {
processGraphFetchExpression,
Expand Down Expand Up @@ -834,6 +835,19 @@ export class QueryBuilderValueSpecificationProcessor
this.queryBuilderState,
);
return;
} else if (
matchFunctionName(
functionName,
QUERY_BUILDER_SUPPORTED_FUNCTIONS.WAVG_ROW_MAPPER,
)
) {
processWAVGRowMapperExpression(
valueSpecification,
this.parentExpression,
this.queryBuilderState,
this.parentLambda,
);
return;
}
throw new UnsupportedOperationError(
`Can't process expression of function ${functionName}()`,
Expand All @@ -851,13 +865,13 @@ export class QueryBuilderValueSpecificationProcessor
this.parentExpression,
`Can't process property expression: parent expression cannot be retrieved`,
);

if (
matchFunctionName(this.parentExpression.functionName, [
QUERY_BUILDER_SUPPORTED_FUNCTIONS.TDS_PROJECT,
QUERY_BUILDER_SUPPORTED_FUNCTIONS.RELATION_PROJECT,
QUERY_BUILDER_SUPPORTED_FUNCTIONS.TDS_GROUP_BY,
QUERY_BUILDER_SUPPORTED_FUNCTIONS.TDS_AGG,
QUERY_BUILDER_SUPPORTED_FUNCTIONS.WAVG_ROW_MAPPER,
...Object.values(
QUERY_BUILDER_SUPPORTED_CALENDAR_AGGREGATION_FUNCTIONS,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export enum QUERY_BUILDER_STATE_HASH_STRUCTURE {
AGGREGATE_OPERATOR_STD_DEV_POPULATION = 'AGGREGATE_OPERATOR_STD_DEV_POPULATION',
AGGREGATE_OPERATOR_STD_DEV_SAMPLE = 'AGGREGATE_OPERATOR_STD_DEV_SAMPLE',
AGGREGATE_OPERATOR_SUM = 'AGGREGATE_OPERATOR_SUM',
AGGREGATE_OPERATOR_WAVG = 'AGGREGATE_OPERATOR_WAVG',
AGGREGATE_COLUMN_STATE = 'AGGREGATE_COLUMN_STATE',
AGGREGATE_OPERATOR_PERCENTILE = 'AGGREGATE_OPERATOR_PERCENTILE',
AGGREGATION_STATE = 'AGGREGATION_STATE',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
TEST_DATA__simpleProjectionWithSlice,
TEST_DATA_simpleTypedRelationProjection,
TEST_DATA__projectionWithPercentileAggregation,
TEST_DATA__projectionWithWAVGAggregation,
} from './TEST_DATA__QueryBuilder_Generic.js';
import TEST_DATA__ComplexRelationalModel from './TEST_DATA__QueryBuilder_Model_ComplexRelational.json' with { type: 'json' };
import TEST_DATA__ComplexM2MModel from './TEST_DATA__QueryBuilder_Model_ComplexM2M.json' with { type: 'json' };
Expand Down Expand Up @@ -284,6 +285,12 @@ const cases: RoundtripTestCase[] = [
TEST_DATA__projectionWithPercentileAggregation,
undefined,
],
[
'Projection column with wavg aggregation',
projectionCtx,
TEST_DATA__projectionWithWAVGAggregation,
undefined,
],
// graph fetch
['Simple graph fetch', graphFetchCtx, TEST_DATA__simpleGraphFetch, undefined],
[
Expand Down
Loading

0 comments on commit a5ca813

Please sign in to comment.