Skip to content

Commit cedbcc0

Browse files
authored
Merge pull request #1130 from merico-dev/1129-aggregation-by-a-custom-function
1129 aggregation by a custom function
2 parents 67c41bd + cb13dc2 commit cedbcc0

File tree

16 files changed

+143
-18
lines changed

16 files changed

+143
-18
lines changed

api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/api",
3-
"version": "10.18.1",
3+
"version": "10.19.0",
44
"description": "",
55
"main": "index.js",
66
"scripts": {

dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtable/dashboard",
3-
"version": "10.18.1",
3+
"version": "10.19.0",
44
"license": "Apache-2.0",
55
"publishConfig": {
66
"access": "public",

dashboard/src/components/panel/settings/common/aggregation-selector.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
import { Group, NumberInput, Select, SpacingValue, Sx, SystemProp } from '@mantine/core';
1+
import { Group, NumberInput, Select, SpacingValue, SystemProp } from '@mantine/core';
2+
import { IconMathFunction } from '@tabler/icons-react';
23
import React, { useEffect } from 'react';
3-
import { AggregationType } from '~/utils/aggregation';
4+
import { InlineFunctionInput } from '~/components/widgets/inline-function-input';
5+
import { ModalFunctionEditor } from '~/components/widgets/modal-function-editor';
6+
import { AggregationType, DefaultCustomAggregationFunc } from '~/utils/aggregation';
47

58
const options: { label: string; value: AggregationType['type'] }[] = [
69
{ label: 'None', value: 'none' },
@@ -12,6 +15,7 @@ const options: { label: string; value: AggregationType['type'] }[] = [
1215
{ label: 'Coefficient of Variation', value: 'CV' },
1316
{ label: 'Standard Variation', value: 'std' },
1417
{ label: 'Quantile(99%, 95%, ...)', value: 'quantile' },
18+
{ label: 'Custom', value: 'custom' },
1519
];
1620

1721
interface IAggregationSelector {
@@ -25,7 +29,6 @@ function _AggregationSelector({ label, value, onChange, pt = 'sm' }: IAggregatio
2529
// migrate from legacy
2630
useEffect(() => {
2731
if (typeof value === 'string') {
28-
console.log(value);
2932
onChange({
3033
type: value,
3134
config: {},
@@ -36,6 +39,8 @@ function _AggregationSelector({ label, value, onChange, pt = 'sm' }: IAggregatio
3639
const changeType = (type: AggregationType['type']) => {
3740
if (type === 'quantile') {
3841
onChange({ type: 'quantile', config: { p: 0.99 } });
42+
} else if (type === 'custom') {
43+
onChange({ type: 'custom', config: { func: DefaultCustomAggregationFunc } });
3944
} else {
4045
onChange({ type, config: {} });
4146
}
@@ -49,6 +54,15 @@ function _AggregationSelector({ label, value, onChange, pt = 'sm' }: IAggregatio
4954
},
5055
});
5156
};
57+
58+
const changeCustomFunc = (func: TFunctionString) => {
59+
onChange({
60+
type: 'custom',
61+
config: {
62+
func,
63+
},
64+
});
65+
};
5266
return (
5367
<Group grow noWrap pt={pt}>
5468
<Select ref={ref} label={label} data={options} value={value.type} onChange={changeType} />
@@ -63,6 +77,20 @@ function _AggregationSelector({ label, value, onChange, pt = 'sm' }: IAggregatio
6377
max={1}
6478
/>
6579
)}
80+
{value.type === 'custom' && (
81+
<ModalFunctionEditor
82+
label=""
83+
triggerLabel="Edit Function"
84+
value={value.config.func}
85+
onChange={changeCustomFunc}
86+
defaultValue={DefaultCustomAggregationFunc}
87+
triggerButtonProps={{
88+
size: 'xs',
89+
sx: { flexGrow: 0, alignSelf: 'center', marginTop: '22px' },
90+
leftIcon: <IconMathFunction size={16} />,
91+
}}
92+
/>
93+
)}
6694
</Group>
6795
);
6896
}

dashboard/src/components/plugins/viz-components/cartesian/editors/y-axes.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function YAxisField({ control, index, remove }: IYAxisField) {
3535
control={control}
3636
render={({ field }) => (
3737
// @ts-expect-error type of onChange
38-
<Select label="Align" required data={nameAlignmentOptions} sx={{ flex: 1 }} {...field} />
38+
<Select label="Name Anchor" required data={nameAlignmentOptions} sx={{ flex: 1 }} {...field} />
3939
)}
4040
/>
4141
</Group>

dashboard/src/components/plugins/viz-components/cartesian/option/series/reference_lines.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function getReferenceLines(
2323
data: [
2424
{
2525
name: r.name,
26-
[keyOfAxis]: Number(variableValueMap[r.variable_key]),
26+
[keyOfAxis]: variableValueMap[r.variable_key],
2727
},
2828
],
2929
silent: true,

dashboard/src/components/widgets/about-function-utils/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function AboutFunctionUtils() {
99

1010
return (
1111
<>
12-
<Modal opened={opened} onClose={close} title="About FunctionUtils" zIndex={320}>
12+
<Modal opened={opened} onClose={close} title="About FunctionUtils" zIndex={330} withinPortal>
1313
<ReadonlyRichText
1414
value={FunctionUtilsDescription}
1515
styles={{ root: { border: 'none' }, content: { padding: 0, table: { marginBottom: 0 } } }}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Box, Button, Group, Modal } from '@mantine/core';
2+
import { useDisclosure } from '@mantine/hooks';
3+
import { forwardRef } from 'react';
4+
import { InlineFunctionInput } from '../inline-function-input';
5+
import { AnyObject } from '~/types';
6+
7+
interface Props {
8+
value: TFunctionString;
9+
onChange: (v: TFunctionString) => void;
10+
defaultValue: TFunctionString;
11+
label: string;
12+
triggerLabel?: string;
13+
triggerButtonProps?: AnyObject;
14+
}
15+
16+
export const ModalFunctionEditor = forwardRef(
17+
({ value, onChange, label, triggerLabel = 'Edit', triggerButtonProps = {}, defaultValue }: Props, _ref: any) => {
18+
const [opened, { open, close }] = useDisclosure(false);
19+
20+
return (
21+
<>
22+
<Modal opened={opened} onClose={close} title="Authentication" withinPortal zIndex={320} size="900px">
23+
<Box h={600}>
24+
<InlineFunctionInput value={value} onChange={onChange} defaultValue={defaultValue} label={label} />
25+
</Box>
26+
</Modal>
27+
28+
<Button onClick={open} {...triggerButtonProps}>
29+
{triggerLabel}
30+
</Button>
31+
</>
32+
);
33+
},
34+
);

dashboard/src/dashboard-editor/ui/settings/content/edit-panel/variable-config/variable-field.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,12 @@ export const TemplateVariableField = React.forwardRef(function _TemplateVariable
4747
value={value.aggregation}
4848
onChange={(v) => handleChange('aggregation', v)}
4949
/>
50-
51-
<Divider my="xs" label="Format" labelPosition="center" />
52-
<NumbroFormatSelector value={value.formatter} onChange={(v) => handleChange('formatter', v)} />
50+
{value.aggregation.type !== 'custom' && (
51+
<>
52+
<Divider my="xs" label="Format" labelPosition="center" />
53+
<NumbroFormatSelector value={value.formatter} onChange={(v) => handleChange('formatter', v)} />
54+
</>
55+
)}
5356

5457
{withStyle && <TemplateVariableStyleField value={value} onChange={onChange} />}
5558
</Box>

dashboard/src/model/meta-model/dashboard/content/panel/variable.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export const VariableMeta = types
3232
type: types.literal('quantile'),
3333
config: types.model({ p: types.number }),
3434
}),
35+
types.model({
36+
type: types.literal('custom'),
37+
config: types.model({ func: types.string }),
38+
}),
3539
),
3640
})
3741
.views((self) => ({

dashboard/src/utils/aggregation.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
11
import { quantile } from 'd3-array';
22
import _ from 'lodash';
33
import * as math from 'mathjs';
4-
import { extractData } from './data';
4+
import { extractData, extractFullQueryData } from './data';
5+
import { functionUtils } from './function-utils';
6+
7+
export type TCustomAggregation = {
8+
type: 'custom';
9+
config: {
10+
func: string;
11+
};
12+
};
13+
export const DefaultCustomAggregationFunc = [
14+
'function aggregation({ queryData }, utils) {',
15+
' return "Aggregation Result";',
16+
'}',
17+
].join('\n');
518

619
export type AggregationType =
720
| {
@@ -13,7 +26,8 @@ export type AggregationType =
1326
config: {
1427
p: number;
1528
};
16-
};
29+
}
30+
| TCustomAggregation;
1731
export const DefaultAggregation: AggregationType = {
1832
type: 'none',
1933
config: {},
@@ -71,8 +85,21 @@ export function formatNumbersAndAggregateValue(possibleNumbers: Array<string | n
7185
return aggregateValueFromNumbers(numbers, aggregation);
7286
}
7387

88+
function runCustomAggregation(data: TPanelData, data_field: string, aggregation: TCustomAggregation) {
89+
try {
90+
const queryData = extractFullQueryData(data, data_field);
91+
return new Function(`return ${aggregation.config.func}`)()({ queryData }, functionUtils);
92+
} catch (error) {
93+
console.error(error);
94+
return (error as any).message;
95+
}
96+
}
97+
7498
export function aggregateValue(data: TPanelData, data_field: string, aggregation: AggregationType) {
7599
try {
100+
if (aggregation.type === 'custom') {
101+
return runCustomAggregation(data, data_field, aggregation);
102+
}
76103
return formatNumbersAndAggregateValue(extractData(data, data_field), aggregation);
77104
} catch (error) {
78105
console.error(error);

0 commit comments

Comments
 (0)