Skip to content

Commit

Permalink
[Lens] Fix context formula functions (#172710)
Browse files Browse the repository at this point in the history
## Summary
Fix #170762



https://github.com/elastic/kibana/assets/315764/f5b50ffa-4a03-45ee-bc7a-2f2aca7fa3bd



### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
drewdaemon and kibanamachine authored Dec 19, 2023
1 parent d79f191 commit 99763dc
Show file tree
Hide file tree
Showing 23 changed files with 264 additions and 171 deletions.
1 change: 1 addition & 0 deletions packages/kbn-es-query/src/expressions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { Filter, Query, TimeRange } from '../filters';

export interface ExecutionContextSearch {
now?: number;
filters?: Filter[];
query?: Query | Query[];
timeRange?: TimeRange;
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pageLoadAssetSize:
kibanaUsageCollection: 16463
kibanaUtils: 79713
kubernetesSecurity: 77234
lens: 39000
lens: 41000
licenseManagement: 41817
licensing: 29004
links: 44490
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/plugins/data/common/search/expressions/kibana.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('interpreter/functions#kibana', () => {
beforeEach(() => {
input = { timeRange: { from: '0', to: '1' } };
search = {
now: 0,
type: 'kibana_context',
query: { language: 'lucene', query: 'geo.src:US' },
filters: [
Expand Down
1 change: 1 addition & 0 deletions src/plugins/data/common/search/expressions/kibana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const kibana: ExpressionFunctionKibana = {
// TODO: But it shouldn't be need.
...input,
type: 'kibana_context',
now: getSearchContext().now ?? Date.now(),
query: [...toArray(getSearchContext().query), ...toArray((input || {}).query)],
filters: [...(getSearchContext().filters || []), ...((input || {}).filters || [])],
timeRange: getSearchContext().timeRange || (input ? input.timeRange : undefined),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export * from './overall_metric';
export * from './derivative';
export * from './moving_average';
export * from './ui_setting';
export * from './math_column';
export type { MapColumnArguments } from './map_column';
export { mapColumn } from './map_column';
export type { MathArguments, MathInput } from './math';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ export type MathColumnArguments = MathArguments & {
copyMetaFrom?: string | null;
};

export const mathColumn: ExpressionFunctionDefinition<
export type ExpressionFunctionMathColumn = ExpressionFunctionDefinition<
'mathColumn',
Datatable,
MathColumnArguments,
Promise<Datatable>
> = {
>;

export const mathColumn: ExpressionFunctionMathColumn = {
name: 'mathColumn',
type: 'datatable',
inputTypes: ['datatable'],
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/expressions/common/expression_functions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ExpressionFunctionDerivative,
ExpressionFunctionMovingAverage,
ExpressionFunctionOverallMetric,
ExpressionFunctionMathColumn,
} from './specs';
import { ExpressionAstFunction } from '../ast';

Expand Down Expand Up @@ -132,4 +133,5 @@ export interface ExpressionFunctionDefinitions {
overall_metric: ExpressionFunctionOverallMetric;
derivative: ExpressionFunctionDerivative;
moving_average: ExpressionFunctionMovingAverage;
math_column: ExpressionFunctionMathColumn;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { ExecutionContext } from '@kbn/expressions-plugin/common';
import { Adapters } from '@kbn/inspector-plugin/common';
import { formulaIntervalFn, formulaNowFn, formulaTimeRangeFn } from './context_fns';

describe('interval', () => {
it('should return 0 if no time range available', () => {
// (not sure if this case is actually possible)
const result = formulaIntervalFn.fn(undefined, { targetBars: 100 }, {
getSearchContext: () => ({
/* no time range */
}),
} as ExecutionContext<Adapters>);
expect(result).toEqual(0);
});

it('should return 0 if no targetBars is passed', () => {
const result = formulaIntervalFn.fn(
undefined,
{
/* no targetBars */
},
{
getSearchContext: () => ({
timeRange: {
from: 'now-15m',
to: 'now',
},
}),
} as ExecutionContext<Adapters>
);
expect(result).toEqual(0);
});

it('should return a valid value > 0 if both timeRange and targetBars is passed', () => {
const result = formulaIntervalFn.fn(undefined, { targetBars: 100 }, {
getSearchContext: () => ({
timeRange: {
from: 'now-15m',
to: 'now',
},
}),
} as ExecutionContext<Adapters>);
expect(result).toEqual(10000);
});
});

describe('time range', () => {
it('should return 0 if no time range is available', () => {
// (not sure if this case is actually possible)
const result = formulaTimeRangeFn.fn(undefined, {}, {
getSearchContext: () => ({
/* no time range */
}),
} as ExecutionContext<Adapters>);
expect(result).toEqual(0);
});

it('should return a valid value > 0 if time range is available', () => {
const result = formulaTimeRangeFn.fn(undefined, {}, {
getSearchContext: () => ({
timeRange: {
from: 'now-15m',
to: 'now',
},
now: 1000000, // important to provide this to make the result consistent
}),
} as ExecutionContext<Adapters>);

expect(result).toBe(900000);
});
});

describe('now', () => {
it('should return the now value when passed', () => {
const now = 123456789;
expect(
formulaNowFn.fn(undefined, {}, {
getSearchContext: () => ({
now,
}),
} as ExecutionContext<Adapters>)
).toEqual(now);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { getAbsoluteTimeRange, calcAutoIntervalNear } from '@kbn/data-plugin/common';
import type { TimeRange } from '@kbn/es-query';
import type { ExpressionFunctionDefinition } from '@kbn/expressions-plugin/common';
import moment from 'moment';
import { i18n } from '@kbn/i18n';

export type ExpressionFunctionFormulaTimeRange = ExpressionFunctionDefinition<
'formula_time_range',
undefined,
object,
number
>;

const getTimeRangeAsNumber = (timeRange: TimeRange | undefined, now: number | undefined) => {
if (!timeRange) return 0;
const absoluteTimeRange = getAbsoluteTimeRange(
timeRange,
now != null ? { forceNow: new Date(now) } : {}
);
return timeRange ? moment(absoluteTimeRange.to).diff(moment(absoluteTimeRange.from)) : 0;
};

export const formulaTimeRangeFn: ExpressionFunctionFormulaTimeRange = {
name: 'formula_time_range',

help: i18n.translate('xpack.lens.formula.timeRange.help', {
defaultMessage: 'The specified time range, in milliseconds (ms).',
}),

args: {},

fn(_input, _args, { getSearchContext }) {
const { timeRange, now } = getSearchContext();
return getTimeRangeAsNumber(timeRange, now);
},
};

export type ExpressionFunctionFormulaInterval = ExpressionFunctionDefinition<
'formula_interval',
undefined,
{
targetBars?: number;
},
number
>;

export const formulaIntervalFn: ExpressionFunctionFormulaInterval = {
name: 'formula_interval',

help: i18n.translate('xpack.lens.formula.interval.help', {
defaultMessage: 'The specified minimum interval for the date histogram, in milliseconds (ms).',
}),

args: {
targetBars: {
types: ['number'],
help: i18n.translate('xpack.lens.formula.interval.targetBars.help', {
defaultMessage: 'The target number of bars for the date histogram.',
}),
},
},

fn(_input, args, { getSearchContext }) {
const { timeRange, now } = getSearchContext();
return timeRange && args.targetBars
? calcAutoIntervalNear(args.targetBars, getTimeRangeAsNumber(timeRange, now)).asMilliseconds()
: 0;
},
};

export type ExpressionFunctionFormulaNow = ExpressionFunctionDefinition<
'formula_now',
undefined,
object,
number
>;

export const formulaNowFn: ExpressionFunctionFormulaNow = {
name: 'formula_now',

help: i18n.translate('xpack.lens.formula.now.help', {
defaultMessage: 'The current now moment used in Kibana expressed in milliseconds (ms).',
}),

args: {},

fn(_input, _args, { getSearchContext }) {
return getSearchContext().now ?? Date.now();
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export * from './context_fns';
1 change: 1 addition & 0 deletions x-pack/plugins/lens/common/expressions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './format_column';
export * from './map_to_columns';
export * from './time_scale';
export * from './datatable';
export * from './formula_context';
Loading

0 comments on commit 99763dc

Please sign in to comment.