Skip to content

Commit cbfc655

Browse files
committed
[Lens] Calculation operations (elastic#83789)
# Conflicts: # x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts # x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts
1 parent 79fa876 commit cbfc655

File tree

14 files changed

+701
-6
lines changed

14 files changed

+701
-6
lines changed

x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export function WorkspacePanel({
161161

162162
const expression = useMemo(
163163
() => {
164-
if (!configurationValidationError) {
164+
if (!configurationValidationError || configurationValidationError.length === 0) {
165165
try {
166166
return buildExpression({
167167
visualization: activeVisualization,

x-pack/plugins/lens/public/indexpattern_datasource/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@ export class IndexPatternDatasource {
3737
getIndexPatternDatasource,
3838
renameColumns,
3939
formatColumn,
40+
counterRate,
4041
getTimeScaleFunction,
4142
getSuffixFormatter,
4243
} = await import('../async_services');
4344
return core.getStartServices().then(([coreStart, { data }]) => {
4445
data.fieldFormats.register([getSuffixFormatter(data.fieldFormats.deserialize)]);
4546
expressions.registerFunction(getTimeScaleFunction(data));
47+
expressions.registerFunction(counterRate);
4648
expressions.registerFunction(renameColumns);
4749
expressions.registerFunction(formatColumn);
4850
return getIndexPatternDatasource({

x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -599,9 +599,39 @@ describe('IndexPattern Data Source', () => {
599599

600600
describe('getTableSpec', () => {
601601
it('should include col1', () => {
602-
expect(publicAPI.getTableSpec()).toEqual([
603-
{
604-
columnId: 'col1',
602+
expect(publicAPI.getTableSpec()).toEqual([{ columnId: 'col1' }]);
603+
});
604+
605+
it('should skip columns that are being referenced', () => {
606+
publicAPI = indexPatternDatasource.getPublicAPI({
607+
state: {
608+
...enrichBaseState(baseState),
609+
layers: {
610+
first: {
611+
indexPatternId: '1',
612+
columnOrder: ['col1', 'col2'],
613+
columns: {
614+
col1: {
615+
label: 'Sum',
616+
dataType: 'number',
617+
isBucketed: false,
618+
619+
operationType: 'sum',
620+
sourceField: 'test',
621+
params: {},
622+
} as IndexPatternColumn,
623+
col2: {
624+
label: 'Cumulative sum',
625+
dataType: 'number',
626+
isBucketed: false,
627+
628+
operationType: 'cumulative_sum',
629+
references: ['col1'],
630+
params: {},
631+
} as IndexPatternColumn,
632+
},
633+
},
634+
},
605635
},
606636
]);
607637
});

x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri
7878
export * from './rename_columns';
7979
export * from './format_column';
8080
export * from './time_scale';
81+
export * from './counter_rate';
8182
export * from './suffix_formatter';
8283

8384
export function getIndexPatternDatasource({
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { i18n } from '@kbn/i18n';
8+
import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types';
9+
import { IndexPatternLayer } from '../../../types';
10+
import { checkForDateHistogram, dateBasedOperationToExpression, hasDateField } from './utils';
11+
import { OperationDefinition } from '..';
12+
13+
const ofName = (name?: string) => {
14+
return i18n.translate('xpack.lens.indexPattern.CounterRateOf', {
15+
defaultMessage: 'Counter rate of {name}',
16+
values: {
17+
name:
18+
name ??
19+
i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
20+
defaultMessage: '(incomplete)',
21+
}),
22+
},
23+
});
24+
};
25+
26+
export type CounterRateIndexPatternColumn = FormattedIndexPatternColumn &
27+
ReferenceBasedIndexPatternColumn & {
28+
operationType: 'counter_rate';
29+
};
30+
31+
export const counterRateOperation: OperationDefinition<
32+
CounterRateIndexPatternColumn,
33+
'fullReference'
34+
> = {
35+
type: 'counter_rate',
36+
priority: 1,
37+
displayName: i18n.translate('xpack.lens.indexPattern.counterRate', {
38+
defaultMessage: 'Counter rate',
39+
}),
40+
input: 'fullReference',
41+
selectionStyle: 'field',
42+
requiredReferences: [
43+
{
44+
input: ['field'],
45+
specificOperations: ['max'],
46+
validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed,
47+
},
48+
],
49+
getPossibleOperation: () => {
50+
return {
51+
dataType: 'number',
52+
isBucketed: false,
53+
scale: 'ratio',
54+
};
55+
},
56+
getDefaultLabel: (column, indexPattern, columns) => {
57+
return ofName(columns[column.references[0]]?.label);
58+
},
59+
toExpression: (layer, columnId) => {
60+
return dateBasedOperationToExpression(layer, columnId, 'lens_counter_rate');
61+
},
62+
buildColumn: ({ referenceIds, previousColumn, layer }) => {
63+
const metric = layer.columns[referenceIds[0]];
64+
return {
65+
label: ofName(metric?.label),
66+
dataType: 'number',
67+
operationType: 'counter_rate',
68+
isBucketed: false,
69+
scale: 'ratio',
70+
references: referenceIds,
71+
params:
72+
previousColumn?.dataType === 'number' &&
73+
previousColumn.params &&
74+
'format' in previousColumn.params &&
75+
previousColumn.params.format
76+
? { format: previousColumn.params.format }
77+
: undefined,
78+
};
79+
},
80+
isTransferable: (column, newIndexPattern) => {
81+
return hasDateField(newIndexPattern);
82+
},
83+
getErrorMessage: (layer: IndexPatternLayer) => {
84+
return checkForDateHistogram(
85+
layer,
86+
i18n.translate('xpack.lens.indexPattern.counterRate', {
87+
defaultMessage: 'Counter rate',
88+
})
89+
);
90+
},
91+
};
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { i18n } from '@kbn/i18n';
8+
import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types';
9+
import { IndexPatternLayer } from '../../../types';
10+
import { checkForDateHistogram, dateBasedOperationToExpression } from './utils';
11+
import { OperationDefinition } from '..';
12+
13+
const ofName = (name?: string) => {
14+
return i18n.translate('xpack.lens.indexPattern.cumulativeSumOf', {
15+
defaultMessage: 'Cumulative sum rate of {name}',
16+
values: {
17+
name:
18+
name ??
19+
i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
20+
defaultMessage: '(incomplete)',
21+
}),
22+
},
23+
});
24+
};
25+
26+
export type CumulativeSumIndexPatternColumn = FormattedIndexPatternColumn &
27+
ReferenceBasedIndexPatternColumn & {
28+
operationType: 'cumulative_sum';
29+
};
30+
31+
export const cumulativeSumOperation: OperationDefinition<
32+
CumulativeSumIndexPatternColumn,
33+
'fullReference'
34+
> = {
35+
type: 'cumulative_sum',
36+
priority: 1,
37+
displayName: i18n.translate('xpack.lens.indexPattern.cumulativeSum', {
38+
defaultMessage: 'Cumulative sum',
39+
}),
40+
input: 'fullReference',
41+
selectionStyle: 'field',
42+
requiredReferences: [
43+
{
44+
input: ['field'],
45+
specificOperations: ['count', 'sum'],
46+
validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed,
47+
},
48+
],
49+
getPossibleOperation: () => {
50+
return {
51+
dataType: 'number',
52+
isBucketed: false,
53+
scale: 'ratio',
54+
};
55+
},
56+
getDefaultLabel: (column, indexPattern, columns) => {
57+
return ofName(columns[column.references[0]]?.label);
58+
},
59+
toExpression: (layer, columnId) => {
60+
return dateBasedOperationToExpression(layer, columnId, 'cumulative_sum');
61+
},
62+
buildColumn: ({ referenceIds, previousColumn, layer }) => {
63+
const metric = layer.columns[referenceIds[0]];
64+
return {
65+
label: ofName(metric?.label),
66+
dataType: 'number',
67+
operationType: 'cumulative_sum',
68+
isBucketed: false,
69+
scale: 'ratio',
70+
references: referenceIds,
71+
params:
72+
previousColumn?.dataType === 'number' &&
73+
previousColumn.params &&
74+
'format' in previousColumn.params &&
75+
previousColumn.params.format
76+
? { format: previousColumn.params.format }
77+
: undefined,
78+
};
79+
},
80+
isTransferable: () => {
81+
return true;
82+
},
83+
getErrorMessage: (layer: IndexPatternLayer) => {
84+
return checkForDateHistogram(
85+
layer,
86+
i18n.translate('xpack.lens.indexPattern.cumulativeSum', {
87+
defaultMessage: 'Cumulative sum',
88+
})
89+
);
90+
},
91+
};
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { i18n } from '@kbn/i18n';
8+
import { FormattedIndexPatternColumn, ReferenceBasedIndexPatternColumn } from '../column_types';
9+
import { IndexPatternLayer } from '../../../types';
10+
import { checkForDateHistogram, dateBasedOperationToExpression, hasDateField } from './utils';
11+
import { OperationDefinition } from '..';
12+
13+
const ofName = (name?: string) => {
14+
return i18n.translate('xpack.lens.indexPattern.derivativeOf', {
15+
defaultMessage: 'Differences of {name}',
16+
values: {
17+
name:
18+
name ??
19+
i18n.translate('xpack.lens.indexPattern.incompleteOperation', {
20+
defaultMessage: '(incomplete)',
21+
}),
22+
},
23+
});
24+
};
25+
26+
export type DerivativeIndexPatternColumn = FormattedIndexPatternColumn &
27+
ReferenceBasedIndexPatternColumn & {
28+
operationType: 'derivative';
29+
};
30+
31+
export const derivativeOperation: OperationDefinition<
32+
DerivativeIndexPatternColumn,
33+
'fullReference'
34+
> = {
35+
type: 'derivative',
36+
priority: 1,
37+
displayName: i18n.translate('xpack.lens.indexPattern.derivative', {
38+
defaultMessage: 'Differences',
39+
}),
40+
input: 'fullReference',
41+
selectionStyle: 'full',
42+
requiredReferences: [
43+
{
44+
input: ['field'],
45+
validateMetadata: (meta) => meta.dataType === 'number' && !meta.isBucketed,
46+
},
47+
],
48+
getPossibleOperation: () => {
49+
return {
50+
dataType: 'number',
51+
isBucketed: false,
52+
scale: 'ratio',
53+
};
54+
},
55+
getDefaultLabel: (column, indexPattern, columns) => {
56+
return ofName(columns[column.references[0]]?.label);
57+
},
58+
toExpression: (layer, columnId) => {
59+
return dateBasedOperationToExpression(layer, columnId, 'derivative');
60+
},
61+
buildColumn: ({ referenceIds, previousColumn, layer }) => {
62+
const metric = layer.columns[referenceIds[0]];
63+
return {
64+
label: ofName(metric?.label),
65+
dataType: 'number',
66+
operationType: 'derivative',
67+
isBucketed: false,
68+
scale: 'ratio',
69+
references: referenceIds,
70+
params:
71+
previousColumn?.dataType === 'number' &&
72+
previousColumn.params &&
73+
'format' in previousColumn.params &&
74+
previousColumn.params.format
75+
? { format: previousColumn.params.format }
76+
: undefined,
77+
};
78+
},
79+
isTransferable: (column, newIndexPattern) => {
80+
return hasDateField(newIndexPattern);
81+
},
82+
getErrorMessage: (layer: IndexPatternLayer) => {
83+
return checkForDateHistogram(
84+
layer,
85+
i18n.translate('xpack.lens.indexPattern.derivative', {
86+
defaultMessage: 'Differences',
87+
})
88+
);
89+
},
90+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export { counterRateOperation, CounterRateIndexPatternColumn } from './counter_rate';
8+
export { cumulativeSumOperation, CumulativeSumIndexPatternColumn } from './cumulative_sum';
9+
export { derivativeOperation, DerivativeIndexPatternColumn } from './derivative';
10+
export { movingAverageOperation, MovingAverageIndexPatternColumn } from './moving_average';

0 commit comments

Comments
 (0)