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

lens should register expression functions in setup contract #110639

Merged
merged 12 commits into from
Sep 7, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { functionWrapper } from 'src/plugins/expressions/common/expression_funct

describe('lens_counter_rate', () => {
const fn = functionWrapper(counterRate);
const runFn = (input: Datatable, args: CounterRateArgs) => fn(input, args) as Datatable;
const runFn = (input: Datatable, args: CounterRateArgs) => fn(input, args) as Promise<Datatable>;

it('calculates counter rate', () => {
const result = runFn(
it('calculates counter rate', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
Expand All @@ -31,8 +31,8 @@ describe('lens_counter_rate', () => {
expect(result.rows.map((row) => row.output)).toEqual([undefined, 0, 2, 3, 2]);
});

it('calculates counter rate with decreasing values in input', () => {
const result = runFn(
it('calculates counter rate with decreasing values in input', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
Expand All @@ -48,8 +48,8 @@ describe('lens_counter_rate', () => {
expect(result.rows.map((row) => row.output)).toEqual([undefined, 6, 5, 4]);
});

it('skips null or undefined values until there is real data', () => {
const result = runFn(
it('skips null or undefined values until there is real data', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
Expand Down Expand Up @@ -85,8 +85,8 @@ describe('lens_counter_rate', () => {
]);
});

it('treats 0 as real data', () => {
const result = runFn(
it('treats 0 as real data', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
Expand Down Expand Up @@ -123,8 +123,8 @@ describe('lens_counter_rate', () => {
]);
});

it('calculates counter rate for multiple series', () => {
const result = runFn(
it('calculates counter rate for multiple series', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [
Expand Down Expand Up @@ -157,8 +157,8 @@ describe('lens_counter_rate', () => {
]);
});

it('treats missing split column as separate series', () => {
const result = runFn(
it('treats missing split column as separate series', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [
Expand Down Expand Up @@ -190,8 +190,8 @@ describe('lens_counter_rate', () => {
]);
});

it('treats null like undefined and empty string for split columns', () => {
const result = runFn(
it('treats null like undefined and empty string for split columns', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [
Expand Down Expand Up @@ -225,8 +225,8 @@ describe('lens_counter_rate', () => {
]);
});

it('calculates counter rate for multiple series by multiple split columns', () => {
const result = runFn(
it('calculates counter rate for multiple series by multiple split columns', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [
Expand Down Expand Up @@ -259,8 +259,8 @@ describe('lens_counter_rate', () => {
]);
});

it('splits separate series by the string representation of the cell values', () => {
const result = runFn(
it('splits separate series by the string representation of the cell values', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [
Expand All @@ -280,8 +280,8 @@ describe('lens_counter_rate', () => {
expect(result.rows.map((row) => row.output)).toEqual([undefined, 2 - 1, undefined, 11 - 10]);
});

it('casts values to number before calculating counter rate', () => {
const result = runFn(
it('casts values to number before calculating counter rate', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
Expand All @@ -292,8 +292,8 @@ describe('lens_counter_rate', () => {
expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, 3, 2]);
});

it('casts values to number before calculating counter rate for NaN like values', () => {
const result = runFn(
it('casts values to number before calculating counter rate for NaN like values', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [{ id: 'val', name: 'val', meta: { type: 'number' } }],
Expand All @@ -304,8 +304,8 @@ describe('lens_counter_rate', () => {
expect(result.rows.map((row) => row.output)).toEqual([undefined, 7 - 5, NaN, 2, 5 - 2]);
});

it('copies over meta information from the source column', () => {
const result = runFn(
it('copies over meta information from the source column', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [
Expand Down Expand Up @@ -346,8 +346,8 @@ describe('lens_counter_rate', () => {
});
});

it('sets output name on output column if specified', () => {
const result = runFn(
it('sets output name on output column if specified', async () => {
const result = await runFn(
{
type: 'datatable',
columns: [
Expand All @@ -370,7 +370,7 @@ describe('lens_counter_rate', () => {
});
});

it('returns source table if input column does not exist', () => {
it('returns source table if input column does not exist', async () => {
const input: Datatable = {
type: 'datatable',
columns: [
Expand All @@ -384,12 +384,16 @@ describe('lens_counter_rate', () => {
],
rows: [{ val: 5 }],
};
expect(runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output' })).toBe(input);
expect(await runFn(input, { inputColumnId: 'nonexisting', outputColumnId: 'output' })).toBe(
input
);
});

it('throws an error if output column exists already', () => {
expect(() =>
runFn(
it('throws an error if output column exists already', async () => {
let error: Error | undefined;

try {
await runFn(
{
type: 'datatable',
columns: [
Expand All @@ -404,7 +408,13 @@ describe('lens_counter_rate', () => {
rows: [{ val: 5 }],
},
{ inputColumnId: 'val', outputColumnId: 'val' }
)
).toThrow();
);
} catch (e) {
error = e;
}

expect(error).toMatchInlineSnapshot(
`[Error: Specified outputColumnId val already exists. Please pick another column id.]`
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 {
buildResultColumns,
getBucketIdentifier,
} from '../../../../../../src/plugins/expressions/common';
import type { CounterRateExpressionFunction } from './types';

export const counterRateFn: CounterRateExpressionFunction['fn'] = (
input,
{ by, inputColumnId, outputColumnId, outputColumnName }
) => {
const resultColumns = buildResultColumns(input, outputColumnId, inputColumnId, outputColumnName);

if (!resultColumns) {
return input;
}
const previousValues: Partial<Record<string, number>> = {};

return {
...input,
columns: resultColumns,
rows: input.rows.map((row) => {
const newRow = { ...row };

const bucketIdentifier = getBucketIdentifier(row, by);
const previousValue = previousValues[bucketIdentifier];
const currentValue = newRow[inputColumnId];
if (currentValue != null && previousValue != null) {
const currentValueAsNumber = Number(currentValue);
if (currentValueAsNumber >= previousValue) {
newRow[outputColumnId] = currentValueAsNumber - previousValue;
} else {
newRow[outputColumnId] = currentValueAsNumber;
}
} else {
newRow[outputColumnId] = undefined;
}

if (currentValue != null) {
previousValues[bucketIdentifier] = Number(currentValue);
} else {
previousValues[bucketIdentifier] = undefined;
}

return newRow;
}),
};
};
64 changes: 7 additions & 57 deletions x-pack/plugins/lens/common/expressions/counter_rate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,8 @@
*/

import { i18n } from '@kbn/i18n';
import {
getBucketIdentifier,
buildResultColumns,
} from '../../../../../../src/plugins/expressions/common';
import type {
ExpressionFunctionDefinition,
Datatable,
} from '../../../../../../src/plugins/expressions/common';

import type { CounterRateExpressionFunction } from './types';

export interface CounterRateArgs {
by?: string[];
Expand All @@ -22,13 +16,6 @@ export interface CounterRateArgs {
outputColumnName?: string;
}

export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition<
'lens_counter_rate',
Datatable,
CounterRateArgs,
Datatable
>;

/**
* Calculates the counter rate of a specified column in the data table.
*
Expand Down Expand Up @@ -59,7 +46,7 @@ export type ExpressionFunctionCounterRate = ExpressionFunctionDefinition<
* before comparison. If the values are objects, the return value of their `toString` method will be used for comparison.
* Missing values (`null` and `undefined`) will be treated as empty strings.
*/
export const counterRate: ExpressionFunctionCounterRate = {
export const counterRate: CounterRateExpressionFunction = {
name: 'lens_counter_rate',
type: 'datatable',

Expand Down Expand Up @@ -101,46 +88,9 @@ export const counterRate: ExpressionFunctionCounterRate = {
},
},

fn(input, { by, inputColumnId, outputColumnId, outputColumnName }) {
const resultColumns = buildResultColumns(
input,
outputColumnId,
inputColumnId,
outputColumnName
);

if (!resultColumns) {
return input;
}
const previousValues: Partial<Record<string, number>> = {};
return {
...input,
columns: resultColumns,
rows: input.rows.map((row) => {
const newRow = { ...row };

const bucketIdentifier = getBucketIdentifier(row, by);
const previousValue = previousValues[bucketIdentifier];
const currentValue = newRow[inputColumnId];
if (currentValue != null && previousValue != null) {
const currentValueAsNumber = Number(currentValue);
if (currentValueAsNumber >= previousValue) {
newRow[outputColumnId] = currentValueAsNumber - previousValue;
} else {
newRow[outputColumnId] = currentValueAsNumber;
}
} else {
newRow[outputColumnId] = undefined;
}

if (currentValue != null) {
previousValues[bucketIdentifier] = Number(currentValue);
} else {
previousValues[bucketIdentifier] = undefined;
}

return newRow;
}),
};
async fn(...args) {
/** Build optimization: prevent adding extra code into initial bundle **/
const { counterRateFn } = await import('./counter_rate_fn');
return counterRateFn(...args);
},
};
16 changes: 16 additions & 0 deletions x-pack/plugins/lens/common/expressions/counter_rate/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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 { Datatable, ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions';
import { CounterRateArgs } from './index';

export type CounterRateExpressionFunction = ExpressionFunctionDefinition<
'lens_counter_rate',
Datatable,
CounterRateArgs,
Datatable | Promise<Datatable>
>;
Loading